org.browsermob.proxy.jetty.util.jmx.ModelMBeanImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.browsermob.proxy.jetty.util.jmx.ModelMBeanImpl.java

Source

// ========================================================================
// $Id: ModelMBeanImpl.java,v 1.18 2005/08/13 00:01:28 gregwilkins Exp $
// Copyright 1999-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed 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.browsermob.proxy.jetty.util.jmx;

import org.apache.commons.logging.Log;
import org.browsermob.proxy.jetty.log.LogFactory;
import org.browsermob.proxy.jetty.util.LogSupport;
import org.browsermob.proxy.jetty.util.TypeUtil;

import javax.management.*;
import javax.management.modelmbean.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/* ------------------------------------------------------------ */
/** Model MBean Implementation.
 * This implementation of the JMX Model MBean API is designed to allow
 * easy creation of Model MBeans. From minimal descriptions of
 * operations and attributes, reflection is used to determine the full
 * signature and ResourceBundles are used to determine other meta data.
 *
 * This class is normally used in one of the following patterns:<UL>
 * <LI>As a base class for a real MBean that contains the actual
 * attributes and operations of the MBean.  Such an Object is only
 * usable in a JMX environment.
 * <LI>As a proxy MBean to another non-JMX object. The attributes and
 * operations of the proxied object are defined in the MBean.  This
 * pattern is used when an existing non-JMX objects API is to be
 * exposed as an MBean.
 * <LI>As a base class for a proxy MBean. The attributes and oepration
 * of the MBean are implemented by the derived class but delegate to
 * one or more other objects. This pattern is used if existing objects
 * are to be managed by JMX, but a new management API needs to be
 * defined.
 * </UL>
 *
 * @version $Revision: 1.18 $
 * @author Greg Wilkins (gregw)
 */
public class ModelMBeanImpl implements ModelMBean, MBeanRegistration {
    private static Log log = LogFactory.getLog(ModelMBeanImpl.class);

    public final static int IMPACT_ACTION = MBeanOperationInfo.ACTION;
    public final static int IMPACT_ACTION_INFO = MBeanOperationInfo.ACTION_INFO;
    public final static int IMPACT_INFO = MBeanOperationInfo.INFO;
    public final static int IMPACT_UNKOWN = MBeanOperationInfo.UNKNOWN;

    public final static String STRING = "java.lang.String";
    public final static String OBJECT = "java.lang.Object";
    public final static String INT = "int";

    public final static String[] NO_PARAMS = new String[0];

    public final static boolean READ_WRITE = true;
    public final static boolean READ_ONLY = false;
    public final static boolean ON_MBEAN = true;
    public final static boolean ON_OBJECT = false;

    private static HashMap __objectId = new HashMap();

    private static String __defaultDomain = "org.browsermob.proxy.jetty";

    protected ModelMBeanInfoSupport _beanInfo;
    private MBeanServer _mBeanServer;
    private Object _object;
    private ObjectName _objectName;

