jdk/src/share/classes/com/sun/jmx/mbeanserver/Introspector.java
changeset 834 dc74d4ddc28e
parent 715 f16baef3a20e
parent 833 bfa2bef7517c
child 1322 d038148778cc
--- a/jdk/src/share/classes/com/sun/jmx/mbeanserver/Introspector.java	Sat Jul 05 23:29:16 2008 -0700
+++ b/jdk/src/share/classes/com/sun/jmx/mbeanserver/Introspector.java	Wed Jul 09 09:56:00 2008 -0700
@@ -25,23 +25,39 @@
 
 package com.sun.jmx.mbeanserver;
 
+import com.sun.jmx.remote.util.EnvHelp;
+import java.beans.BeanInfo;
+import java.beans.PropertyDescriptor;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 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.HashMap;
 import java.util.Map;
+import java.util.logging.Level;
+import javax.management.AttributeNotFoundException;
+import javax.management.Description;
 
 import javax.management.Descriptor;
+import javax.management.DescriptorFields;
 import javax.management.DescriptorKey;
 import javax.management.DynamicMBean;
 import javax.management.ImmutableDescriptor;
+import javax.management.MBean;
 import javax.management.MBeanInfo;
+import javax.management.MXBean;
 import javax.management.NotCompliantMBeanException;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.MXBeanMappingFactory;
 
+import static com.sun.jmx.defaults.JmxProperties.MBEANSERVER_LOGGER;
+import com.sun.jmx.mbeanserver.Util;
 import com.sun.jmx.remote.util.EnvHelp;
 import java.beans.BeanInfo;
 import java.beans.PropertyDescriptor;
@@ -133,8 +149,12 @@
         }
     }
 
-    public static void checkCompliance(Class mbeanClass)
-        throws NotCompliantMBeanException {
+    public static void checkCompliance(Class<?> mbeanClass)
+    throws NotCompliantMBeanException {
+
+        // Check that @Resource is used correctly (if it used).
+        MBeanInjector.validate(mbeanClass);
+
         // Is DynamicMBean?
         //
         if (DynamicMBean.class.isAssignableFrom(mbeanClass))
@@ -157,21 +177,39 @@
         } catch (NotCompliantMBeanException e) {
             mxbeanException = e;
         }
+        // Is @MBean or @MXBean class?
+        // In fact we find @MBean or @MXBean as a hacky variant of
+        // getStandardMBeanInterface or getMXBeanInterface.  If we get here
+        // then nothing worked.
         final String msg =
             "MBean class " + mbeanClass.getName() + " does not implement " +
-            "DynamicMBean, neither follows the Standard MBean conventions (" +
-            mbeanException.toString() + ") nor the MXBean conventions (" +
-            mxbeanException.toString() + ")";
+            "DynamicMBean; does not follow the Standard MBean conventions (" +
+            mbeanException.toString() + "); does not follow the MXBean conventions (" +
+            mxbeanException.toString() + "); and does not have or inherit the @" +
+            MBean.class.getSimpleName() + " or @" + MXBean.class.getSimpleName() +
+            " annotation";
         throw new NotCompliantMBeanException(msg);
     }
 
