--- /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;
+ }
+
+}