/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.aries.jmx.whiteboard;

import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.management.DynamicMBean;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.StandardMBean;

import org.apache.aries.jmx.util.shared.RegistrableStandardEmitterMBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

final class MBeanHolder {

    /** default log */
    private final Logger log = LoggerFactory.getLogger(getClass());

    private final Object mbean;

    private final ObjectName requestedObjectName;

    private final Map<MBeanServer, ObjectName> registrations;

    static <T> MBeanHolder create(final T mbean,
            final ObjectName requestedObjectName) {
        if (mbean instanceof DynamicMBean) {
            return new MBeanHolder(mbean, requestedObjectName);
        } else if (mbean == null) {
            return null;
        }

        Class<?> mbeanClass = mbean.getClass();
        @SuppressWarnings("unchecked") // This is all in aid of getting new StandardMBean to work.
        Class<T> mbeanInterface = (Class<T>) getMBeanInterface(mbeanClass);
        if (mbeanInterface == null) {
            return null;
        }

        if (mbeanInterface.getName().equals(
            mbeanClass.getName().concat("MBean"))) {
            return new MBeanHolder(mbean, requestedObjectName);
        }

        try {
            StandardMBean stdMbean = new RegistrableStandardEmitterMBean(mbean, mbeanInterface);
            return new MBeanHolder(stdMbean, requestedObjectName);
        } catch (NotCompliantMBeanException e) {
            LoggerFactory.getLogger(MBeanHolder.class).error(
                "create: Cannot create StandardMBean for " + mbean
                    + " of type " + mbeanClass + " for interface "
                    + mbeanInterface, e);
            return null;
        }
    }

    private static Class<?> getMBeanInterface(final Class<?> mbeanClass) {
        if (mbeanClass == null) {
            return null;
        }

        for (Class<?> i : mbeanClass.getInterfaces()) {
            if (i.getName().endsWith("MBean")) {
                return i;
            }

            Class<?> mbeanInterface = getMBeanInterface(i);
            if (mbeanInterface != null) {
                return mbeanInterface;
            }
        }

        if (mbeanClass.getSuperclass() != null) {
            return getMBeanInterface(mbeanClass.getSuperclass());
        }

        return null;
    }

    private MBeanHolder(final Object mbean, final ObjectName requestedObjectName) {
        this.mbean = mbean;
        this.requestedObjectName = requestedObjectName;
        this.registrations = new IdentityHashMap<MBeanServer, ObjectName>();
    }

    void register(final MBeanServer server) {
        ObjectInstance instance;
        try {
            instance = server.registerMBean(mbean, requestedObjectName);
            registrations.put(server, instance.getObjectName());
        } catch (InstanceAlreadyExistsException e) {
            log.error("register: Failure registering MBean " + mbean, e);
        } catch (MBeanRegistrationException e) {
            log.error("register: Failure registering MBean " + mbean, e);
        } catch (NotCompliantMBeanException e) {
            log.error("register: Failure registering MBean " + mbean, e);
        }
    }

    void unregister(final MBeanServer server) {
        final ObjectName registeredName = registrations.remove(server);
        if (registeredName != null) {
            unregister(server, registeredName);
        }
    }

    void unregister() {
        for (Entry<MBeanServer, ObjectName> entry : registrations.entrySet()) {
            unregister(entry.getKey(), entry.getValue());
        }
        registrations.clear();
    }

    private void unregister(final MBeanServer server, final ObjectName name) {
        try {
            server.unregisterMBean(name);
        } catch (MBeanRegistrationException e) {
            log.error("unregister: preDeregister of " + name
                + " threw an exception", e);
        } catch (InstanceNotFoundException e) {
            // not really expected !
            log.error("unregister: Unexpected unregistration problem of MBean "
                + name, e);
        }
    }
}
