jdk/src/share/classes/com/sun/jmx/mbeanserver/MBeanIntrospector.java
changeset 834 dc74d4ddc28e
parent 715 f16baef3a20e
parent 833 bfa2bef7517c
child 1221 e3dc70e4e610
--- a/jdk/src/share/classes/com/sun/jmx/mbeanserver/MBeanIntrospector.java	Sat Jul 05 23:29:16 2008 -0700
+++ b/jdk/src/share/classes/com/sun/jmx/mbeanserver/MBeanIntrospector.java	Wed Jul 09 09:56:00 2008 -0700
@@ -36,20 +36,28 @@
 import java.lang.reflect.Type;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.WeakHashMap;
+import javax.management.Description;
 
 import javax.management.Descriptor;
 import javax.management.ImmutableDescriptor;
 import javax.management.IntrospectionException;
 import javax.management.InvalidAttributeValueException;
+import javax.management.MBean;
 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.MXBean;
+import javax.management.ManagedAttribute;
+import javax.management.ManagedOperation;
 import javax.management.NotCompliantMBeanException;
 import javax.management.NotificationBroadcaster;
+import javax.management.NotificationInfo;
+import javax.management.NotificationInfos;
 import javax.management.ReflectionException;
 
 /**
@@ -153,6 +161,25 @@
     abstract MBeanAttributeInfo getMBeanAttributeInfo(String attributeName,
             M getter, M setter) throws IntrospectionException;
 
+    final String getAttributeDescription(
+            String attributeName, String defaultDescription,
+            Method getter, Method setter) throws IntrospectionException {
+        String g = Introspector.descriptionForElement(getter);
+        String s = Introspector.descriptionForElement(setter);
+        if (g == null) {
+            if (s == null)
+                return defaultDescription;
+            else
+                return s;
+        } else if (s == null || g.equals(s)) {
+            return g;
+        } else {
+            throw new IntrospectionException(
+                    "Inconsistent @Description on getter and setter for " +
+                    "attribute " + attributeName);
+        }
+    }
+
     /**
      * Construct an MBeanOperationInfo for the given operation based on
      * the M it was derived from.
@@ -184,8 +211,12 @@
     }
 
     void checkCompliance(Class<?> mbeanType) throws NotCompliantMBeanException {
-        if (!mbeanType.isInterface()) {
-            throw new NotCompliantMBeanException("Not an interface: " +
+        if (!mbeanType.isInterface() &&
+                !mbeanType.isAnnotationPresent(MBean.class) &&
+                !Introspector.hasMXBeanAnnotation(mbeanType)) {
+            throw new NotCompliantMBeanException("Not an interface and " +
+                    "does not have @" + MBean.class.getSimpleName() +
+                    " or @" + MXBean.class.getSimpleName() + " annotation: " +
                     mbeanType.getName());
         }
     }
@@ -194,7 +225,12 @@
      * Get the methods to be analyzed to build the MBean interface.
      */
     List<Method> getMethods(final Class<?> mbeanType) throws Exception {
-        return Arrays.asList(mbeanType.getMethods());
+        if (mbeanType.isInterface())
+            return Arrays.asList(mbeanType.getMethods());
+
+        final List<Method> methods = newList();
+        getAnnotatedMethods(mbeanType, methods);
+        return methods;
     }
 
     final PerInterface<M> getPerInterface(Class<?> mbeanInterface)
@@ -232,8 +268,11 @@
             MBeanAnalyzer<M> analyzer) throws IntrospectionException {
         final MBeanInfoMaker maker = new MBeanInfoMaker();
         analyzer.visit(maker);
-        final String description =
+        final String defaultDescription =
                 "Information on the management interface of the MBean";
+        String description = Introspector.descriptionForElement(mbeanInterface);
+        if (description == null)
+            description = defaultDescription;
         return maker.makeMBeanInfo(mbeanInterface, description);
     }
 
@@ -407,7 +446,15 @@
     throws NotCompliantMBeanException {
         MBeanInfo mbi =
                 getClassMBeanInfo(resource.getClass(), perInterface);
-        MBeanNotificationInfo[] notifs = findNotifications(resource);
+        MBeanNotificationInfo[] notifs;
+        try {
+            notifs = findNotifications(resource);
+        } catch (RuntimeException e) {
+            NotCompliantMBeanException x =
+                    new NotCompliantMBeanException(e.getMessage());
+            x.initCause(e);
+            throw x;
+        }
         Descriptor d = getSpecificMBeanDescriptor();
         boolean anyNotifs = (notifs != null && notifs.length > 0);
         if (!anyNotifs && ImmutableDescriptor.EMPTY_DESCRIPTOR.equals(d))
@@ -460,13 +507,43 @@
         }
     }
 
