jdk/src/share/classes/com/sun/jmx/mbeanserver/MBeanIntrospector.java
changeset 2 90ce3da70b43
child 287 bff5501b2a02
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/jmx/mbeanserver/MBeanIntrospector.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,458 @@
+/*
+ * Copyright 2005-2006 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package com.sun.jmx.mbeanserver;
+
+
+import static com.sun.jmx.mbeanserver.Util.*;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.WeakHashMap;
+
+import javax.management.Descriptor;
+import javax.management.ImmutableDescriptor;
+import javax.management.InvalidAttributeValueException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.NotCompliantMBeanException;
+import javax.management.NotificationBroadcaster;
+import javax.management.ReflectionException;
+
+/**
+ * An introspector for MBeans of a certain type.  There is one instance
+ * of this class for Standard MBeans and one for MXBeans, characterized
+ * by the two concrete subclasses of this abstract class.
+ *
+ * @param <M> the representation of methods for this kind of MBean:
+ * Method for Standard MBeans, ConvertingMethod for MXBeans.
+ *
+ * @since 1.6
+ */
+/*
+ * Using a type parameter <M> allows us to deal with the fact that
+ * Method and ConvertingMethod have no useful common ancestor, on
+ * which we could call getName, getGenericReturnType, etc.  A simpler approach
+ * would be to wrap every Method in an object that does have a common
+ * ancestor with ConvertingMethod.  But that would mean an extra object
+ * for every Method in every Standard MBean interface.
+ */
+abstract class MBeanIntrospector<M> {
+    static final class PerInterfaceMap<M>
+            extends WeakHashMap<Class<?>, WeakReference<PerInterface<M>>> {}
+
+    /** The map from interface to PerInterface for this type of MBean. */
+    abstract PerInterfaceMap<M> getPerInterfaceMap();
+    /**
+     * The map from concrete implementation class and interface to
+     * MBeanInfo for this type of MBean.
+     */
+    abstract MBeanInfoMap getMBeanInfoMap();
+
+    /** Make an interface analyzer for this type of MBean. */
+    abstract MBeanAnalyzer<M> getAnalyzer(Class<?> mbeanInterface)
+    throws NotCompliantMBeanException;
+
+    /** True if MBeans with this kind of introspector are MXBeans. */
+    abstract boolean isMXBean();
+
+    /** Find the M corresponding to the given Method. */
+    abstract M mFrom(Method m);
+
+    /** Get the name of this method. */
+    abstract String getName(M m);
+
+    /**
+     * Get the return type of this method.  This is the return type
+     * of a method in a Java interface, so for MXBeans it is the
+     * declared Java type, not the mapped Open Type.
+     */
+    abstract Type getGenericReturnType(M m);
+
+    /**
+     * Get the parameter types of this method in the Java interface
+     * it came from.
+     */
+    abstract Type[] getGenericParameterTypes(M m);
+
+    /**
+     * Get the signature of this method as a caller would have to supply
+     * it in MBeanServer.invoke.  For MXBeans, the named types will be
+     * the mapped Open Types for the parameters.
+     */
+    abstract String[] getSignature(M m);
+
+    /**
+     * Check that this method is valid.  For example, a method in an
+     * MXBean interface is not valid if one of its parameters cannot be
+     * mapped to an Open Type.
+     */
+    abstract void checkMethod(M m) throws IllegalArgumentException;
+
+    /**
+     * Invoke the method with the given target and arguments.
+     *
+     * @param cookie Additional information about the target.  For an
+     * MXBean, this is the MXBeanLookup associated with the MXBean.
+     */
+    /*
+     * It would be cleaner if the type of the cookie were a
+     * type parameter to this class, but that would involve a lot of
+     * messy type parameter propagation just to avoid a couple of casts.
+     */
+    abstract Object invokeM2(M m, Object target, Object[] args, Object cookie)
+    throws InvocationTargetException, IllegalAccessException,
+            MBeanException;
+
+    /**
+     * Test whether the given value is valid for the given parameter of this
+     * M.
+     */
+    abstract boolean validParameter(M m, Object value, int paramNo,
+            Object cookie);
+
+    /**
+     * Construct an MBeanAttributeInfo for the given attribute based on the
+     * given getter and setter.  One but not both of the getter and setter
+     * may be null.
+     */
+    abstract MBeanAttributeInfo getMBeanAttributeInfo(String attributeName,
+            M getter, M setter);
+    /**
+     * Construct an MBeanOperationInfo for the given operation based on
+     * the M it was derived from.
+     */
+    abstract MBeanOperationInfo getMBeanOperationInfo(String operationName,
+            M operation);
+
+    /**
+     * Get a Descriptor containing fields that MBeans of this kind will
+     * always have.  For example, MXBeans will always have "mxbean=true".
+     */
+    abstract Descriptor getBasicMBeanDescriptor();
+
+    /**
+     * Get a Descriptor containing additional fields beyond the ones
+     * from getBasicMBeanDescriptor that MBeans whose concrete class
+     * is resourceClass will always have.
+     */
+    abstract Descriptor getMBeanDescriptor(Class<?> resourceClass);
+
+
+    final PerInterface<M> getPerInterface(Class<?> mbeanInterface)
+    throws NotCompliantMBeanException {
+        PerInterfaceMap<M> map = getPerInterfaceMap();
+        synchronized (map) {
+            WeakReference<PerInterface<M>> wr = map.get(mbeanInterface);
+            PerInterface<M> pi = (wr == null) ? null : wr.get();
+            if (pi == null) {
+                try {
+                    MBeanAnalyzer<M> analyzer = getAnalyzer(mbeanInterface);
+                    MBeanInfo mbeanInfo =
+                            makeInterfaceMBeanInfo(mbeanInterface, analyzer);
+                    pi = new PerInterface<M>(mbeanInterface, this, analyzer,
+                            mbeanInfo);
+                    wr = new WeakReference<PerInterface<M>>(pi);
+                    map.put(mbeanInterface, wr);
+                } catch (Exception x) {
+                    throw Introspector.throwException(mbeanInterface,x);
+                }
+            }
+            return pi;
+        }
+    }
+
+    /**
+     * Make the MBeanInfo skeleton for the given MBean interface using
+     * the given analyzer.  This will never be the MBeanInfo of any real
+     * MBean (because the getClassName() must be a concrete class), but
+     * its MBeanAttributeInfo[] and MBeanOperationInfo[] can be inserted
+     * into such an MBeanInfo, and its Descriptor can be the basis for
+     * the MBeanInfo's Descriptor.
+     */
+    private MBeanInfo makeInterfaceMBeanInfo(Class<?> mbeanInterface,
+            MBeanAnalyzer<M> analyzer) {
+        final MBeanInfoMaker maker = new MBeanInfoMaker();
+        analyzer.visit(maker);
+        final String description =
+                "Information on the management interface of the MBean";
+        return maker.makeMBeanInfo(mbeanInterface, description);
+    }
+
+    /** True if the given getter and setter are consistent. */
+    final boolean consistent(M getter, M setter) {
+        return (getter == null || setter == null ||
+                getGenericReturnType(getter).equals(getGenericParameterTypes(setter)[0]));
+    }
+
+    /**
+     * Invoke the given M on the given target with the given args and cookie.
+     * Wrap exceptions appropriately.
+     */
+    final Object invokeM(M m, Object target, Object[] args, Object cookie)
+    throws MBeanException, ReflectionException {
+        try {
+            return invokeM2(m, target, args, cookie);
+        } catch (InvocationTargetException e) {
+            unwrapInvocationTargetException(e);
+            throw new RuntimeException(e); // not reached
+        } catch (IllegalAccessException e) {
+            throw new ReflectionException(e, e.toString());
+        }
+        /* We do not catch and wrap RuntimeException or Error,
+         * because we're in a DynamicMBean, so the logic for DynamicMBeans
+         * will do the wrapping.
+         */
+    }
+
+    /**
+     * Invoke the given setter on the given target with the given argument
+     * and cookie.  Wrap exceptions appropriately.
+     */
+    /* If the value is of the wrong type for the method we are about to
+     * invoke, we are supposed to throw an InvalidAttributeValueException.
+     * Rather than making the check always, we invoke the method, then
+     * if it throws an exception we check the type to see if that was
+     * what caused the exception.  The assumption is that an exception
+     * from an invalid type will arise before any user method is ever
+     * called (either in reflection or in OpenConverter).
+     */
+    final void invokeSetter(String name, M setter, Object target, Object arg,
+            Object cookie)
+            throws MBeanException, ReflectionException,
+            InvalidAttributeValueException {
+        try {
+            invokeM2(setter, target, new Object[] {arg}, cookie);
+        } catch (IllegalAccessException e) {
+            throw new ReflectionException(e, e.toString());
+        } catch (RuntimeException e) {
+            maybeInvalidParameter(name, setter, arg, cookie);
+            throw e;
+        } catch (InvocationTargetException e) {
+            maybeInvalidParameter(name, setter, arg, cookie);
+            unwrapInvocationTargetException(e);
+        }
+    }
+
+    private void maybeInvalidParameter(String name, M setter, Object arg,
+            Object cookie)
+            throws InvalidAttributeValueException {
+        if (!validParameter(setter, arg, 0, cookie)) {
+            final String msg =
+                    "Invalid value for attribute " + name + ": " + arg;
+            throw new InvalidAttributeValueException(msg);
+        }
+    }
+
+    static boolean isValidParameter(Method m, Object value, int paramNo) {
+        Class<?> c = m.getParameterTypes()[paramNo];
+        try {
+            // Following is expensive but we only call this method to determine
+            // if an exception is due to an incompatible parameter type.
+            // Plain old c.isInstance doesn't work for primitive types.
+            Object a = Array.newInstance(c, 1);
+            Array.set(a, 0, value);
+            return true;
+        } catch (IllegalArgumentException e) {
+            return false;
+        }
+    }
+
+    private static void
+            unwrapInvocationTargetException(InvocationTargetException e)
+            throws MBeanException {
+        Throwable t = e.getCause();
+        if (t instanceof RuntimeException)
+            throw (RuntimeException) t;
+        else if (t instanceof Error)
+            throw (Error) t;
+        else
+            throw new MBeanException((Exception) t,
+                    (t == null ? null : t.toString()));
+    }
+
+    /** A visitor that constructs the per-interface MBeanInfo. */
+    private class MBeanInfoMaker implements MBeanAnalyzer.MBeanVisitor<M> {
+
+        public void visitAttribute(String attributeName,
+                M getter,
+                M setter) {
+            MBeanAttributeInfo mbai =
+                    getMBeanAttributeInfo(attributeName, getter, setter);
+
+            attrs.add(mbai);
+        }
+
+        public void visitOperation(String operationName,
+                M operation) {
+            MBeanOperationInfo mboi =
+                    getMBeanOperationInfo(operationName, operation);
+
+            ops.add(mboi);
+        }
+
+        /** Make an MBeanInfo based on the attributes and operations
+         *  found in the interface. */
+        MBeanInfo makeMBeanInfo(Class<?> mbeanInterface,
+                String description) {
+            final MBeanAttributeInfo[] attrArray =
+                    attrs.toArray(new MBeanAttributeInfo[0]);
+            final MBeanOperationInfo[] opArray =
+                    ops.toArray(new MBeanOperationInfo[0]);
+            final String interfaceClassName =
+                    "interfaceClassName=" + mbeanInterface.getName();
+            final Descriptor interfDescriptor =
+                    new ImmutableDescriptor(interfaceClassName);
+            final Descriptor mbeanDescriptor = getBasicMBeanDescriptor();
+            final Descriptor annotatedDescriptor =
+                    Introspector.descriptorForElement(mbeanInterface);
+            final Descriptor descriptor =
+                    DescriptorCache.getInstance().union(interfDescriptor,
+                    mbeanDescriptor,
+                    annotatedDescriptor);
+
+            return new MBeanInfo(mbeanInterface.getName(),
+                    description,
+                    attrArray,
+                    null,
+                    opArray,
+                    null,
+                    descriptor);
+        }
+
+        private final List<MBeanAttributeInfo> attrs = newList();
+        private final List<MBeanOperationInfo> ops = newList();
+    }
+
+    /*
+     * Looking up the MBeanInfo for a given base class (implementation class)
+     * is complicated by the fact that we may use the same base class with
+     * several different explicit MBean interfaces via the
+     * javax.management.StandardMBean class.  It is further complicated
+     * by the fact that we have to be careful not to retain a strong reference
+     * to any Class object for fear we would prevent a ClassLoader from being
+     * garbage-collected.  So we have a first lookup from the base class
+     * to a map for each interface that base class might specify giving
+     * the MBeanInfo constructed for that base class and interface.
+     */
+    static class MBeanInfoMap
+            extends WeakHashMap<Class<?>, WeakHashMap<Class<?>, MBeanInfo>> {
+    }
+
+    /**
+     * Return the MBeanInfo for the given resource, based on the given
+     * per-interface data.
+     */
+    final MBeanInfo getMBeanInfo(Object resource, PerInterface<M> perInterface) {
+        MBeanInfo mbi =
+                getClassMBeanInfo(resource.getClass(), perInterface);
+        MBeanNotificationInfo[] notifs = findNotifications(resource);
+        if (notifs == null || notifs.length == 0)
+            return mbi;
+        else {
+            return new MBeanInfo(mbi.getClassName(),
+                    mbi.getDescription(),
+                    mbi.getAttributes(),
+                    mbi.getConstructors(),
+                    mbi.getOperations(),
+                    notifs,
+                    mbi.getDescriptor());
+        }
+    }
+
+    /**
+     * Return the basic MBeanInfo for resources of the given class and
+     * per-interface data.  This MBeanInfo might not be the final MBeanInfo
+     * for instances of the class, because if the class is a
+     * NotificationBroadcaster then each instance gets to decide what
+     * MBeanNotificationInfo[] to put in its own MBeanInfo.
+     */
+    final MBeanInfo getClassMBeanInfo(Class<?> resourceClass,
+            PerInterface<M> perInterface) {
+        MBeanInfoMap map = getMBeanInfoMap();
+        synchronized (map) {
+            WeakHashMap<Class<?>, MBeanInfo> intfMap = map.get(resourceClass);
+            if (intfMap == null) {
+                intfMap = new WeakHashMap<Class<?>, MBeanInfo>();
+                map.put(resourceClass, intfMap);
+            }
+            Class<?> intfClass = perInterface.getMBeanInterface();
+            MBeanInfo mbi = intfMap.get(intfClass);
+            if (mbi == null) {
+                MBeanInfo imbi = perInterface.getMBeanInfo();
+                Descriptor descriptor =
+                        ImmutableDescriptor.union(imbi.getDescriptor(),
+                        getMBeanDescriptor(resourceClass));
+                mbi = new MBeanInfo(resourceClass.getName(),
+                        imbi.getDescription(),
+                        imbi.getAttributes(),
+                        findConstructors(resourceClass),
+                        imbi.getOperations(),
+                        (MBeanNotificationInfo[]) null,
+                        descriptor);
+                intfMap.put(intfClass, mbi);
+            }
+            return mbi;
+        }
+    }
+
+    static MBeanNotificationInfo[] findNotifications(Object moi) {
+        if (!(moi instanceof NotificationBroadcaster))
+            return null;
+        MBeanNotificationInfo[] mbn =
+                ((NotificationBroadcaster) moi).getNotificationInfo();
+        if (mbn == null)
+            return null;
+        MBeanNotificationInfo[] result =
+                new MBeanNotificationInfo[mbn.length];
+        for (int i = 0; i < mbn.length; i++) {
+            MBeanNotificationInfo ni = mbn[i];
+            if (ni.getClass() != MBeanNotificationInfo.class)
+                ni = (MBeanNotificationInfo) ni.clone();
+            result[i] = ni;
+        }
+        return result;
+    }
+
+    private static MBeanConstructorInfo[] findConstructors(Class<?> c) {
+        Constructor[] cons = c.getConstructors();
+        MBeanConstructorInfo[] mbc = new MBeanConstructorInfo[cons.length];
+        for (int i = 0; i < cons.length; i++) {
+            final String descr = "Public constructor of the MBean";
+            mbc[i] = new MBeanConstructorInfo(descr, cons[i]);
+        }
+        return mbc;
+    }
+
+}