Introspector.java revision 0
0N/A/*
0N/A * Copyright 1999-2007 Sun Microsystems, Inc. All Rights Reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
0N/A * published by the Free Software Foundation. Sun designates this
0N/A * particular file as subject to the "Classpath" exception as provided
0N/A * by Sun in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
0N/A * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0N/A * CA 95054 USA or visit www.sun.com if you need additional information or
0N/A * have any questions.
0N/A */
0N/A
0N/Apackage com.sun.jmx.mbeanserver;
0N/A
0N/Aimport java.lang.annotation.Annotation;
0N/Aimport java.lang.reflect.AnnotatedElement;
0N/Aimport java.lang.reflect.Constructor;
0N/Aimport java.lang.reflect.Method;
0N/Aimport java.lang.reflect.Modifier;
0N/Aimport java.lang.reflect.UndeclaredThrowableException;
0N/Aimport java.util.Arrays;
0N/Aimport java.util.HashMap;
0N/Aimport java.util.Map;
0N/A
0N/Aimport javax.management.Descriptor;
0N/Aimport javax.management.DescriptorKey;
0N/Aimport javax.management.DynamicMBean;
0N/Aimport javax.management.ImmutableDescriptor;
0N/Aimport javax.management.MBeanInfo;
0N/Aimport javax.management.NotCompliantMBeanException;
0N/A
0N/Aimport com.sun.jmx.mbeanserver.Util;
0N/A
0N/A/**
0N/A * This class contains the methods for performing all the tests needed to verify
0N/A * that a class represents a JMX compliant MBean.
0N/A *
0N/A * @since 1.5
0N/A */
0N/Apublic class Introspector {
0N/A
0N/A
0N/A /*
0N/A * ------------------------------------------
0N/A * PRIVATE CONSTRUCTORS
0N/A * ------------------------------------------
0N/A */
0N/A
0N/A // private constructor defined to "hide" the default public constructor
0N/A private Introspector() {
0N/A
0N/A // ------------------------------
0N/A // ------------------------------
0N/A
0N/A }
0N/A
0N/A /*
0N/A * ------------------------------------------
0N/A * PUBLIC METHODS
0N/A * ------------------------------------------
0N/A */
0N/A
0N/A /**
0N/A * Tell whether a MBean of the given class is a Dynamic MBean.
0N/A * This method does nothing more than returning
0N/A * <pre>
0N/A * javax.management.DynamicMBean.class.isAssignableFrom(c)
0N/A * </pre>
0N/A * This method does not check for any JMX MBean compliance:
0N/A * <ul><li>If <code>true</code> is returned, then instances of
0N/A * <code>c</code> are DynamicMBean.</li>
0N/A * <li>If <code>false</code> is returned, then no further
0N/A * assumption can be made on instances of <code>c</code>.
0N/A * In particular, instances of <code>c</code> may, or may not
0N/A * be JMX standard MBeans.</li>
0N/A * </ul>
0N/A * @param c The class of the MBean under examination.
0N/A * @return <code>true</code> if instances of <code>c</code> are
0N/A * Dynamic MBeans, <code>false</code> otherwise.
0N/A *
0N/A **/
0N/A public static final boolean isDynamic(final Class c) {
0N/A // Check if the MBean implements the DynamicMBean interface
0N/A return javax.management.DynamicMBean.class.isAssignableFrom(c);
0N/A }
0N/A
0N/A /**
0N/A * Basic method for testing that a MBean of a given class can be
0N/A * instantiated by the MBean server.<p>
0N/A * This method checks that:
0N/A * <ul><li>The given class is a concrete class.</li>
0N/A * <li>The given class exposes at least one public constructor.</li>
0N/A * </ul>
0N/A * If these conditions are not met, throws a NotCompliantMBeanException.
0N/A * @param c The class of the MBean we want to create.
0N/A * @exception NotCompliantMBeanException if the MBean class makes it
0N/A * impossible to instantiate the MBean from within the
0N/A * MBeanServer.
0N/A *
0N/A **/
0N/A public static void testCreation(Class c)
0N/A throws NotCompliantMBeanException {
0N/A // Check if the class is a concrete class
0N/A final int mods = c.getModifiers();
0N/A if (Modifier.isAbstract(mods) || Modifier.isInterface(mods)) {
0N/A throw new NotCompliantMBeanException("MBean class must be concrete");
0N/A }
0N/A
0N/A // Check if the MBean has a public constructor
0N/A final Constructor[] consList = c.getConstructors();
0N/A if (consList.length == 0) {
0N/A throw new NotCompliantMBeanException("MBean class must have public constructor");
0N/A }
0N/A }
0N/A
0N/A public static void checkCompliance(Class mbeanClass)
0N/A throws NotCompliantMBeanException {
0N/A // Is DynamicMBean?
0N/A //
0N/A if (DynamicMBean.class.isAssignableFrom(mbeanClass))
0N/A return;
0N/A // Is Standard MBean?
0N/A //
0N/A final Exception mbeanException;
0N/A try {
0N/A getStandardMBeanInterface(mbeanClass);
0N/A return;
0N/A } catch (NotCompliantMBeanException e) {
0N/A mbeanException = e;
0N/A }
0N/A // Is MXBean?
0N/A //
0N/A final Exception mxbeanException;
0N/A try {
0N/A getMXBeanInterface(mbeanClass);
0N/A return;
0N/A } catch (NotCompliantMBeanException e) {
0N/A mxbeanException = e;
0N/A }
0N/A final String msg =
0N/A "MBean class " + mbeanClass.getName() + " does not implement " +
0N/A "DynamicMBean, neither follows the Standard MBean conventions (" +
0N/A mbeanException.toString() + ") nor the MXBean conventions (" +
0N/A mxbeanException.toString() + ")";
0N/A throw new NotCompliantMBeanException(msg);
0N/A }
0N/A
0N/A public static DynamicMBean makeDynamicMBean(Object mbean)
0N/A throws NotCompliantMBeanException {
0N/A if (mbean instanceof DynamicMBean)
0N/A return (DynamicMBean) mbean;
0N/A final Class mbeanClass = mbean.getClass();
0N/A Class<?> c = null;
0N/A try {
0N/A c = getStandardMBeanInterface(mbeanClass);
0N/A } catch (NotCompliantMBeanException e) {
0N/A // Ignore exception - we need to check whether
0N/A // mbean is an MXBean first.
0N/A }
0N/A if (c != null)
0N/A return new StandardMBeanSupport(mbean, Util.<Class<Object>>cast(c));
0N/A
0N/A try {
0N/A c = getMXBeanInterface(mbeanClass);
0N/A } catch (NotCompliantMBeanException e) {
0N/A // Ignore exception - we cannot decide whether mbean was supposed
0N/A // to be an MBean or an MXBean. We will call checkCompliance()
0N/A // to generate the appropriate exception.
0N/A }
0N/A if (c != null)
0N/A return new MXBeanSupport(mbean, Util.<Class<Object>>cast(c));
0N/A checkCompliance(mbeanClass);
0N/A throw new NotCompliantMBeanException("Not compliant"); // not reached
0N/A }
0N/A
0N/A /**
0N/A * Basic method for testing if a given class is a JMX compliant MBean.
0N/A *
0N/A * @param baseClass The class to be tested
0N/A *
0N/A * @return <code>null</code> if the MBean is a DynamicMBean,
0N/A * the computed {@link javax.management.MBeanInfo} otherwise.
0N/A * @exception NotCompliantMBeanException The specified class is not a
0N/A * JMX compliant MBean
0N/A */
0N/A public static MBeanInfo testCompliance(Class baseClass)
0N/A throws NotCompliantMBeanException {
0N/A
0N/A // ------------------------------
0N/A // ------------------------------
0N/A
0N/A // Check if the MBean implements the MBean or the Dynamic
0N/A // MBean interface
0N/A if (isDynamic(baseClass))
0N/A return null;
0N/A
0N/A return testCompliance(baseClass, null);
0N/A }
0N/A
0N/A public static void testComplianceMXBeanInterface(Class interfaceClass)
0N/A throws NotCompliantMBeanException {
0N/A MXBeanIntrospector.getInstance().getAnalyzer(interfaceClass);
0N/A }
0N/A
0N/A /**
0N/A * Basic method for testing if a given class is a JMX compliant
0N/A * Standard MBean. This method is only called by the legacy code
0N/A * in com.sun.management.jmx.
0N/A *
0N/A * @param baseClass The class to be tested.
0N/A *
0N/A * @param mbeanInterface the MBean interface that the class implements,
0N/A * or null if the interface must be determined by introspection.
0N/A *
0N/A * @return the computed {@link javax.management.MBeanInfo}.
0N/A * @exception NotCompliantMBeanException The specified class is not a
0N/A * JMX compliant Standard MBean
0N/A */
0N/A public static synchronized MBeanInfo
0N/A testCompliance(final Class<?> baseClass,
0N/A Class<?> mbeanInterface)
0N/A throws NotCompliantMBeanException {
0N/A if (mbeanInterface == null)
0N/A mbeanInterface = getStandardMBeanInterface(baseClass);
0N/A MBeanIntrospector<?> introspector = StandardMBeanIntrospector.getInstance();
0N/A return getClassMBeanInfo(introspector, baseClass, mbeanInterface);
0N/A }
0N/A
0N/A private static <M> MBeanInfo
0N/A getClassMBeanInfo(MBeanIntrospector<M> introspector,
0N/A Class<?> baseClass, Class<?> mbeanInterface)
0N/A throws NotCompliantMBeanException {
0N/A PerInterface<M> perInterface = introspector.getPerInterface(mbeanInterface);
0N/A return introspector.getClassMBeanInfo(baseClass, perInterface);
0N/A }
0N/A
0N/A /**
0N/A * Get the MBean interface implemented by a JMX Standard
0N/A * MBean class. This method is only called by the legacy
0N/A * code in "com.sun.management.jmx".
0N/A *
0N/A * @param baseClass The class to be tested.
0N/A *
0N/A * @return The MBean interface implemented by the MBean.
0N/A * Return <code>null</code> if the MBean is a DynamicMBean,
0N/A * or if no MBean interface is found.
0N/A */
0N/A public static Class getMBeanInterface(Class baseClass) {
0N/A // Check if the given class implements the MBean interface
0N/A // or the Dynamic MBean interface
0N/A if (isDynamic(baseClass)) return null;
0N/A try {
0N/A return getStandardMBeanInterface(baseClass);
0N/A } catch (NotCompliantMBeanException e) {
0N/A return null;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Get the MBean interface implemented by a JMX Standard MBean class.
0N/A *
0N/A * @param baseClass The class to be tested.
0N/A *
0N/A * @return The MBean interface implemented by the Standard MBean.
0N/A *
0N/A * @throws NotCompliantMBeanException The specified class is
0N/A * not a JMX compliant Standard MBean.
0N/A */
0N/A public static Class getStandardMBeanInterface(Class baseClass)
0N/A throws NotCompliantMBeanException {
0N/A Class current = baseClass;
0N/A Class mbeanInterface = null;
0N/A while (current != null) {
0N/A mbeanInterface =
0N/A findMBeanInterface(current, current.getName());
0N/A if (mbeanInterface != null) break;
0N/A current = current.getSuperclass();
0N/A }
0N/A if (mbeanInterface != null) {
0N/A return mbeanInterface;
0N/A } else {
0N/A final String msg =
0N/A "Class " + baseClass.getName() +
0N/A " is not a JMX compliant Standard MBean";
0N/A throw new NotCompliantMBeanException(msg);
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Get the MXBean interface implemented by a JMX MXBean class.
0N/A *
0N/A * @param baseClass The class to be tested.
0N/A *
0N/A * @return The MXBean interface implemented by the MXBean.
0N/A *
0N/A * @throws NotCompliantMBeanException The specified class is
0N/A * not a JMX compliant MXBean.
0N/A */
0N/A public static Class getMXBeanInterface(Class baseClass)
0N/A throws NotCompliantMBeanException {
0N/A try {
0N/A return MXBeanSupport.findMXBeanInterface(baseClass);
0N/A } catch (Exception e) {
0N/A throw throwException(baseClass,e);
0N/A }
0N/A }
0N/A
0N/A /*
0N/A * ------------------------------------------
0N/A * PRIVATE METHODS
0N/A * ------------------------------------------
0N/A */
0N/A
0N/A
0N/A /**
0N/A * Try to find the MBean interface corresponding to the class aName
0N/A * - i.e. <i>aName</i>MBean, from within aClass and its superclasses.
0N/A **/
0N/A private static Class findMBeanInterface(Class aClass, String aName) {
0N/A Class current = aClass;
0N/A while (current != null) {
0N/A final Class[] interfaces = current.getInterfaces();
0N/A final int len = interfaces.length;
0N/A for (int i=0;i<len;i++) {
0N/A final Class inter =
0N/A implementsMBean(interfaces[i], aName);
0N/A if (inter != null) return inter;
0N/A }
0N/A current = current.getSuperclass();
0N/A }
0N/A return null;
0N/A }
0N/A
0N/A public static Descriptor descriptorForElement(final AnnotatedElement elmt) {
0N/A if (elmt == null)
0N/A return ImmutableDescriptor.EMPTY_DESCRIPTOR;
0N/A final Annotation[] annots = elmt.getAnnotations();
0N/A return descriptorForAnnotations(annots);
0N/A }
0N/A
0N/A public static Descriptor descriptorForAnnotations(Annotation[] annots) {
0N/A if (annots.length == 0)
0N/A return ImmutableDescriptor.EMPTY_DESCRIPTOR;
0N/A Map<String, Object> descriptorMap = new HashMap<String, Object>();
0N/A for (Annotation a : annots) {
0N/A Class<? extends Annotation> c = a.annotationType();
0N/A Method[] elements = c.getMethods();
0N/A for (Method element : elements) {
0N/A DescriptorKey key = element.getAnnotation(DescriptorKey.class);
0N/A if (key != null) {
0N/A String name = key.value();
0N/A Object value;
0N/A try {
0N/A value = element.invoke(a);
0N/A } catch (RuntimeException e) {
0N/A // we don't expect this - except for possibly
0N/A // security exceptions?
0N/A // RuntimeExceptions shouldn't be "UndeclaredThrowable".
0N/A // anyway...
0N/A //
0N/A throw e;
0N/A } catch (Exception e) {
0N/A // we don't expect this
0N/A throw new UndeclaredThrowableException(e);
0N/A }
0N/A value = annotationToField(value);
0N/A Object oldValue = descriptorMap.put(name, value);
0N/A if (oldValue != null && !equals(oldValue, value)) {
0N/A final String msg =
0N/A "Inconsistent values for descriptor field " + name +
0N/A " from annotations: " + value + " :: " + oldValue;
0N/A throw new IllegalArgumentException(msg);
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A if (descriptorMap.isEmpty())
0N/A return ImmutableDescriptor.EMPTY_DESCRIPTOR;
0N/A else
0N/A return new ImmutableDescriptor(descriptorMap);
0N/A }
0N/A
0N/A /**
0N/A * Throws a NotCompliantMBeanException or a SecurityException.
0N/A * @param notCompliant the class which was under examination
0N/A * @param cause the raeson why NotCompliantMBeanException should
0N/A * be thrown.
0N/A * @return nothing - this method always throw an exception.
0N/A * The return type makes it possible to write
0N/A * <pre> throw throwException(clazz,cause); </pre>
0N/A * @throws SecurityException - if cause is a SecurityException
0N/A * @throws NotCompliantMBeanException otherwise.
0N/A **/
0N/A static NotCompliantMBeanException throwException(Class<?> notCompliant,
0N/A Throwable cause)
0N/A throws NotCompliantMBeanException, SecurityException {
0N/A if (cause instanceof SecurityException)
0N/A throw (SecurityException) cause;
0N/A if (cause instanceof NotCompliantMBeanException)
0N/A throw (NotCompliantMBeanException)cause;
0N/A final String classname =
0N/A (notCompliant==null)?"null class":notCompliant.getName();
0N/A final String reason =
0N/A (cause==null)?"Not compliant":cause.getMessage();
0N/A final NotCompliantMBeanException res =
0N/A new NotCompliantMBeanException(classname+": "+reason);
0N/A res.initCause(cause);
0N/A throw res;
0N/A }
0N/A
0N/A // Convert a value from an annotation element to a descriptor field value
0N/A // E.g. with @interface Foo {class value()} an annotation @Foo(String.class)
0N/A // will produce a Descriptor field value "java.lang.String"
0N/A private static Object annotationToField(Object x) {
0N/A // An annotation element cannot have a null value but never mind
0N/A if (x == null)
0N/A return null;
0N/A if (x instanceof Number || x instanceof String ||
0N/A x instanceof Character || x instanceof Boolean ||
0N/A x instanceof String[])
0N/A return x;
0N/A // Remaining possibilities: array of primitive (e.g. int[]),
0N/A // enum, class, array of enum or class.
0N/A Class<?> c = x.getClass();
0N/A if (c.isArray()) {
0N/A if (c.getComponentType().isPrimitive())
0N/A return x;
0N/A Object[] xx = (Object[]) x;
0N/A String[] ss = new String[xx.length];
0N/A for (int i = 0; i < xx.length; i++)
0N/A ss[i] = (String) annotationToField(xx[i]);
0N/A return ss;
0N/A }
0N/A if (x instanceof Class)
0N/A return ((Class<?>) x).getName();
0N/A if (x instanceof Enum)
0N/A return ((Enum) x).name();
0N/A // The only other possibility is that the value is another
0N/A // annotation, or that the language has evolved since this code
0N/A // was written. We don't allow for either of those currently.
0N/A throw new IllegalArgumentException("Illegal type for annotation " +
0N/A "element: " + x.getClass().getName());
0N/A }
0N/A
0N/A // This must be consistent with the check for duplicate field values in
0N/A // ImmutableDescriptor.union. But we don't expect to be called very
0N/A // often so this inefficient check should be enough.
0N/A private static boolean equals(Object x, Object y) {
0N/A return Arrays.deepEquals(new Object[] {x}, new Object[] {y});
0N/A }
0N/A
0N/A /**
0N/A * Returns the XXMBean interface or null if no such interface exists
0N/A *
0N/A * @param c The interface to be tested
0N/A * @param clName The name of the class implementing this interface
0N/A */
0N/A private static Class implementsMBean(Class c, String clName) {
0N/A String clMBeanName = clName + "MBean";
0N/A if (c.getName().equals(clMBeanName)) {
0N/A return c;
0N/A }
0N/A Class[] interfaces = c.getInterfaces();
0N/A for (int i = 0;i < interfaces.length; i++) {
0N/A if (interfaces[i].getName().equals(clMBeanName))
0N/A return interfaces[i];
0N/A }
0N/A
0N/A return null;
0N/A }
0N/A}