+    /*
+     * Add to "methods" every public method that has the @ManagedAttribute
+     * or @ManagedOperation annotation, in the given class or any of
+     * its superclasses or superinterfaces.
+     *
+     * We always add superclass or superinterface methods first, so that
+     * the stable sort used by eliminateCovariantMethods will put the
+     * method from the most-derived class last.  This means that we will
+     * see the version of the @ManagedAttribute (or ...Operation) annotation
+     * from that method, which might have a different description or whatever.
+     */
+    private static void getAnnotatedMethods(Class<?> c, List<Method> methods)
+    throws Exception {
+        Class<?> sup = c.getSuperclass();
+        if (sup != null)
+            getAnnotatedMethods(sup, methods);
+        Class<?>[] intfs = c.getInterfaces();
+        for (Class<?> intf : intfs)
+            getAnnotatedMethods(intf, methods);
+        for (Method m : c.getMethods()) {
+            // We are careful not to add m if it is inherited from a parent
+            // class or interface, because duplicate methods lead to nasty
+            // behaviour in eliminateCovariantMethods.
+            if (m.getDeclaringClass() == c &&
+                    (m.isAnnotationPresent(ManagedAttribute.class) ||
+                     m.isAnnotationPresent(ManagedOperation.class)))
+                methods.add(m);
+        }
+    }
+
     static MBeanNotificationInfo[] findNotifications(Object moi) {
         if (!(moi instanceof NotificationBroadcaster))
             return null;
         MBeanNotificationInfo[] mbn =
                 ((NotificationBroadcaster) moi).getNotificationInfo();
         if (mbn == null || mbn.length == 0)
-            return null;
+            return findNotificationsFromAnnotations(moi.getClass());
         MBeanNotificationInfo[] result =
                 new MBeanNotificationInfo[mbn.length];
         for (int i = 0; i < mbn.length; i++) {
@@ -478,11 +555,81 @@
         return result;
     }
 
+    private static MBeanNotificationInfo[] findNotificationsFromAnnotations(
+            Class<?> mbeanClass) {
+        Class<?> c = getAnnotatedNotificationInfoClass(mbeanClass);
+        if (c == null)
+            return null;
+        NotificationInfo ni = c.getAnnotation(NotificationInfo.class);
+        NotificationInfos nis = c.getAnnotation(NotificationInfos.class);
+        List<NotificationInfo> list = newList();
+        if (ni != null)
+            list.add(ni);
+        if (nis != null)
+            list.addAll(Arrays.asList(nis.value()));
+        if (list.isEmpty())
+            return null;
+        List<MBeanNotificationInfo> mbnis = newList();
+        for (NotificationInfo x : list) {
+            // The Descriptor includes any fields explicitly specified by
+            // x.descriptorFields(), plus any fields from the contained
+            // @Description annotation.
+            Descriptor d = new ImmutableDescriptor(x.descriptorFields());
+            d = ImmutableDescriptor.union(
+                    d, Introspector.descriptorForAnnotation(x.description()));
+            MBeanNotificationInfo mbni = new MBeanNotificationInfo(
+                    x.types(), x.notificationClass().getName(),
+                    x.description().value(), d);
+            mbnis.add(mbni);
+        }
+        return mbnis.toArray(new MBeanNotificationInfo[mbnis.size()]);
+    }
+
+    private static final Map<Class<?>, WeakReference<Class<?>>>
+            annotatedNotificationInfoClasses = newWeakHashMap();
+
+    private static Class<?> getAnnotatedNotificationInfoClass(Class<?> baseClass) {
+        synchronized (annotatedNotificationInfoClasses) {
+            WeakReference<Class<?>> wr =
+                    annotatedNotificationInfoClasses.get(baseClass);
+            if (wr != null)
+                return wr.get();
+            Class<?> c = null;
+            if (baseClass.isAnnotationPresent(NotificationInfo.class) ||
+                    baseClass.isAnnotationPresent(NotificationInfos.class)) {
+                c = baseClass;
+            } else {
+                Class<?>[] intfs = baseClass.getInterfaces();
+                for (Class<?> intf : intfs) {
+                    Class<?> c1 = getAnnotatedNotificationInfoClass(intf);
+                    if (c1 != null) {
+                        if (c != null) {
+                            throw new IllegalArgumentException(
+                                    "Class " + baseClass.getName() + " inherits " +
+                                    "@NotificationInfo(s) from both " +
+                                    c.getName() + " and " + c1.getName());
+                        }
+                        c = c1;
+                    }
+                }
+            }
+            // Record the result of the search.  If no @NotificationInfo(s)
+            // were found, c is null, and we store a WeakReference(null).
+            // This prevents us from having to search again and fail again.
+            annotatedNotificationInfoClasses.put(baseClass,
+                    new WeakReference<Class<?>>(c));
+            return c;
+        }
+    }
+
     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";
+            String descr = "Public constructor of the MBean";
+            Description d = cons[i].getAnnotation(Description.class);
+            if (d != null)
+                descr = d.value();
             mbc[i] = new MBeanConstructorInfo(descr, cons[i]);
         }
         return mbc;