jdk/src/share/classes/javax/management/JMX.java
changeset 687 874e25a9844a
parent 2 90ce3da70b43
child 833 bfa2bef7517c
child 715 f16baef3a20e
--- a/jdk/src/share/classes/javax/management/JMX.java	Thu Jun 05 13:40:09 2008 +0200
+++ b/jdk/src/share/classes/javax/management/JMX.java	Thu Jun 05 13:42:47 2008 +0200
@@ -26,8 +26,17 @@
 package javax.management;
 
 import com.sun.jmx.mbeanserver.Introspector;
+import com.sun.jmx.remote.util.ClassLogger;
+import java.beans.BeanInfo;
+import java.beans.PropertyDescriptor;
+import java.io.Serializable;
 import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
+import java.util.Map;
+import java.util.TreeMap;
+import javax.management.openmbean.MXBeanMappingFactory;
 
 /**
  * Static methods from the JMX API.  There are no instances of this class.
@@ -39,6 +48,8 @@
      * this class.
      */
     static final JMX proof = new JMX();
+    private static final ClassLogger logger =
+        new ClassLogger("javax.management.misc", "JMX");
 
     private JMX() {}
 
@@ -85,6 +96,14 @@
     public static final String MXBEAN_FIELD = "mxbean";
 
     /**
+     * The name of the
+     * <a href="Descriptor.html#mxbeanMappingFactoryClass">{@code
+     * mxbeanMappingFactoryClass}</a> field.
+     */
+    public static final String MXBEAN_MAPPING_FACTORY_CLASS_FIELD =
+            "mxbeanMappingFactoryClass";
+
+    /**
      * The name of the <a href="Descriptor.html#openType">{@code
      * openType}</a> field.
      */
