--- 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