    private boolean _dirty = false;
    private HashMap _getter = new HashMap(4);
    private HashMap _setter = new HashMap(4);
    private HashMap _method = new HashMap(4);
    private ArrayList _attributes = new ArrayList(4);
    private ArrayList _operations = new ArrayList(4);
    private ArrayList _notifications = new ArrayList(4);
    private String _baseObjectName = null;
    private Map _components = new HashMap(4);

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /** Create MBean for Object.
     * Attempts to create an MBean for the object by searching the
     * package and class name space.  For example an object of the
     * type <PRE>
     *   class com.acme.MyClass extends com.acme.util.BaseClass
     * </PRE>
     * Then this method would look for the following
     * classes:<UL>
     * <LI>com.acme.MyClassMBean
     * <LI>com.acme.jmx.MyClassMBean
     * <LI>com.acme.util.BaseClassMBean
     * <LI>com.acme.util.jmx.BaseClassMBean
     * </UL>
     * @param o The object
     * @return A new instance of an MBean for the object or null.
     */
    public static ModelMBean mbeanFor(Object o) {
        try {
            Class oClass = o.getClass();
            ClassLoader loader = oClass.getClassLoader();

            ModelMBean mbean = null;
            boolean jmx = false;
            Class[] interfaces = null;
            int i = 0;

            while (mbean == null && oClass != null) {
                Class focus = interfaces == null ? oClass : interfaces[i];
                String pName = focus.getPackage().getName();
                String cName = focus.getName().substring(pName.length() + 1);
                String mName = pName + (jmx ? ".jmx." : ".") + cName + "MBean";

                try {
                    Class mClass = loader.loadClass(mName);
                    if (log.isTraceEnabled())
                        log.trace("mbeanFor " + o + " mClass=" + mClass);
                    mbean = (ModelMBean) mClass.newInstance();
                    mbean.setManagedResource(o, "objectReference");
                    if (log.isDebugEnabled())
                        log.debug("mbeanFor " + o + " is " + mbean);
                    return mbean;
                } catch (ClassNotFoundException e) {
                    if (e.toString().endsWith("MBean")) {
                        if (log.isTraceEnabled())
                            log.trace(e.toString());
                    } else
                        log.warn(LogSupport.EXCEPTION, e);
                } catch (Error e) {
                    log.warn(LogSupport.EXCEPTION, e);
                    mbean = null;
                } catch (Exception e) {
                    log.warn(LogSupport.EXCEPTION, e);
                    mbean = null;
                }

                if (jmx) {
                    if (interfaces != null) {
                        i++;
                        if (i >= interfaces.length) {
                            interfaces = null;
                            oClass = oClass.getSuperclass();
                        }
                    } else {
                        interfaces = oClass.getInterfaces();
                        i = 0;
                        if (interfaces == null || interfaces.length == 0) {
                            interfaces = null;
                            oClass = oClass.getSuperclass();
                        }
                    }
                }
                jmx = !jmx;
            }
        } catch (Exception e) {
            LogSupport.ignore(log, e);
        }
        return null;
    }

    /* ------------------------------------------------------------ */
    /** MBean Constructor.
     * No proxy object is defined.  Attributes and operations are
     * defined on this instance. 
     */
    public ModelMBeanImpl() {
    }

    /* ------------------------------------------------------------ */
    /** Proxy MBean Constructor. 
     * @param proxyObject The actual object on which attributes and
     * operations are to be defined and called. 
     */
    public ModelMBeanImpl(Object proxyObject) {
        try {
            setManagedResource(proxyObject, "objectReference");
        } catch (Exception e) {
            log.warn(LogSupport.EXCEPTION, e);
            throw new IllegalArgumentException(e.toString());
        }
    }

    /* ------------------------------------------------------------ */
    public static String getDefaultDomain() {
        return __defaultDomain;
    }

    /* ------------------------------------------------------------ */
    public static void setDefaultDomain(String d) {
        __defaultDomain = d;
    }

    /* ------------------------------------------------------------ */
    public MBeanServer getMBeanServer() {
        return _mBeanServer;
    }

    /* ------------------------------------------------------------ */
    public ObjectName getObjectName() {
        return _objectName;
    }

    /* ------------------------------------------------------------ */
    public Object getManagedResource() {
        return _object;
    }

    /* ------------------------------------------------------------ */
    public void setManagedResource(Object proxyObject, String type) throws MBeanException,
            RuntimeOperationsException, InstanceNotFoundException, InvalidTargetObjectTypeException {
        if (proxyObject == null) {
            proxyObject = null;
            return;
        }

        log.debug("setManagedResource");
        if (!"objectreference".equalsIgnoreCase(type))
            throw new InvalidTargetObjectTypeException(type);

        if (_object == null) {
            // first set so define attributes etc.
            _object = proxyObject;

            defineManagedResource();
        } else
            _object = proxyObject;
    }

    /* ------------------------------------------------------------ */
    /** Define the Managed Resource.
     * This method is called the first time setManagedResource is
     * called with a non-null object. It should be implemented by a
     * derived ModelMBean to define the attributes and operations
     * after an initial object has been set.
     */
    protected void defineManagedResource() {
    }

    /* ------------------------------------------------------------ */
    /** Not Supported.
     * Use RequiredModelMBean for this style of MBean creation.
     */
    public void setModelMBeanInfo(ModelMBeanInfo info) throws MBeanException, RuntimeOperationsException {
        throw new Error("setModelMBeanInfo not supported");
    }

    /* ------------------------------------------------------------ */
    /** Define an attribute on the managed object.
     * The meta data is defined by looking for standard getter and
     * setter methods. Descriptions are obtained with a call to
     * findDescription with the attribute name.
     * @param name The name of the attribute. Normal java bean
     * capitlization is enforced on this name.
     */
    public synchronized void defineAttribute(String name) {
        defineAttribute(name, true, false);
    }