+    /**
+     * <p>Make a DynamicMBean out of the existing MBean object.  The object
+     * may already be a DynamicMBean, or it may be a Standard MBean or
+     * MXBean, possibly defined using {@code @MBean} or {@code @MXBean}.</p>
+     * @param mbean the object to convert to a DynamicMBean.
+     * @param <T> a type parameter defined for implementation convenience
+     * (which would have to be removed if this method were part of the public
+     * API).
+     * @return the converted DynamicMBean.
+     * @throws NotCompliantMBeanException if {@code mbean} is not a compliant
+     * MBean object, including the case where it is null.
+     */
     public static <T> DynamicMBean makeDynamicMBean(T mbean)
     throws NotCompliantMBeanException {
         if (mbean == null)
             throw new NotCompliantMBeanException("Null MBean object");
         if (mbean instanceof DynamicMBean)
             return (DynamicMBean) mbean;
-        final Class mbeanClass = mbean.getClass();
+        final Class<?> mbeanClass = mbean.getClass();
         Class<? super T> c = null;
         try {
             c = Util.cast(getStandardMBeanInterface(mbeanClass));
@@ -270,7 +308,7 @@
      *         Return <code>null</code> if the MBean is a DynamicMBean,
      *         or if no MBean interface is found.
      */
-    public static Class getMBeanInterface(Class baseClass) {
+    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;
@@ -291,10 +329,12 @@
      * @throws NotCompliantMBeanException The specified class is
      * not a JMX compliant Standard MBean.
      */
-    public static Class getStandardMBeanInterface(Class baseClass)
-        throws NotCompliantMBeanException {
-        Class current = baseClass;
-        Class mbeanInterface = null;
+    public static <T> Class<? super T> getStandardMBeanInterface(Class<T> baseClass)
+    throws NotCompliantMBeanException {
+        if (baseClass.isAnnotationPresent(MBean.class))
+            return baseClass;
+        Class<? super T> current = baseClass;
+        Class<? super T> mbeanInterface = null;
         while (current != null) {
             mbeanInterface =
                 findMBeanInterface(current, current.getName());
@@ -321,8 +361,10 @@
      * @throws NotCompliantMBeanException The specified class is
      * not a JMX compliant MXBean.
      */
-    public static Class getMXBeanInterface(Class baseClass)
+    public static <T> Class<? super T> getMXBeanInterface(Class<T> baseClass)
         throws NotCompliantMBeanException {
+        if (hasMXBeanAnnotation(baseClass))
+            return baseClass;
         try {
             return MXBeanSupport.findMXBeanInterface(baseClass);
         } catch (Exception e) {
@@ -345,19 +387,24 @@
      * ------------------------------------------
      */
 
+    static boolean hasMXBeanAnnotation(Class<?> c) {
+        MXBean m = c.getAnnotation(MXBean.class);
+        return (m != null && m.value());
+    }
 
     /**
      * 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 Class findMBeanInterface(Class aClass, String aName) {
-        Class current = aClass;
+    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 Class<?>[] interfaces = current.getInterfaces();
             final int len = interfaces.length;
             for (int i=0;i<len;i++)  {
-                final Class inter =
-                    implementsMBean(interfaces[i], aName);
+                Class<? super T> inter = Util.cast(interfaces[i]);
+                inter = implementsMBean(inter, aName);
                 if (inter != null) return inter;
             }
             current = current.getSuperclass();
@@ -365,6 +412,48 @@
         return null;
     }
 
+    public static String descriptionForElement(AnnotatedElement elmt) {
+        if (elmt == null)
+            return null;
+        Description d = elmt.getAnnotation(Description.class);
+        if (d == null)
+            return null;
+        return d.value();
+    }
+
+    public static String descriptionForParameter(
+            Annotation[] parameterAnnotations) {
+        for (Annotation a : parameterAnnotations) {
+            if (a instanceof Description)
+                return ((Description) a).value();
+        }
+        return null;
+    }
+
+    public static String nameForParameter(
+            Annotation[] parameterAnnotations) {
+        for (Annotation a : parameterAnnotations) {
+            Class<? extends Annotation> ac = a.annotationType();
+            // You'd really have to go out of your way to have more than
+            // one @Name annotation, so we don't check for that.
+            if (ac.getSimpleName().equals("Name")) {
+                try {
+                    Method value = ac.getMethod("value");
+                    if (value.getReturnType() == String.class &&
+                            value.getParameterTypes().length == 0) {
+                        return (String) value.invoke(a);
+                    }
+                } catch (Exception e) {
+                    MBEANSERVER_LOGGER.log(
+                            Level.WARNING,
+                            "Unexpected exception getting @" + ac.getName(),
+                            e);
+                }
+            }
+        }
+        return null;
+    }
+
     public static Descriptor descriptorForElement(final AnnotatedElement elmt) {
         if (elmt == null)
             return ImmutableDescriptor.EMPTY_DESCRIPTOR;
@@ -372,41 +461,18 @@
         return descriptorForAnnotations(annots);
     }
 
+    public static Descriptor descriptorForAnnotation(Annotation annot) {
+        return descriptorForAnnotations(new Annotation[] {annot});
+    }
+
     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();
-            for (Method element : elements) {
-                DescriptorKey key = element.getAnnotation(DescriptorKey.class);
-                if (key != null) {
-                    String name = key.value();
-                    Object value;
-                    try {
-                        value = element.invoke(a);
-                    } 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 (a instanceof DescriptorFields)
+                addDescriptorFieldsToMap(descriptorMap, (DescriptorFields) a);
+            addAnnotationFieldsToMap(descriptorMap, a);
         }
 
         if (descriptorMap.isEmpty())
@@ -415,6 +481,62 @@
             return new ImmutableDescriptor(descriptorMap);
     }
 
+    private static void addDescriptorFieldsToMap(
+            Map<String, Object> descriptorMap, DescriptorFields df) {
+        for (String field : df.value()) {
+            int eq = field.indexOf('=');
+            if (eq < 0) {
+                throw new IllegalArgumentException(
+                        "@DescriptorFields string must contain '=': " +
+                        field);
+            }
+            String name = field.substring(0, eq);
+            String value = field.substring(eq + 1);
+            addToMap(descriptorMap, name, value);
+        }
+    }
+
+    private static void addAnnotationFieldsToMap(
+            Map<String, Object> descriptorMap, Annotation a) {
+        Class<? extends Annotation> c = a.annotationType();
+        Method[] elements = c.getMethods();
+        for (Method element : elements) {
+            DescriptorKey key = element.getAnnotation(DescriptorKey.class);
+            if (key != null) {
+                String name = key.value();
+                Object value;
+                try {
+                    value = element.invoke(a);
+                } 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);
+                }
+                if (!key.omitIfDefault() ||
+                        !equals(value, element.getDefaultValue())) {
+                    value = annotationToField(value);
+                    addToMap(descriptorMap, name, value);
+                }
+            }
+        }
+    }
+
+    private static void addToMap(
+            Map<String, Object> descriptorMap, String name, Object 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);
+        }
+    }
+
     /**
      * Throws a NotCompliantMBeanException or a SecurityException.
      * @param notCompliant the class which was under examination
@@ -473,8 +595,13 @@
         // 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: " + x.getClass().getName());
+                "element using @DescriptorKey: " + c.getName());
     }
 
     // This must be consistent with the check for duplicate field values in
@@ -490,15 +617,15 @@
      * @param c The interface to be tested
      * @param clName The name of the class implementing this interface
      */
-    private static Class implementsMBean(Class c, String clName) {
+    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();
+        Class<?>[] interfaces = c.getInterfaces();
         for (int i = 0;i < interfaces.length; i++) {
             if (interfaces[i].getName().equals(clMBeanName))
-                return interfaces[i];
+                return Util.cast(interfaces[i]);
         }
 
         return null;