/*
 * Copyright (c) 1999, 2015, Oracle and/or its affiliates. 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.jmx.mbeanserver;

import java.lang.annotation.Annotation;
import java.lang.ref.SoftReference;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.WeakHashMap;

import javax.management.Descriptor;
import javax.management.DescriptorKey;
import javax.management.DynamicMBean;
import javax.management.ImmutableDescriptor;
import javax.management.MBeanInfo;
import javax.management.NotCompliantMBeanException;

import com.sun.jmx.remote.util.EnvHelp;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import javax.management.AttributeNotFoundException;
import javax.management.openmbean.CompositeData;

import sun.reflect.misc.MethodUtil;
import sun.reflect.misc.ReflectUtil;

This class contains the methods for performing all the tests needed to verify that a class represents a JMX compliant MBean.
Since:1.5
/** * This class contains the methods for performing all the tests needed to verify * that a class represents a JMX compliant MBean. * * @since 1.5 */
public class Introspector { final public static boolean ALLOW_NONPUBLIC_MBEAN; static { String val = AccessController.doPrivileged(new GetPropertyAction("jdk.jmx.mbeans.allowNonPublic")); ALLOW_NONPUBLIC_MBEAN = Boolean.parseBoolean(val); } /* * ------------------------------------------ * PRIVATE CONSTRUCTORS * ------------------------------------------ */ // private constructor defined to "hide" the default public constructor private Introspector() { // ------------------------------ // ------------------------------ } /* * ------------------------------------------ * PUBLIC METHODS * ------------------------------------------ */
Tell whether a MBean of the given class is a Dynamic MBean. This method does nothing more than returning
javax.management.DynamicMBean.class.isAssignableFrom(c)
This method does not check for any JMX MBean compliance:
  • If true is returned, then instances of c are DynamicMBean.
  • If false is returned, then no further assumption can be made on instances of c. In particular, instances of c may, or may not be JMX standard MBeans.
Params:
  • c – The class of the MBean under examination.
Returns:true if instances of c are Dynamic MBeans, false otherwise.
/** * Tell whether a MBean of the given class is a Dynamic MBean. * This method does nothing more than returning * <pre> * javax.management.DynamicMBean.class.isAssignableFrom(c) * </pre> * This method does not check for any JMX MBean compliance: * <ul><li>If <code>true</code> is returned, then instances of * <code>c</code> are DynamicMBean.</li> * <li>If <code>false</code> is returned, then no further * assumption can be made on instances of <code>c</code>. * In particular, instances of <code>c</code> may, or may not * be JMX standard MBeans.</li> * </ul> * @param c The class of the MBean under examination. * @return <code>true</code> if instances of <code>c</code> are * Dynamic MBeans, <code>false</code> otherwise. * **/
public static final boolean isDynamic(final Class<?> c) { // Check if the MBean implements the DynamicMBean interface return javax.management.DynamicMBean.class.isAssignableFrom(c); }
Basic method for testing that a MBean of a given class can be instantiated by the MBean server.

This method checks that:

  • The given class is a concrete class.
  • The given class exposes at least one public constructor.
If these conditions are not met, throws a NotCompliantMBeanException.
Params:
  • c – The class of the MBean we want to create.
Throws:
/** * Basic method for testing that a MBean of a given class can be * instantiated by the MBean server.<p> * This method checks that: * <ul><li>The given class is a concrete class.</li> * <li>The given class exposes at least one public constructor.</li> * </ul> * If these conditions are not met, throws a NotCompliantMBeanException. * @param c The class of the MBean we want to create. * @exception NotCompliantMBeanException if the MBean class makes it * impossible to instantiate the MBean from within the * MBeanServer. * **/
public static void testCreation(Class<?> c) throws NotCompliantMBeanException { // Check if the class is a concrete class final int mods = c.getModifiers(); if (Modifier.isAbstract(mods) || Modifier.isInterface(mods)) { throw new NotCompliantMBeanException("MBean class must be concrete"); } // Check if the MBean has a public constructor final Constructor<?>[] consList = c.getConstructors(); if (consList.length == 0) { throw new NotCompliantMBeanException("MBean class must have public constructor"); } } public static void checkCompliance(Class<?> mbeanClass) throws NotCompliantMBeanException { // Is DynamicMBean? // if (DynamicMBean.class.isAssignableFrom(mbeanClass)) return; // Is Standard MBean? // final Exception mbeanException; try { getStandardMBeanInterface(mbeanClass); return; } catch (NotCompliantMBeanException e) { mbeanException = e; } // Is MXBean? // final Exception mxbeanException; try { getMXBeanInterface(mbeanClass); return; } catch (NotCompliantMBeanException e) { mxbeanException = e; } final String msg = "MBean class " + mbeanClass.getName() + " does not implement " + "DynamicMBean, and neither follows the Standard MBean conventions (" + mbeanException.toString() + ") nor the MXBean conventions (" + mxbeanException.toString() + ")"; throw new NotCompliantMBeanException(msg); } public static <T> DynamicMBean makeDynamicMBean(T mbean) throws NotCompliantMBeanException { if (mbean instanceof DynamicMBean) return (DynamicMBean) mbean; final Class<?> mbeanClass = mbean.getClass(); Class<? super T> c = null; try { c = Util.cast(getStandardMBeanInterface(mbeanClass)); } catch (NotCompliantMBeanException e) { // Ignore exception - we need to check whether // mbean is an MXBean first. } if (c != null) return new StandardMBeanSupport(mbean, c); try { c = Util.cast(getMXBeanInterface(mbeanClass)); } catch (NotCompliantMBeanException e) { // Ignore exception - we cannot decide whether mbean was supposed // to be an MBean or an MXBean. We will call checkCompliance() // to generate the appropriate exception. } if (c != null) return new MXBeanSupport(mbean, c); checkCompliance(mbeanClass); throw new NotCompliantMBeanException("Not compliant"); // not reached }
Basic method for testing if a given class is a JMX compliant MBean.
Params:
  • baseClass – The class to be tested
Throws:
Returns:null if the MBean is a DynamicMBean, the computed MBeanInfo otherwise.
/** * Basic method for testing if a given class is a JMX compliant MBean. * * @param baseClass The class to be tested * * @return <code>null</code> if the MBean is a DynamicMBean, * the computed {@link javax.management.MBeanInfo} otherwise. * @exception NotCompliantMBeanException The specified class is not a * JMX compliant MBean */
public static MBeanInfo testCompliance(Class<?> baseClass) throws NotCompliantMBeanException { // ------------------------------ // ------------------------------ // Check if the MBean implements the MBean or the Dynamic // MBean interface if (isDynamic(baseClass)) return null; return testCompliance(baseClass, null); }
Tests the given interface class for being a compliant MXBean interface. A compliant MXBean interface is any publicly accessible interface following the MXBean conventions.
Params:
  • interfaceClass – An interface class to test for the MXBean compliance
Throws:
/** * Tests the given interface class for being a compliant MXBean interface. * A compliant MXBean interface is any publicly accessible interface * following the {@link MXBean} conventions. * @param interfaceClass An interface class to test for the MXBean compliance * @throws NotCompliantMBeanException Thrown when the tested interface * is not public or contradicts the {@link MXBean} conventions. */
public static void testComplianceMXBeanInterface(Class<?> interfaceClass) throws NotCompliantMBeanException { MXBeanIntrospector.getInstance().getAnalyzer(interfaceClass); }
Tests the given interface class for being a compliant MBean interface. A compliant MBean interface is any publicly accessible interface following the MBean conventions.
Params:
  • interfaceClass – An interface class to test for the MBean compliance
Throws:
/** * Tests the given interface class for being a compliant MBean interface. * A compliant MBean interface is any publicly accessible interface * following the {@code MBean} conventions. * @param interfaceClass An interface class to test for the MBean compliance * @throws NotCompliantMBeanException Thrown when the tested interface * is not public or contradicts the {@code MBean} conventions. */
public static void testComplianceMBeanInterface(Class<?> interfaceClass) throws NotCompliantMBeanException{ StandardMBeanIntrospector.getInstance().getAnalyzer(interfaceClass); }
Basic method for testing if a given class is a JMX compliant Standard MBean. This method is only called by the legacy code in com.sun.management.jmx.
Params:
  • baseClass – The class to be tested.
  • mbeanInterface – the MBean interface that the class implements, or null if the interface must be determined by introspection.
Throws:
Returns:the computed MBeanInfo.
/** * Basic method for testing if a given class is a JMX compliant * Standard MBean. This method is only called by the legacy code * in com.sun.management.jmx. * * @param baseClass The class to be tested. * * @param mbeanInterface the MBean interface that the class implements, * or null if the interface must be determined by introspection. * * @return the computed {@link javax.management.MBeanInfo}. * @exception NotCompliantMBeanException The specified class is not a * JMX compliant Standard MBean */
public static synchronized MBeanInfo testCompliance(final Class<?> baseClass, Class<?> mbeanInterface) throws NotCompliantMBeanException { if (mbeanInterface == null) mbeanInterface = getStandardMBeanInterface(baseClass); ReflectUtil.checkPackageAccess(mbeanInterface); MBeanIntrospector<?> introspector = StandardMBeanIntrospector.getInstance(); return getClassMBeanInfo(introspector, baseClass, mbeanInterface); } private static <M> MBeanInfo getClassMBeanInfo(MBeanIntrospector<M> introspector, Class<?> baseClass, Class<?> mbeanInterface) throws NotCompliantMBeanException { PerInterface<M> perInterface = introspector.getPerInterface(mbeanInterface); return introspector.getClassMBeanInfo(baseClass, perInterface); }
Get the MBean interface implemented by a JMX Standard MBean class. This method is only called by the legacy code in "com.sun.management.jmx".
Params:
  • baseClass – The class to be tested.
Returns:The MBean interface implemented by the MBean. Return null if the MBean is a DynamicMBean, or if no MBean interface is found.
/** * Get the MBean interface implemented by a JMX Standard * MBean class. This method is only called by the legacy * code in "com.sun.management.jmx". * * @param baseClass The class to be tested. * * @return The MBean interface implemented by the MBean. * Return <code>null</code> if the MBean is a DynamicMBean, * or if no MBean interface is found. */
public static Class<?> getMBeanInterface(Class<?> baseClass) { // Check if the given class implements the MBean interface // or the Dynamic MBean interface if (isDynamic(baseClass)) return null; try { return getStandardMBeanInterface(baseClass); } catch (NotCompliantMBeanException e) { return null; } }
Get the MBean interface implemented by a JMX Standard MBean class.
Params:
  • baseClass – The class to be tested.
Throws:
Returns:The MBean interface implemented by the Standard MBean.
/** * Get the MBean interface implemented by a JMX Standard MBean class. * * @param baseClass The class to be tested. * * @return The MBean interface implemented by the Standard MBean. * * @throws NotCompliantMBeanException The specified class is * not a JMX compliant Standard MBean. */
public static <T> Class<? super T> getStandardMBeanInterface(Class<T> baseClass) throws NotCompliantMBeanException { Class<? super T> current = baseClass; Class<? super T> mbeanInterface = null; while (current != null) { mbeanInterface = findMBeanInterface(current, current.getName()); if (mbeanInterface != null) break; current = current.getSuperclass(); } if (mbeanInterface != null) { return mbeanInterface; } else { final String msg = "Class " + baseClass.getName() + " is not a JMX compliant Standard MBean"; throw new NotCompliantMBeanException(msg); } }
Get the MXBean interface implemented by a JMX MXBean class.
Params:
  • baseClass – The class to be tested.
Throws:
Returns:The MXBean interface implemented by the MXBean.
/** * Get the MXBean interface implemented by a JMX MXBean class. * * @param baseClass The class to be tested. * * @return The MXBean interface implemented by the MXBean. * * @throws NotCompliantMBeanException The specified class is * not a JMX compliant MXBean. */
public static <T> Class<? super T> getMXBeanInterface(Class<T> baseClass) throws NotCompliantMBeanException { try { return MXBeanSupport.findMXBeanInterface(baseClass); } catch (Exception e) { throw throwException(baseClass,e); } } /* * ------------------------------------------ * PRIVATE METHODS * ------------------------------------------ */
Try to find the MBean interface corresponding to the class aName - i.e. aNameMBean, from within aClass and its superclasses.
/** * Try to find the MBean interface corresponding to the class aName * - i.e. <i>aName</i>MBean, from within aClass and its superclasses. **/
private static <T> Class<? super T> findMBeanInterface( Class<T> aClass, String aName) { Class<? super T> current = aClass; while (current != null) { final Class<?>[] interfaces = current.getInterfaces(); final int len = interfaces.length; for (int i=0;i<len;i++) { Class<? super T> inter = Util.cast(interfaces[i]); inter = implementsMBean(inter, aName); if (inter != null) return inter; } current = current.getSuperclass(); } return null; } public static Descriptor descriptorForElement(final AnnotatedElement elmt) { if (elmt == null) return ImmutableDescriptor.EMPTY_DESCRIPTOR; final Annotation[] annots = elmt.getAnnotations(); return descriptorForAnnotations(annots); } public static Descriptor descriptorForAnnotations(Annotation[] annots) { if (annots.length == 0) return ImmutableDescriptor.EMPTY_DESCRIPTOR; Map<String, Object> descriptorMap = new HashMap<String, Object>(); for (Annotation a : annots) { Class<? extends Annotation> c = a.annotationType(); Method[] elements = c.getMethods(); boolean packageAccess = false; for (Method element : elements) { DescriptorKey key = element.getAnnotation(DescriptorKey.class); if (key != null) { String name = key.value(); Object value; try { // Avoid checking access more than once per annotation if (!packageAccess) { ReflectUtil.checkPackageAccess(c); packageAccess = true; } value = MethodUtil.invoke(element, a, null); } catch (RuntimeException e) { // we don't expect this - except for possibly // security exceptions? // RuntimeExceptions shouldn't be "UndeclaredThrowable". // anyway... // throw e; } catch (Exception e) { // we don't expect this throw new UndeclaredThrowableException(e); } value = annotationToField(value); Object oldValue = descriptorMap.put(name, value); if (oldValue != null && !equals(oldValue, value)) { final String msg = "Inconsistent values for descriptor field " + name + " from annotations: " + value + " :: " + oldValue; throw new IllegalArgumentException(msg); } } } } if (descriptorMap.isEmpty()) return ImmutableDescriptor.EMPTY_DESCRIPTOR; else return new ImmutableDescriptor(descriptorMap); }
Throws a NotCompliantMBeanException or a SecurityException.
Params:
  • notCompliant – the class which was under examination
  • cause – the raeson why NotCompliantMBeanException should be thrown.
Throws:
Returns:nothing - this method always throw an exception. The return type makes it possible to write
 throw throwException(clazz,cause); 
/** * Throws a NotCompliantMBeanException or a SecurityException. * @param notCompliant the class which was under examination * @param cause the raeson why NotCompliantMBeanException should * be thrown. * @return nothing - this method always throw an exception. * The return type makes it possible to write * <pre> throw throwException(clazz,cause); </pre> * @throws SecurityException - if cause is a SecurityException * @throws NotCompliantMBeanException otherwise. **/
static NotCompliantMBeanException throwException(Class<?> notCompliant, Throwable cause) throws NotCompliantMBeanException, SecurityException { if (cause instanceof SecurityException) throw (SecurityException) cause; if (cause instanceof NotCompliantMBeanException) throw (NotCompliantMBeanException)cause; final String classname = (notCompliant==null)?"null class":notCompliant.getName(); final String reason = (cause==null)?"Not compliant":cause.getMessage(); final NotCompliantMBeanException res = new NotCompliantMBeanException(classname+": "+reason); res.initCause(cause); throw res; } // Convert a value from an annotation element to a descriptor field value // E.g. with @interface Foo {class value()} an annotation @Foo(String.class) // will produce a Descriptor field value "java.lang.String" private static Object annotationToField(Object x) { // An annotation element cannot have a null value but never mind if (x == null) return null; if (x instanceof Number || x instanceof String || x instanceof Character || x instanceof Boolean || x instanceof String[]) return x; // Remaining possibilities: array of primitive (e.g. int[]), // enum, class, array of enum or class. Class<?> c = x.getClass(); if (c.isArray()) { if (c.getComponentType().isPrimitive()) return x; Object[] xx = (Object[]) x; String[] ss = new String[xx.length]; for (int i = 0; i < xx.length; i++) ss[i] = (String) annotationToField(xx[i]); return ss; } if (x instanceof Class<?>) return ((Class<?>) x).getName(); if (x instanceof Enum<?>) return ((Enum<?>) x).name(); // The only other possibility is that the value is another // annotation, or that the language has evolved since this code // was written. We don't allow for either of those currently. // If it is indeed another annotation, then x will be a proxy // with an unhelpful name like $Proxy2. So we extract the // proxy's interface to use that in the exception message. if (Proxy.isProxyClass(c)) c = c.getInterfaces()[0]; // array "can't be empty" throw new IllegalArgumentException("Illegal type for annotation " + "element using @DescriptorKey: " + c.getName()); } // This must be consistent with the check for duplicate field values in // ImmutableDescriptor.union. But we don't expect to be called very // often so this inefficient check should be enough. private static boolean equals(Object x, Object y) { return Arrays.deepEquals(new Object[] {x}, new Object[] {y}); }
Returns the XXMBean interface or null if no such interface exists
Params:
  • c – The interface to be tested
  • clName – The name of the class implementing this interface
/** * Returns the XXMBean interface or null if no such interface exists * * @param c The interface to be tested * @param clName The name of the class implementing this interface */
private static <T> Class<? super T> implementsMBean(Class<T> c, String clName) { String clMBeanName = clName + "MBean"; if (c.getName().equals(clMBeanName)) { return c; } Class<?>[] interfaces = c.getInterfaces(); for (int i = 0;i < interfaces.length; i++) { if (interfaces[i].getName().equals(clMBeanName) && (Modifier.isPublic(interfaces[i].getModifiers()) || ALLOW_NONPUBLIC_MBEAN)) { return Util.cast(interfaces[i]); } } return null; } public static Object elementFromComplex(Object complex, String element) throws AttributeNotFoundException { try { if (complex.getClass().isArray() && element.equals("length")) { return Array.getLength(complex); } else if (complex instanceof CompositeData) { return ((CompositeData) complex).get(element); } else { // Java Beans introspection // Class<?> clazz = complex.getClass(); Method readMethod; if (JavaBeansAccessor.isAvailable()) { readMethod = JavaBeansAccessor.getReadMethod(clazz, element); } else { // Java Beans not available so use simple introspection // to locate method readMethod = SimpleIntrospector.getReadMethod(clazz, element); } if (readMethod != null) { ReflectUtil.checkPackageAccess(readMethod.getDeclaringClass()); return MethodUtil.invoke(readMethod, complex, new Class<?>[0]); } throw new AttributeNotFoundException( "Could not find the getter method for the property " + element + " using the Java Beans introspector"); } } catch (InvocationTargetException e) { throw new IllegalArgumentException(e); } catch (AttributeNotFoundException e) { throw e; } catch (Exception e) { throw EnvHelp.initCause( new AttributeNotFoundException(e.getMessage()), e); } }
A simple introspector that uses reflection to analyze a class and identify its "getter" methods. This class is intended for use only when Java Beans is not present (which implies that there isn't explicit information about the bean available).
/** * A simple introspector that uses reflection to analyze a class and * identify its "getter" methods. This class is intended for use only when * Java Beans is not present (which implies that there isn't explicit * information about the bean available). */
private static class SimpleIntrospector { private SimpleIntrospector() { } private static final String GET_METHOD_PREFIX = "get"; private static final String IS_METHOD_PREFIX = "is"; // cache to avoid repeated lookups private static final Map<Class<?>,SoftReference<List<Method>>> cache = Collections.synchronizedMap( new WeakHashMap<Class<?>,SoftReference<List<Method>>> ());
Returns the list of methods cached for the given class, or null if not cached.
/** * Returns the list of methods cached for the given class, or {@code null} * if not cached. */
private static List<Method> getCachedMethods(Class<?> clazz) { // return cached methods if possible SoftReference<List<Method>> ref = cache.get(clazz); if (ref != null) { List<Method> cached = ref.get(); if (cached != null) return cached; } return null; }
Returns true if the given method is a "getter" method (where "getter" method is a public method of the form getXXX or "boolean isXXX")
/** * Returns {@code true} if the given method is a "getter" method (where * "getter" method is a public method of the form getXXX or "boolean * isXXX") */
static boolean isReadMethod(Method method) { // ignore static methods int modifiers = method.getModifiers(); if (Modifier.isStatic(modifiers)) return false; String name = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); int paramCount = paramTypes.length; if (paramCount == 0 && name.length() > 2) { // boolean isXXX() if (name.startsWith(IS_METHOD_PREFIX)) return (method.getReturnType() == boolean.class); // getXXX() if (name.length() > 3 && name.startsWith(GET_METHOD_PREFIX)) return (method.getReturnType() != void.class); } return false; }
Returns the list of "getter" methods for the given class. The list is ordered so that isXXX methods appear before getXXX methods - this is for compatibility with the JavaBeans Introspector.
/** * Returns the list of "getter" methods for the given class. The list * is ordered so that isXXX methods appear before getXXX methods - this * is for compatibility with the JavaBeans Introspector. */
static List<Method> getReadMethods(Class<?> clazz) { // return cached result if available List<Method> cachedResult = getCachedMethods(clazz); if (cachedResult != null) return cachedResult; // get list of public methods, filtering out methods that have // been overridden to return a more specific type. List<Method> methods = StandardMBeanIntrospector.getInstance().getMethods(clazz); methods = MBeanAnalyzer.eliminateCovariantMethods(methods); // filter out the non-getter methods List<Method> result = new LinkedList<Method>(); for (Method m: methods) { if (isReadMethod(m)) { // favor isXXX over getXXX if (m.getName().startsWith(IS_METHOD_PREFIX)) { result.add(0, m); } else { result.add(m); } } } // add result to cache cache.put(clazz, new SoftReference<List<Method>>(result)); return result; }
Returns the "getter" to read the given property from the given class or null if no method is found.
/** * Returns the "getter" to read the given property from the given class or * {@code null} if no method is found. */
static Method getReadMethod(Class<?> clazz, String property) { if (Character.isUpperCase(property.charAt(0))) { // the property name must start with a lower-case letter return null; } // first character after 'get/is' prefix must be in uppercase // (compatibility with JavaBeans) property = property.substring(0, 1).toUpperCase(Locale.ENGLISH) + property.substring(1); String getMethod = GET_METHOD_PREFIX + property; String isMethod = IS_METHOD_PREFIX + property; for (Method m: getReadMethods(clazz)) { String name = m.getName(); if (name.equals(isMethod) || name.equals(getMethod)) { return m; } } return null; } } }