    /* ------------------------------------------------------------ */
    /** Define an attribute on the managed object.
     * The meta data is defined by looking for standard getter and
     * setter methods. Descriptions are obtained with a call to
     * findDescription with the attribute name.
     * @param name The name of the attribute. Normal java bean
     * capitlization is enforced on this name.
     * @param writable If false, do not look for a setter.
     */
    public synchronized void defineAttribute(String name, boolean writable) {
        defineAttribute(name, writable, false);
    }

    /* ------------------------------------------------------------ */
    /** Define an attribute on the managed object.
     * The meta data is defined by looking for standard getter and
     * setter methods. Descriptions are obtained with a call to
     * findDescription with the attribute name.
     * @param name The name of the attribute. Normal java bean
     * capitlization is enforced on this name.
     * @param writable If false, do not look for a setter.
     * @param onMBean .
     */
    public synchronized void defineAttribute(String name, boolean writable, boolean onMBean) {
        _dirty = true;

        String uName = name.substring(0, 1).toUpperCase() + name.substring(1);
        name = java.beans.Introspector.decapitalize(name);
        Class oClass = onMBean ? this.getClass() : _object.getClass();

        Class type = null;
        Method getter = null;
        Method setter = null;
        Method[] methods = oClass.getMethods();
        for (int m = 0; m < methods.length; m++) {
            if ((methods[m].getModifiers() & Modifier.PUBLIC) == 0)
                continue;

            // Look for a getter
            if (methods[m].getName().equals("get" + uName) && methods[m].getParameterTypes().length == 0) {
                if (getter != null)
                    throw new IllegalArgumentException("Multiple getters for attr " + name);
                getter = methods[m];
                if (type != null && !type.equals(methods[m].getReturnType()))
                    throw new IllegalArgumentException("Type conflict for attr " + name);
                type = methods[m].getReturnType();
            }

            // Look for an is getter
            if (methods[m].getName().equals("is" + uName) && methods[m].getParameterTypes().length == 0) {
                if (getter != null)
                    throw new IllegalArgumentException("Multiple getters for attr " + name);
                getter = methods[m];
                if (type != null && !type.equals(methods[m].getReturnType()))
                    throw new IllegalArgumentException("Type conflict for attr " + name);
                type = methods[m].getReturnType();
            }

            // look for a setter
            if (writable && methods[m].getName().equals("set" + uName)
                    && methods[m].getParameterTypes().length == 1) {
                if (setter != null)
                    throw new IllegalArgumentException("Multiple setters for attr " + name);
                setter = methods[m];
                if (type != null && !type.equals(methods[m].getParameterTypes()[0]))
                    throw new IllegalArgumentException("Type conflict for attr " + name);
                type = methods[m].getParameterTypes()[0];
            }
        }

        if (getter == null && setter == null)
            throw new IllegalArgumentException("No getter or setters found for " + name);

        try {
            // Remember the methods
            _getter.put(name, getter);
            _setter.put(name, setter);
            // create and add the info
            _attributes.add(new ModelMBeanAttributeInfo(name, findDescription(name), getter, setter));
        } catch (Exception e) {
            log.warn(LogSupport.EXCEPTION, e);
            throw new IllegalArgumentException(e.toString());
        }
    }

    /* ------------------------------------------------------------ */
    /** Define an attribute.
     * Explicit definition of an attribute. Reflection is used to
     * locate the actual getter and setter methods.
     * @param attrInfo ModelMBeanAttributeInfo.
     */
    public synchronized void defineAttribute(ModelMBeanAttributeInfo attrInfo) {
        if (_object == null)
            throw new IllegalStateException("No Object");

        _dirty = true;

        String name = attrInfo.getName();
        String uName = name.substring(0, 1).toUpperCase() + name.substring(1);
        Class oClass = _object.getClass();

        try {
            Class type = TypeUtil.fromName(attrInfo.getType());
            if (type == null)
                type = Thread.currentThread().getContextClassLoader().loadClass(attrInfo.getType());

            Method getter = null;
            Method setter = null;

            if (attrInfo.isReadable())
                getter = oClass.getMethod((attrInfo.isIs() ? "is" : "get") + uName, (java.lang.Class[]) null);

            if (attrInfo.isWritable())
                setter = oClass.getMethod("set" + uName, new Class[] { type });

            _getter.put(name, getter);
            _setter.put(name, setter);
            _attributes.add(attrInfo);
        } catch (Exception e) {
            log.warn(LogSupport.EXCEPTION, e);
            throw new IllegalArgumentException(e.toString());
        }
    }