@@ -97,6 +116,264 @@
     public static final String ORIGINAL_TYPE_FIELD = "originalType";
 
     /**
+     * <p>Options to apply to an MBean proxy or to an instance of {@link
+     * StandardMBean}.</p>
+     *
+     * <p>For example, to specify a custom {@link MXBeanMappingFactory}
+     * for a {@code StandardMBean}, you might write this:</p>
+     *
+     * <pre>
+     * MXBeanMappingFactory factory = new MyMXBeanMappingFactory();
+     * JMX.MBeanOptions opts = new JMX.MBeanOptions();
+     * opts.setMXBeanMappingFactory(factory);
+     * StandardMBean mbean = new StandardMBean(impl, intf, opts);
+     * </pre>
+     *
+     * @see javax.management.JMX.ProxyOptions
+     */
+    public static class MBeanOptions implements Serializable, Cloneable {
+        private static final long serialVersionUID = -6380842449318177843L;
+
+        static final MBeanOptions MXBEAN = new MBeanOptions();
+        static {
+            MXBEAN.setMXBeanMappingFactory(MXBeanMappingFactory.DEFAULT);
+        }
+
+        private MXBeanMappingFactory mappingFactory;
+
+        /**
+         * <p>Construct an {@code MBeanOptions} object where all options have
+         * their default values.</p>
+         */
+        public MBeanOptions() {}
+
+        @Override
+        public MBeanOptions clone() {
+            try {
+                return (MBeanOptions) super.clone();
+            } catch (CloneNotSupportedException e) {
+                throw new AssertionError(e);
+            }
+        }
+
+        /**
+         * <p>True if this is an MXBean proxy or a StandardMBean instance
+         * that is an MXBean.  The default value is false.</p>
+         *
+         * <p>This method is equivalent to {@link #getMXBeanMappingFactory()
+         * this.getMXBeanMappingFactory()}{@code != null}.</p>
+         *
+         * @return true if this is an MXBean proxy or a StandardMBean instance
+         * that is an MXBean.
+         */
+        public boolean isMXBean() {
+            return (this.mappingFactory != null);
+        }
+
+        /**
+         * <p>The mappings between Java types and Open Types to be used in
+         * an MXBean proxy or a StandardMBean instance that is an MXBean,
+         * or null if this instance is not for an MXBean.
+         * The default value is null.</p>
+         *
+         * @return the mappings to be used in this proxy or StandardMBean,
+         * or null if this instance is not for an MXBean.
+         */
+        public MXBeanMappingFactory getMXBeanMappingFactory() {
+            return mappingFactory;
+        }
+
+        /**
+         * <p>Set the {@link #getMXBeanMappingFactory() MXBeanMappingFactory} to
+         * the given value.  The value should be null if this instance is not
+         * for an MXBean.  If this instance is for an MXBean, the value should
+         * usually be either a custom mapping factory, or
+         * {@link MXBeanMappingFactory#forInterface
+         * MXBeanMappingFactory.forInterface}{@code (mxbeanInterface)}
+         * which signifies
+         * that the {@linkplain MXBeanMappingFactory#DEFAULT default} mapping
+         * factory should be used unless an {@code @}{@link
+         * javax.management.openmbean.MXBeanMappingFactoryClass
+         * MXBeanMappingFactoryClass} annotation on {@code mxbeanInterface}
+         * specifies otherwise.</p>
+         *
+         * <p>Examples:</p>
+         * <pre>
+         * MBeanOptions opts = new MBeanOptions();
+         * opts.setMXBeanMappingFactory(myMappingFactory);
+         * MyMXBean proxy = JMX.newMBeanProxy(
+         *         mbeanServerConnection, objectName, MyMXBean.class, opts);
+         *
+         * // ...or...
+         *
+         * MBeanOptions opts = new MBeanOptions();
+         * MXBeanMappingFactory defaultFactoryForMyMXBean =
+         *         MXBeanMappingFactory.forInterface(MyMXBean.class);
+         * opts.setMXBeanMappingFactory(defaultFactoryForMyMXBean);
+         * MyMXBean proxy = JMX.newMBeanProxy(
+         *         mbeanServerConnection, objectName, MyMXBean.class, opts);
+         * </pre>
+         *
+         * @param f the new value.  If null, this instance is not for an
+         * MXBean.
+         */
+        public void setMXBeanMappingFactory(MXBeanMappingFactory f) {
+            this.mappingFactory = f;
+        }
+
+        /* To maximise object sharing, classes in this package can replace
+         * a private MBeanOptions with no MXBeanMappingFactory with one
+         * of these shared instances.  But they must be EXTREMELY careful
+         * never to give out the shared instances to user code, which could
+         * modify them.
+         */
+        private static final MBeanOptions[] CANONICALS = {
+            new MBeanOptions(), MXBEAN,
+        };
+        // Overridden in local subclasses:
+        MBeanOptions[] canonicals() {
+            return CANONICALS;
+        }
+
+        // This is only used by the logic for canonical instances.
+        // Overridden in local subclasses:
+        boolean same(MBeanOptions opt) {
+            return (opt.mappingFactory == mappingFactory);
+        }
+
+        final MBeanOptions canonical() {
+            for (MBeanOptions opt : canonicals()) {
+                if (opt.getClass() == this.getClass() && same(opt))
+                    return opt;
+            }
+            return this;
+        }
+
+        final MBeanOptions uncanonical() {
+            for (MBeanOptions opt : canonicals()) {
+                if (this == opt)
+                    return clone();
+            }
+            return this;
+        }
+
+        private Map<String, Object> toMap() {
+            Map<String, Object> map = new TreeMap<String, Object>();
+            try {
+                BeanInfo bi = java.beans.Introspector.getBeanInfo(getClass());
+                PropertyDescriptor[] pds = bi.getPropertyDescriptors();
+                for (PropertyDescriptor pd : pds) {
+                    String name = pd.getName();
+                    if (name.equals("class"))
+                        continue;
+                    Method get = pd.getReadMethod();
+                    if (get != null)
+                        map.put(name, get.invoke(this));
+                }
+            } catch (Exception e) {
+                Throwable t = e;
+                if (t instanceof InvocationTargetException)
+                    t = t.getCause();
+                map.put("Exception", t);
+            }
+            return map;
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + toMap();
+            // For example "MBeanOptions{MXBean=true, <etc>}".
+        }
+
+        /**
+         * <p>Indicates whether some other object is "equal to" this one. The
+         * result is true if and only if the other object is also an instance
+         * of MBeanOptions or a subclass, and has the same properties with
+         * the same values.</p>
+         * @return {@inheritDoc}
+         */
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this)
+                return true;
+            if (obj == null || obj.getClass() != this.getClass())
+                return false;
+            return toMap().equals(((MBeanOptions) obj).toMap());
+        }
+
+        @Override
+        public int hashCode() {
+            return toMap().hashCode();
+        }
+    }
+
+    /**
+     * <p>Options to apply to an MBean proxy.</p>
+     *
+     * @see #newMBeanProxy
+     */
+    public static class ProxyOptions extends MBeanOptions {
+        private static final long serialVersionUID = 7238804866098386559L;
+
+        private boolean notificationEmitter;
+
+        /**
+         * <p>Construct a {@code ProxyOptions} object where all options have
+         * their default values.</p>
+         */
+        public ProxyOptions() {}
+
+        @Override
+        public ProxyOptions clone() {
+            return (ProxyOptions) super.clone();
+        }
+
+        /**
+         * <p>Defines whether the returned proxy should
+         * implement {@link NotificationEmitter}.  The default value is false.</p>
+         *
+         * @return true if this proxy will be a NotificationEmitter.
+         *
+         * @see JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class,
+         * MBeanOptions)
+         */
+        public boolean isNotificationEmitter() {
+            return this.notificationEmitter;
+        }
+
+        /**
+         * <p>Set the {@link #isNotificationEmitter NotificationEmitter} option to
+         * the given value.</p>
+         * @param emitter the new value.
+         */
+        public void setNotificationEmitter(boolean emitter) {
+            this.notificationEmitter = emitter;
+        }
+
+        // Canonical objects for each of (MXBean,!MXBean) x (Emitter,!Emitter)
+        private static final ProxyOptions[] CANONICALS = {
+            new ProxyOptions(), new ProxyOptions(),
+            new ProxyOptions(), new ProxyOptions(),
+        };
+        static {
+            CANONICALS[1].setMXBeanMappingFactory(MXBeanMappingFactory.DEFAULT);
+            CANONICALS[2].setNotificationEmitter(true);
+            CANONICALS[3].setMXBeanMappingFactory(MXBeanMappingFactory.DEFAULT);
+            CANONICALS[3].setNotificationEmitter(true);
+        }
+        @Override
+        MBeanOptions[] canonicals() {
+            return CANONICALS;
+        }
+
+        @Override
+        boolean same(MBeanOptions opt) {
+            return (super.same(opt) && opt instanceof ProxyOptions &&
+                    ((ProxyOptions) opt).notificationEmitter == notificationEmitter);
+        }
+    }
+
+    /**
      * <p>Make a proxy for a Standard MBean in a local or remote
      * MBean Server.</p>
      *
@@ -172,7 +449,7 @@
      *
      * <p>This method behaves the same as {@link
      * #newMBeanProxy(MBeanServerConnection, ObjectName, Class)}, but
-     * additionally, if {@code notificationBroadcaster} is {@code
+     * additionally, if {@code notificationEmitter} is {@code
      * true}, then the MBean is assumed to be a {@link
      * NotificationBroadcaster} or {@link NotificationEmitter} and the
      * returned proxy will implement {@link NotificationEmitter} as
@@ -189,25 +466,21 @@
      * {@code connection} to forward to.
      * @param interfaceClass the management interface that the MBean
      * exports, which will also be implemented by the returned proxy.
-     * @param notificationBroadcaster make the returned proxy
+     * @param notificationEmitter make the returned proxy
      * implement {@link NotificationEmitter} by forwarding its methods
      * via {@code connection}.
-     *
      * @param <T> allows the compiler to know that if the {@code
      * interfaceClass} parameter is {@code MyMBean.class}, for
      * example, then the return type is {@code MyMBean}.
-     *
      * @return the new proxy instance.
      */
     public static <T> T newMBeanProxy(MBeanServerConnection connection,
                                       ObjectName objectName,
                                       Class<T> interfaceClass,
-                                      boolean notificationBroadcaster) {
-        return MBeanServerInvocationHandler.newProxyInstance(
-                connection,
-                objectName,
-                interfaceClass,
-                notificationBroadcaster);
+                                      boolean notificationEmitter) {
+        ProxyOptions opts = new ProxyOptions();
+        opts.setNotificationEmitter(notificationEmitter);
+        return newMBeanProxy(connection, objectName, interfaceClass, opts);
     }
 
     /**
@@ -314,7 +587,7 @@
      *
      * <p>This method behaves the same as {@link
      * #newMXBeanProxy(MBeanServerConnection, ObjectName, Class)}, but
-     * additionally, if {@code notificationBroadcaster} is {@code
+     * additionally, if {@code notificationEmitter} is {@code
      * true}, then the MXBean is assumed to be a {@link
      * NotificationBroadcaster} or {@link NotificationEmitter} and the
      * returned proxy will implement {@link NotificationEmitter} as
@@ -331,31 +604,105 @@
      * {@code connection} to forward to.
      * @param interfaceClass the MXBean interface,
      * which will also be implemented by the returned proxy.
-     * @param notificationBroadcaster make the returned proxy
+     * @param notificationEmitter make the returned proxy
      * implement {@link NotificationEmitter} by forwarding its methods
      * via {@code connection}.
-     *
      * @param <T> allows the compiler to know that if the {@code
      * interfaceClass} parameter is {@code MyMXBean.class}, for
      * example, then the return type is {@code MyMXBean}.
-     *
      * @return the new proxy instance.
      */
     public static <T> T newMXBeanProxy(MBeanServerConnection connection,
                                        ObjectName objectName,
                                        Class<T> interfaceClass,
-                                       boolean notificationBroadcaster) {
-        // Check interface for MXBean compliance
-        //
+                                       boolean notificationEmitter) {
+        ProxyOptions opts = new ProxyOptions();
+        MXBeanMappingFactory f = MXBeanMappingFactory.forInterface(interfaceClass);
+        opts.setMXBeanMappingFactory(f);
+        opts.setNotificationEmitter(notificationEmitter);
+        return newMBeanProxy(connection, objectName, interfaceClass, opts);
+    }
+
+    /**
+     * <p>Make a proxy for a Standard MBean or MXBean in a local or remote MBean
+     * Server that may also support the methods of {@link
+     * NotificationEmitter} and (for an MXBean) that may define custom MXBean
+     * type mappings.</p>
+     *
+     * <p>This method behaves the same as
+     * {@link #newMBeanProxy(MBeanServerConnection, ObjectName, Class)} or
+     * {@link #newMXBeanProxy(MBeanServerConnection, ObjectName, Class)},
+     * according as {@code opts.isMXBean()} is respectively false or true; but
+     * with the following changes based on {@code opts}.</p>
+     *
+     * <ul>
+     *     <li>If {@code opts.isNotificationEmitter()} is {@code
+     *         true}, then the MBean is assumed to be a {@link
+     *         NotificationBroadcaster} or {@link NotificationEmitter} and the
+     *         returned proxy will implement {@link NotificationEmitter} as
+     *         well as {@code interfaceClass}.  A call to {@link
+     *         NotificationBroadcaster#addNotificationListener} on the proxy
+     *         will result in a call to {@link
+     *         MBeanServerConnection#addNotificationListener(ObjectName,
+     *         NotificationListener, NotificationFilter, Object)}, and
+     *         likewise for the other methods of {@link
+     *     NotificationBroadcaster} and {@link NotificationEmitter}.</li>
+     *
+     *     <li>If {@code opts.getMXBeanMappingFactory()} is not null,
+     *         then the mappings it defines will be applied to convert between
+     *     arbitrary Java types and Open Types.</li>
+     * </ul>
+     *
+     * @param connection the MBean server to forward to.
+     * @param objectName the name of the MBean within
+     * {@code connection} to forward to.
+     * @param interfaceClass the Standard MBean or MXBean interface,
+     * which will also be implemented by the returned proxy.
+     * @param opts the options to apply for this proxy.  Can be null,
+     * in which case default options are applied.
+     * @param <T> allows the compiler to know that if the {@code
+     * interfaceClass} parameter is {@code MyMXBean.class}, for
+     * example, then the return type is {@code MyMXBean}.
+     * @return the new proxy instance.
+     *
+     * @throws IllegalArgumentException if {@code interfaceClass} is not a
+     * valid MXBean interface.
+     */
+    public static <T> T newMBeanProxy(MBeanServerConnection connection,
+                                      ObjectName objectName,
+                                      Class<T> interfaceClass,
+                                      MBeanOptions opts) {
         try {
-            Introspector.testComplianceMXBeanInterface(interfaceClass);
+            return newMBeanProxy2(connection, objectName, interfaceClass, opts);
         } catch (NotCompliantMBeanException e) {
             throw new IllegalArgumentException(e);
         }
+    }
+
+    private static <T> T newMBeanProxy2(MBeanServerConnection connection,
+                                        ObjectName objectName,
+                                        Class<T> interfaceClass,
+                                        MBeanOptions opts)
+    throws NotCompliantMBeanException {
+
+        if (opts == null)
+            opts = new MBeanOptions();
+
+        boolean notificationEmitter = opts instanceof ProxyOptions &&
+                ((ProxyOptions) opts).isNotificationEmitter();
+
+        MXBeanMappingFactory mappingFactory = opts.getMXBeanMappingFactory();
+
+        if (mappingFactory != null) {
+            // Check interface for MXBean compliance
+            Introspector.testComplianceMXBeanInterface(interfaceClass,
+                    mappingFactory);
+        }
+
         InvocationHandler handler = new MBeanServerInvocationHandler(
-                connection, objectName, true);
+                connection, objectName, opts);
         final Class[] interfaces;
-        if (notificationBroadcaster) {
+        if (notificationEmitter) {
             interfaces =
                 new Class<?>[] {interfaceClass, NotificationEmitter.class};
         } else