    /* ------------------------------------------------------------ */
    /** Define an operation on the managed object.
     * Defines an operation with no parameters. Refection is used to
     * determine the return type and the description is found with a
     * call to findDescription on "name()".
     * @param name  Name of the method call
     * @param impact Impact as defined in MBeanOperationInfo
     */
    public synchronized void defineOperation(String name, int impact) {
        defineOperation(name, null, impact, false);
    }

    /* ------------------------------------------------------------ */
    /** Define an operation on the managed object.
     * Defines an operation with parameters. Refection is used to
     * determine find the method and it's return type. The description
     * of the method is found with a call to findDescription on
     * "name(signature)". The name and description of each parameter
     * is found with a call to findDescription with
     * "name(partialSignature", the returned description is for the
     * last parameter of the partial signature and is assumed to start
     * with the parameter name, followed by a colon.
     * @param name The name of the method call.
     * @param signature The types of the operation parameters.
     * @param impact Impact as defined in MBeanOperationInfo
     */
    public synchronized void defineOperation(String name, String[] signature, int impact) {
        defineOperation(name, signature, impact, false);
    }

    /* ------------------------------------------------------------ */
    /** Define an operation on the managed object.
     * Defines an operation with parameters. Refection is used to
     * determine find the method and it's return type. The description
     * of the method is found with a call to findDescription on
     * "name(signature)". The name and description of each parameter
     * is found with a call to findDescription with
     * "name(partialSignature", the returned description is for the
     * last parameter of the partial signature and is assumed to start
     * with the parameter name, followed by a colon.
     * @param name The name of the method call.
     * @param signature The types of the operation parameters.
     * @param impact Impact as defined in MBeanOperationInfo
     * @param onMBean true if the operation is defined on the mbean
     */
    public synchronized void defineOperation(String name, String[] signature, int impact, boolean onMBean) {
        _dirty = true;
        Class oClass = onMBean ? this.getClass() : _object.getClass();
        if (signature == null)
            signature = new String[0];

        try {
            Class[] types = new Class[signature.length];
            MBeanParameterInfo[] pInfo = new MBeanParameterInfo[signature.length];

            // Check types and build methodKey
            String methodKey = name + "(";
            for (int i = 0; i < signature.length; i++) {
                Class type = TypeUtil.fromName(signature[i]);
                if (type == null)
                    type = Thread.currentThread().getContextClassLoader().loadClass(signature[i]);
                types[i] = type;
                signature[i] = type.isPrimitive() ? TypeUtil.toName(type) : signature[i];
                methodKey += (i > 0 ? "," : "") + signature[i];
            }
            methodKey += ")";

            // Build param infos
            for (int i = 0; i < signature.length; i++) {
                String description = findDescription(methodKey + "[" + i + "]");
                int colon = description.indexOf(":");
                if (colon < 0) {
                    description = "param" + i + ":" + description;
                    colon = description.indexOf(":");
                }
                pInfo[i] = new MBeanParameterInfo(description.substring(0, colon).trim(), signature[i],
                        description.substring(colon + 1).trim());
            }

            // build the operation info
            Method method = oClass.getMethod(name, types);
            Class returnClass = method.getReturnType();
            _method.put(methodKey, method);
            _operations.add(new ModelMBeanOperationInfo(name, findDescription(methodKey), pInfo,
                    returnClass.isPrimitive() ? TypeUtil.toName(returnClass) : (returnClass.getName()), impact));
        } catch (Exception e) {
            log.warn("operation " + name, e);
            throw new IllegalArgumentException(e.toString());
        }

    }

    /* ------------------------------------------------------------ */
    /** Define an operation.
     * Explicit definition of an operation. Reflection is used to
     * locate method called.
     * @param opInfo 
     */
    public synchronized void defineOperation(ModelMBeanOperationInfo opInfo) {
        _dirty = true;
        Class oClass = _object.getClass();

        try {
            MBeanParameterInfo[] pInfo = opInfo.getSignature();

            Class[] types = new Class[pInfo.length];
            String method = opInfo.getName() + "(";
            for (int i = 0; i < pInfo.length; i++) {
                Class type = TypeUtil.fromName(pInfo[i].getType());
                if (type == null)
                    type = Thread.currentThread().getContextClassLoader().loadClass(pInfo[i].getType());
                types[i] = type;
                method += (i > 0 ? "," : "") + pInfo[i].getType();
            }
            method += ")";

            _method.put(method, oClass.getMethod(opInfo.getName(), types));
            _operations.add(opInfo);
        } catch (Exception e) {
            log.warn(LogSupport.EXCEPTION, e);
            throw new IllegalArgumentException(e.toString());
        }
    }

    /* ------------------------------------------------------------ */
    public synchronized MBeanInfo getMBeanInfo() {
        log.debug("getMBeanInfo");

        if (_dirty) {
            _dirty = false;
            ModelMBeanAttributeInfo[] attributes = (ModelMBeanAttributeInfo[]) _attributes
                    .toArray(new ModelMBeanAttributeInfo[_attributes.size()]);
            ModelMBeanOperationInfo[] operations = (ModelMBeanOperationInfo[]) _operations
                    .toArray(new ModelMBeanOperationInfo[_operations.size()]);
            ModelMBeanNotificationInfo[] notifications = (ModelMBeanNotificationInfo[]) _notifications
                    .toArray(new ModelMBeanNotificationInfo[_notifications.size()]);

            _beanInfo = new ModelMBeanInfoSupport(_object.getClass().getName(), findDescription(null), attributes,
                    null, operations, notifications);
        }

        return _beanInfo;
    }

    /* ------------------------------------------------------------ */
    public Object getAttribute(String name) throws AttributeNotFoundException, MBeanException, ReflectionException {
        if (log.isDebugEnabled())
            log.debug("getAttribute " + name);
        Method getter = (Method) _getter.get(name);
        if (getter == null)
            throw new AttributeNotFoundException(name);
        try {
            Object o = _object;
            if (getter.getDeclaringClass().isInstance(this))
                o = this;
            return getter.invoke(o, (java.lang.Object[]) null);
        } catch (IllegalAccessException e) {
            log.warn(LogSupport.EXCEPTION, e);
            throw new AttributeNotFoundException(e.toString());
        } catch (InvocationTargetException e) {
            log.warn(LogSupport.EXCEPTION, e);
            throw new ReflectionException((Exception) e.getTargetException());
        }
    }

    /* ------------------------------------------------------------ */
    public AttributeList getAttributes(String[] names) {
        log.debug("getAttributes");
        AttributeList results = new AttributeList(names.length);
        for (int i = 0; i < names.length; i++) {
            try {
                results.add(new Attribute(names[i], getAttribute(names[i])));
            } catch (Exception e) {
                log.warn(LogSupport.EXCEPTION, e);
            }
        }
        return results;
    }

    /* ------------------------------------------------------------ */
    public void setAttribute(Attribute attr)
            throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
        if (attr == null)
            return;

        if (log.isDebugEnabled())
            log.debug("setAttribute " + attr.getName() + "=" + attr.getValue());
        Method setter = (Method) _setter.get(attr.getName());
        if (setter == null)
            throw new AttributeNotFoundException(attr.getName());
        try {
            Object o = _object;
            if (setter.getDeclaringClass().isInstance(this))
                o = this;
            setter.invoke(o, new Object[] { attr.getValue() });
        } catch (IllegalAccessException e) {
            log.warn(LogSupport.EXCEPTION, e);
            throw new AttributeNotFoundException(e.toString());
        } catch (InvocationTargetException e) {
            log.warn(LogSupport.EXCEPTION, e);
            throw new ReflectionException((Exception) e.getTargetException());
        }
    }

    /* ------------------------------------------------------------ */
    public AttributeList setAttributes(AttributeList attrs) {
        log.debug("setAttributes");

        AttributeList results = new AttributeList(attrs.size());
        Iterator iter = attrs.iterator();
        while (iter.hasNext()) {
            try {
                Attribute attr = (Attribute) iter.next();
                setAttribute(attr);
                results.add(new Attribute(attr.getName(), getAttribute(attr.getName())));
            } catch (Exception e) {
                log.warn(LogSupport.EXCEPTION, e);
            }
        }
        return results;
    }

    /* ------------------------------------------------------------ */
    public Object invoke(String name, Object[] params, String[] signature)
            throws MBeanException, ReflectionException {
        if (log.isDebugEnabled())
            log.debug("invoke " + name);

        String methodKey = name + "(";
        if (signature != null)
            for (int i = 0; i < signature.length; i++)
                methodKey += (i > 0 ? "," : "") + signature[i];
        methodKey += ")";

        try {
            Method method = (Method) _method.get(methodKey);
            if (method == null)
                throw new NoSuchMethodException(methodKey);

            Object o = _object;
            if (method.getDeclaringClass().isInstance(this))
                o = this;
            return method.invoke(o, params);
        } catch (NoSuchMethodException e) {
            log.warn(LogSupport.EXCEPTION, e);
            throw new ReflectionException(e);
        } catch (IllegalAccessException e) {
            log.warn(LogSupport.EXCEPTION, e);
            throw new MBeanException(e);
        } catch (InvocationTargetException e) {
            log.warn(LogSupport.EXCEPTION, e);
            throw new ReflectionException((Exception) e.getTargetException());
        }

    }

    /* ------------------------------------------------------------ */
    public void load() throws MBeanException, RuntimeOperationsException, InstanceNotFoundException {
        log.debug("load");
    }

    /* ------------------------------------------------------------ */
    public void store() throws MBeanException, RuntimeOperationsException, InstanceNotFoundException {
        log.debug("store");
    }

    /* ------------------------------------------------------------ */
    public void addNotificationListener(NotificationListener listener, NotificationFilter filter, Object handback)
            throws IllegalArgumentException {
        log.debug("addNotificationListener");
    }

    /* ------------------------------------------------------------ */
    public MBeanNotificationInfo[] getNotificationInfo() {
        log.debug("getNotificationInfo");
        return null;
    }

    /* ------------------------------------------------------------ */
    public void removeNotificationListener(NotificationListener listener) throws ListenerNotFoundException {
        log.debug("removeNotificationListener");
    }

    /* ------------------------------------------------------------ */
    public void addAttributeChangeNotificationListener(NotificationListener listener, String name, Object handback)
            throws MBeanException, RuntimeOperationsException, IllegalArgumentException {
        log.debug("addAttributeChangeNotificationListener");
    }

    /* ------------------------------------------------------------ */
    public void removeAttributeChangeNotificationListener(NotificationListener listener, String name)
            throws MBeanException, RuntimeOperationsException, ListenerNotFoundException {
        log.debug("removeAttributeChangeNotificationListener");
    }

    /* ------------------------------------------------------------ */
    public void sendAttributeChangeNotification(Attribute oldAttr, Attribute newAttr)
            throws MBeanException, RuntimeOperationsException {
        log.debug("sendAttributeChangeNotification");
    }

    /* ------------------------------------------------------------ */
    public void sendAttributeChangeNotification(AttributeChangeNotification notify)
            throws MBeanException, RuntimeOperationsException {
        log.debug("sendAttributeChangeNotification");
    }

    /* ------------------------------------------------------------ */
    public void sendNotification(String notify) throws MBeanException, RuntimeOperationsException {
        log.debug("sendNotification");
    }

    /* ------------------------------------------------------------ */
    public void sendNotification(Notification notify) throws MBeanException, RuntimeOperationsException {
        log.debug("sendNotification");
    }

    /* ------------------------------------------------------------ */
    /* Find MBean descriptions.
     * MBean descriptions are searched for in ResourceBundles. Bundles
     * are looked for in a mbean.property files within each package of
     * the MBean class inheritance hierachy.
     * Once a bundle is found, the key is added to object names in the
     * following order: fully qualied managed resource class name, tail
     * managed resource class name, tail mbean class name. The string
     * "MBean" is stripped from the tail of any name.
     * <P>For example, if the class a.b.C is managed by a MBean
     * p.q.RMBean which is derived from p.SMBean, then the seach order
     * for a key x is as follows:<PRE>
     *   bundle: p.q.mbean    name: a.b.C.x
     *   bundle: p.q.mbean    name: C.x
     *   bundle: p.q.mbean    name: R.x
     *   bundle: p.mbean      name: a.b.C.x
     *   bundle: p.mbean      name: C.x
     *   bundle: p.mbean      name: S.x
     * </PRE>
     * <P>The convention used for keys passed to this method are:<PRE>
     *   null or empty         - Object description
     *   xxx                   - Attribute xxx description
     *   xxx()                 - Simple operation xxx description
     *   xxx(type,..)          - Operation xxx with signature desciption
     *   xxx(type,..)[n]       - Param n of operation xxx description
     * </PRE>
     * @param key 
     * @return Description string.
     */
    private String findDescription(String key) {
        Class lookIn = this.getClass();

        // Array of possible objectNames
        String[] objectNames = new String[3];
        objectNames[0] = _object.getClass().getName();
        if (objectNames[0].indexOf(".") >= 0)
            objectNames[1] = objectNames[0].substring(objectNames[0].lastIndexOf(".") + 1);

        while (lookIn != null) {
            String pkg = lookIn.getName();
            int lastDot = pkg.lastIndexOf(".");
            if (lastDot > 0) {
                objectNames[2] = pkg.substring(lastDot + 1);
                pkg = pkg.substring(0, lastDot);
            } else {
                objectNames[2] = pkg;
                pkg = null;
            }

            String resource = (pkg == null ? "mbean" : (pkg.replace('.', '/') + "/mbean"));
            if (log.isTraceEnabled())
                log.trace("Look for: " + resource);

            try {
                ResourceBundle bundle = ResourceBundle.getBundle(resource, Locale.getDefault(),
                        _object.getClass().getClassLoader());

                if (log.isTraceEnabled())
                    log.trace("Bundle " + resource);

                for (int i = 0; i < objectNames.length; i++) {
                    String name = objectNames[i];

                    if (name == null)
                        continue;
                    if (name.endsWith("MBean"))
                        name = name.substring(0, name.length() - 5);
                    if (key != null && key.length() > 0)
                        name += "." + key;

                    try {
                        String description = bundle.getString(name);
                        if (description != null && description.length() > 0)
                            return description;
                    } catch (Exception e) {
                        if (log.isTraceEnabled())
                            log.trace(e.toString());
                    }
                }
            } catch (Exception e) {
                if (log.isTraceEnabled())
                    log.trace(e.toString());
            }

            lookIn = lookIn.getSuperclass();
        }

        if (key == null || key.length() == 0)
            return objectNames[0];

        return key;
    }

    /* ------------------------------------------------------------ */
    /** Create a new ObjectName.
     * Return a new object name. The default implementation is the
     * results of uniqueObjectName(baseObjectName), if baseObjectName
     * is not set, then the results of uniqueObjectName(defaultDomain+":");
     * @return The Object name
     */
    protected ObjectName newObjectName(MBeanServer server) {
        // Create own ObjectName of the form:
        // package:class=id
        if (_baseObjectName != null) {
            if (_baseObjectName.indexOf(':') >= 0)
                return uniqueObjectName(server, _baseObjectName);
            return uniqueObjectName(server, getDefaultDomain() + ":" + _baseObjectName);
        }
        return uniqueObjectName(server, getDefaultDomain() + ":");
    }

    /* ------------------------------------------------------------ */
    public void setBaseObjectName(String s) {
        _baseObjectName = s;
    }

    /* ------------------------------------------------------------ */
    public String getBaseObjectName() {
        return _baseObjectName;
    }

    /* ------------------------------------------------------------ */
    /** Pre registration notification.
     * If this method is specialized by a derived class that may set
     * the objectName, then it should call this implementation with
     * the new objectName.
     * @param server 
     * @param oName 
     * @return The ObjectName to use.
     */
    public synchronized ObjectName preRegister(MBeanServer server, ObjectName oName) {
        _mBeanServer = server;
        _objectName = oName;
        if (_objectName == null) {
            try {
                oName = newObjectName(server);
            } catch (Exception e) {
                log.warn(LogSupport.EXCEPTION, e);
            }
        }
        if (log.isDebugEnabled())
            log.debug("preRegister " + _objectName + " -> " + oName);
        _objectName = oName;

        return _objectName;
    }

    /* ------------------------------------------------------------ */
    public void postRegister(Boolean ok) {
        if (ok.booleanValue())
            log.info("Registered " + _objectName);
        else {
            _mBeanServer = null;
            _objectName = null;
        }
    }

    /* ------------------------------------------------------------ */
    public void preDeregister() {
        log.info("Deregister " + _objectName);
        getComponentMBeans(null, _components);
        _components.clear();
    }

    /* ------------------------------------------------------------ */
    /** Post Deregister.
     * This implementation destroys this MBean and it cannot be used again.
     */
    public void postDeregister() {
        _beanInfo = null;
        _mBeanServer = null;
        _object = null;
        _objectName = null;
        if (_getter != null)
            _getter.clear();
        _getter = null;
        if (_setter != null)
            _setter.clear();
        _setter = null;
        if (_method != null)
            _method.clear();
        _method = null;
        if (_attributes != null)
            _attributes.clear();
        _attributes = null;
        if (_operations != null)
            _operations.clear();
        _operations = null;
        if (_notifications != null)
            _notifications.clear();
        _notifications = null;
    }

    /* ------------------------------------------------------------ */
    /** Add an id clause to a JMX object name.
     * Used to make unique objectnames when there are no other
     * distinguishing attributes.
     * If the passed object name ends with '=', just a unique ID is
     * added.  Otherwise and classname= clause is added.
     * @param objectName 
     * @return objectName with id= class.
     */
    public synchronized ObjectName uniqueObjectName(MBeanServer server, String objectName) {
        return uniqueObjectName(server, _object, objectName);
    }

    /* ------------------------------------------------------------ */
    public synchronized ObjectName uniqueObjectName(MBeanServer server, Object object, String objectName) {
        if (!objectName.endsWith("=")) {
            String className = object.getClass().getName();
            if (className.indexOf(".") > 0)
                className = className.substring(className.lastIndexOf(".") + 1);
            if (className.endsWith("MBean"))
                className = className.substring(0, className.length() - 5);
            if (!objectName.endsWith(":"))
                objectName += ",";
            objectName += className + "=";
        }

        ObjectName oName = null;
        try {
            while (true) {
                Integer id = (Integer) __objectId.get(objectName);
                if (id == null)
                    id = new Integer(0);
                oName = new ObjectName(objectName + id);
                id = new Integer(id.intValue() + 1);
                __objectId.put(objectName, id);

                // If no server, this must be unique
                if (server == null)
                    break;

                // Otherwise let's check it is unique
                // if not found then it is unique
                if (!server.isRegistered(oName))
                    break;
            }
        } catch (Exception e) {
            log.warn(LogSupport.EXCEPTION, e);
        }

        return oName;
    }

    /* ------------------------------------------------------------ */
    /** Get Component MBeans.
     * Creates, registers and deregisters MBeans for an array of components.
     * On each call the passed map is used to determine components that have
     * already been registers and those that need to be deregistered.
     * @param components the components.
     * @param map A map of previously registered components to object
     * name. If null is passed, a default map for the mbean is used.
     * @return An array of ObjectNames for each component. 
     */
    protected ObjectName[] getComponentMBeans(Object[] components, Map map) {
        if (map == null)
            map = _components;
        ObjectName[] beans = null;
        if (components == null)
            beans = new ObjectName[0];
        else {
            beans = new ObjectName[components == null ? 0 : components.length];

            // Add new beans
            for (int i = 0; i < components.length; i++) {
                ObjectName on = (ObjectName) map.get(components[i]);
                if (on == null) {
                    ModelMBean mbean = mbeanFor(components[i]);
                    if (mbean == null)
                        log.warn("No mbean for " + components[i]);
                    else {
                        try {
                            if (mbean instanceof ModelMBeanImpl) {
                                ((ModelMBeanImpl) mbean).setBaseObjectName(getObjectName().toString());
                                on = getMBeanServer().registerMBean(mbean, null).getObjectName();
                            } else {
                                on = uniqueObjectName(getMBeanServer(), components[i], getObjectName().toString());
                                on = getMBeanServer().registerMBean(mbean, on).getObjectName();
                            }
                            map.put(components[i], on);
                        } catch (Exception e) {
                            log.warn(LogSupport.EXCEPTION, e);
                        }
                    }
                }
                beans[i] = on;
            }
        }

        // Delete old beans
        if (components == null || map.size() > components.length) {
            Object[] to_delete = new Object[map.size() - beans.length];
            int d = 0;
            Iterator iter = map.keySet().iterator();
            keys: while (iter.hasNext()) {
                Object bean = iter.next();
                if (components != null) {
                    for (int i = 0; i < components.length; i++)
                        if (components[i] == bean)
                            continue keys;
                }
                to_delete[d++] = bean;
            }

            for (; d-- > 0;) {
                try {
                    getMBeanServer().unregisterMBean((ObjectName) map.remove(to_delete[d]));
                } catch (Exception e) {
                    log.warn(LogSupport.EXCEPTION, e);
                }
            }
        }

        return beans;
    }

    /** Unregister mbeans for already registered components
     * @param map
     */
    protected void destroyComponentMBeans(Map map) {
        //if no map of registered mbean names is passed,
        //use the default map
        if (null == map)
            map = _components;

        if (map == null)
            return;

        Iterator itor = map.values().iterator();
        while (itor.hasNext()) {
            try {
                ObjectName o = (ObjectName) itor.next();
                getMBeanServer().unregisterMBean(o);
                itor.remove();
            } catch (Exception e) {
                log.warn(LogSupport.EXCEPTION, e);
            }
        }

    }
}