diff -r 4ebc2e2fb97c -r 71c04702a3d5 src/java.management/share/classes/javax/management/MBeanServerInvocationHandler.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.management/share/classes/javax/management/MBeanServerInvocationHandler.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,497 @@
+/*
+ * Copyright (c) 2002, 2013, 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 javax.management;
+
+import com.sun.jmx.mbeanserver.MXBeanProxy;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.WeakHashMap;
+
+/**
+ *
{@link InvocationHandler} that forwards methods in an MBean's
+ * management interface through the MBean server to the MBean.
+ *
+ * Given an {@link MBeanServerConnection}, the {@link ObjectName}
+ * of an MBean within that MBean server, and a Java interface
+ * Intf
that describes the management interface of the
+ * MBean using the patterns for a Standard MBean or an MXBean, this
+ * class can be used to construct a proxy for the MBean. The proxy
+ * implements the interface Intf
such that all of its
+ * methods are forwarded through the MBean server to the MBean.
+ *
+ * If the {@code InvocationHandler} is for an MXBean, then the parameters of
+ * a method are converted from the type declared in the MXBean
+ * interface into the corresponding mapped type, and the return value
+ * is converted from the mapped type into the declared type. For
+ * example, with the method
+
+ * {@code public List reverse(List list);}
+
+ * and given that the mapped type for {@code List} is {@code
+ * String[]}, a call to {@code proxy.reverse(someList)} will convert
+ * {@code someList} from a {@code List} to a {@code String[]},
+ * call the MBean operation {@code reverse}, then convert the returned
+ * {@code String[]} into a {@code List}.
+ *
+ * The method Object.toString(), Object.hashCode(), or
+ * Object.equals(Object), when invoked on a proxy using this
+ * invocation handler, is forwarded to the MBean server as a method on
+ * the proxied MBean only if it appears in one of the proxy's
+ * interfaces. For a proxy created with {@link
+ * JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class)
+ * JMX.newMBeanProxy} or {@link
+ * JMX#newMXBeanProxy(MBeanServerConnection, ObjectName, Class)
+ * JMX.newMXBeanProxy}, this means that the method must appear in the
+ * Standard MBean or MXBean interface. Otherwise these methods have
+ * the following behavior:
+ *
+ * - toString() returns a string representation of the proxy
+ *
- hashCode() returns a hash code for the proxy such
+ * that two equal proxies have the same hash code
+ *
- equals(Object)
+ * returns true if and only if the Object argument is of the same
+ * proxy class as this proxy, with an MBeanServerInvocationHandler
+ * that has the same MBeanServerConnection and ObjectName; if one
+ * of the {@code MBeanServerInvocationHandler}s was constructed with
+ * a {@code Class} argument then the other must have been constructed
+ * with the same {@code Class} for {@code equals} to return true.
+ *
+ *
+ * @since 1.5
+ */
+public class MBeanServerInvocationHandler implements InvocationHandler {
+ /**
+ * Invocation handler that forwards methods through an MBean
+ * server to a Standard MBean. This constructor may be called
+ * instead of relying on {@link
+ * JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class)
+ * JMX.newMBeanProxy}, for instance if you need to supply a
+ * different {@link ClassLoader} to {@link Proxy#newProxyInstance
+ * Proxy.newProxyInstance}.
+ *
+ * This constructor is not appropriate for an MXBean. Use
+ * {@link #MBeanServerInvocationHandler(MBeanServerConnection,
+ * ObjectName, boolean)} for that. This constructor is equivalent
+ * to {@code new MBeanServerInvocationHandler(connection,
+ * objectName, false)}.
+ *
+ * @param connection the MBean server connection through which all
+ * methods of a proxy using this handler will be forwarded.
+ *
+ * @param objectName the name of the MBean within the MBean server
+ * to which methods will be forwarded.
+ */
+ public MBeanServerInvocationHandler(MBeanServerConnection connection,
+ ObjectName objectName) {
+
+ this(connection, objectName, false);
+ }
+
+ /**
+ * Invocation handler that can forward methods through an MBean
+ * server to a Standard MBean or MXBean. This constructor may be called
+ * instead of relying on {@link
+ * JMX#newMXBeanProxy(MBeanServerConnection, ObjectName, Class)
+ * JMX.newMXBeanProxy}, for instance if you need to supply a
+ * different {@link ClassLoader} to {@link Proxy#newProxyInstance
+ * Proxy.newProxyInstance}.
+ *
+ * @param connection the MBean server connection through which all
+ * methods of a proxy using this handler will be forwarded.
+ *
+ * @param objectName the name of the MBean within the MBean server
+ * to which methods will be forwarded.
+ *
+ * @param isMXBean if true, the proxy is for an {@link MXBean}, and
+ * appropriate mappings will be applied to method parameters and return
+ * values.
+ *
+ * @since 1.6
+ */
+ public MBeanServerInvocationHandler(MBeanServerConnection connection,
+ ObjectName objectName,
+ boolean isMXBean) {
+ if (connection == null) {
+ throw new IllegalArgumentException("Null connection");
+ }
+ if (Proxy.isProxyClass(connection.getClass())) {
+ if (MBeanServerInvocationHandler.class.isAssignableFrom(
+ Proxy.getInvocationHandler(connection).getClass())) {
+ throw new IllegalArgumentException("Wrapping MBeanServerInvocationHandler");
+ }
+ }
+ if (objectName == null) {
+ throw new IllegalArgumentException("Null object name");
+ }
+ this.connection = connection;
+ this.objectName = objectName;
+ this.isMXBean = isMXBean;
+ }
+
+ /**
+ * The MBean server connection through which the methods of
+ * a proxy using this handler are forwarded.
+ *
+ * @return the MBean server connection.
+ *
+ * @since 1.6
+ */
+ public MBeanServerConnection getMBeanServerConnection() {
+ return connection;
+ }
+
+ /**
+ * The name of the MBean within the MBean server to which methods
+ * are forwarded.
+ *
+ * @return the object name.
+ *
+ * @since 1.6
+ */
+ public ObjectName getObjectName() {
+ return objectName;
+ }
+
+ /**
+ *
If true, the proxy is for an MXBean, and appropriate mappings
+ * are applied to method parameters and return values.
+ *
+ * @return whether the proxy is for an MXBean.
+ *
+ * @since 1.6
+ */
+ public boolean isMXBean() {
+ return isMXBean;
+ }
+
+ /**
+ *
Return a proxy that implements the given interface by
+ * forwarding its methods through the given MBean server to the
+ * named MBean. As of 1.6, the methods {@link
+ * JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class)} and
+ * {@link JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class,
+ * boolean)} are preferred to this method.
+ *
+ * This method is equivalent to {@link Proxy#newProxyInstance
+ * Proxy.newProxyInstance}(interfaceClass.getClassLoader(),
+ * interfaces, handler)
. Here handler
is the
+ * result of {@link #MBeanServerInvocationHandler new
+ * MBeanServerInvocationHandler(connection, objectName)}, and
+ * interfaces
is an array that has one element if
+ * notificationBroadcaster
is false and two if it is
+ * true. The first element of interfaces
is
+ * interfaceClass
and the second, if present, is
+ * NotificationEmitter.class
.
+ *
+ * @param connection the MBean server to forward to.
+ * @param objectName the name of the MBean within
+ * 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
+ * implement {@link NotificationEmitter} by forwarding its methods
+ * via connection
. 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}.
+ *
+ * @param 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.
+ *
+ * @see JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class, boolean)
+ */
+ public static T newProxyInstance(MBeanServerConnection connection,
+ ObjectName objectName,
+ Class interfaceClass,
+ boolean notificationBroadcaster) {
+ return JMX.newMBeanProxy(connection, objectName, interfaceClass, notificationBroadcaster);
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ final Class> methodClass = method.getDeclaringClass();
+
+ if (methodClass.equals(NotificationBroadcaster.class)
+ || methodClass.equals(NotificationEmitter.class))
+ return invokeBroadcasterMethod(proxy, method, args);
+
+ // local or not: equals, toString, hashCode
+ if (shouldDoLocally(proxy, method))
+ return doLocally(proxy, method, args);
+
+ try {
+ if (isMXBean()) {
+ MXBeanProxy p = findMXBeanProxy(methodClass);
+ return p.invoke(connection, objectName, method, args);
+ } else {
+ final String methodName = method.getName();
+ final Class>[] paramTypes = method.getParameterTypes();
+ final Class> returnType = method.getReturnType();
+
+ /* Inexplicably, InvocationHandler specifies that args is null
+ when the method takes no arguments rather than a
+ zero-length array. */
+ final int nargs = (args == null) ? 0 : args.length;
+
+ if (methodName.startsWith("get")
+ && methodName.length() > 3
+ && nargs == 0
+ && !returnType.equals(Void.TYPE)) {
+ return connection.getAttribute(objectName,
+ methodName.substring(3));
+ }
+
+ if (methodName.startsWith("is")
+ && methodName.length() > 2
+ && nargs == 0
+ && (returnType.equals(Boolean.TYPE)
+ || returnType.equals(Boolean.class))) {
+ return connection.getAttribute(objectName,
+ methodName.substring(2));
+ }
+
+ if (methodName.startsWith("set")
+ && methodName.length() > 3
+ && nargs == 1
+ && returnType.equals(Void.TYPE)) {
+ Attribute attr = new Attribute(methodName.substring(3), args[0]);
+ connection.setAttribute(objectName, attr);
+ return null;
+ }
+
+ final String[] signature = new String[paramTypes.length];
+ for (int i = 0; i < paramTypes.length; i++)
+ signature[i] = paramTypes[i].getName();
+ return connection.invoke(objectName, methodName,
+ args, signature);
+ }
+ } catch (MBeanException e) {
+ throw e.getTargetException();
+ } catch (RuntimeMBeanException re) {
+ throw re.getTargetException();
+ } catch (RuntimeErrorException rre) {
+ throw rre.getTargetError();
+ }
+ /* The invoke may fail because it can't get to the MBean, with
+ one of the these exceptions declared by
+ MBeanServerConnection.invoke:
+ - RemoteException: can't talk to MBeanServer;
+ - InstanceNotFoundException: objectName is not registered;
+ - ReflectionException: objectName is registered but does not
+ have the method being invoked.
+ In all of these cases, the exception will be wrapped by the
+ proxy mechanism in an UndeclaredThrowableException unless
+ it happens to be declared in the "throws" clause of the
+ method being invoked on the proxy.
+ */
+ }
+
+ private static MXBeanProxy findMXBeanProxy(Class> mxbeanInterface) {
+ synchronized (mxbeanProxies) {
+ WeakReference proxyRef =
+ mxbeanProxies.get(mxbeanInterface);
+ MXBeanProxy p = (proxyRef == null) ? null : proxyRef.get();
+ if (p == null) {
+ try {
+ p = new MXBeanProxy(mxbeanInterface);
+ } catch (IllegalArgumentException e) {
+ String msg = "Cannot make MXBean proxy for " +
+ mxbeanInterface.getName() + ": " + e.getMessage();
+ IllegalArgumentException iae =
+ new IllegalArgumentException(msg, e.getCause());
+ iae.setStackTrace(e.getStackTrace());
+ throw iae;
+ }
+ mxbeanProxies.put(mxbeanInterface,
+ new WeakReference(p));
+ }
+ return p;
+ }
+ }
+ private static final WeakHashMap, WeakReference>
+ mxbeanProxies = new WeakHashMap, WeakReference>();
+
+ private Object invokeBroadcasterMethod(Object proxy, Method method,
+ Object[] args) throws Exception {
+ final String methodName = method.getName();
+ final int nargs = (args == null) ? 0 : args.length;
+
+ if (methodName.equals("addNotificationListener")) {
+ /* The various throws of IllegalArgumentException here
+ should not happen, since we know what the methods in
+ NotificationBroadcaster and NotificationEmitter
+ are. */
+ if (nargs != 3) {
+ final String msg =
+ "Bad arg count to addNotificationListener: " + nargs;
+ throw new IllegalArgumentException(msg);
+ }
+ /* Other inconsistencies will produce ClassCastException
+ below. */
+
+ NotificationListener listener = (NotificationListener) args[0];
+ NotificationFilter filter = (NotificationFilter) args[1];
+ Object handback = args[2];
+ connection.addNotificationListener(objectName,
+ listener,
+ filter,
+ handback);
+ return null;
+
+ } else if (methodName.equals("removeNotificationListener")) {
+
+ /* NullPointerException if method with no args, but that
+ shouldn't happen because removeNL does have args. */
+ NotificationListener listener = (NotificationListener) args[0];
+
+ switch (nargs) {
+ case 1:
+ connection.removeNotificationListener(objectName, listener);
+ return null;
+
+ case 3:
+ NotificationFilter filter = (NotificationFilter) args[1];
+ Object handback = args[2];
+ connection.removeNotificationListener(objectName,
+ listener,
+ filter,
+ handback);
+ return null;
+
+ default:
+ final String msg =
+ "Bad arg count to removeNotificationListener: " + nargs;
+ throw new IllegalArgumentException(msg);
+ }
+
+ } else if (methodName.equals("getNotificationInfo")) {
+
+ if (args != null) {
+ throw new IllegalArgumentException("getNotificationInfo has " +
+ "args");
+ }
+
+ MBeanInfo info = connection.getMBeanInfo(objectName);
+ return info.getNotifications();
+
+ } else {
+ throw new IllegalArgumentException("Bad method name: " +
+ methodName);
+ }
+ }
+
+ private boolean shouldDoLocally(Object proxy, Method method) {
+ final String methodName = method.getName();
+ if ((methodName.equals("hashCode") || methodName.equals("toString"))
+ && method.getParameterTypes().length == 0
+ && isLocal(proxy, method))
+ return true;
+ if (methodName.equals("equals")
+ && Arrays.equals(method.getParameterTypes(),
+ new Class>[] {Object.class})
+ && isLocal(proxy, method))
+ return true;
+ if (methodName.equals("finalize")
+ && method.getParameterTypes().length == 0) {
+ return true;
+ }
+ return false;
+ }
+
+ private Object doLocally(Object proxy, Method method, Object[] args) {
+ final String methodName = method.getName();
+
+ if (methodName.equals("equals")) {
+
+ if (this == args[0]) {
+ return true;
+ }
+
+ if (!(args[0] instanceof Proxy)) {
+ return false;
+ }
+
+ final InvocationHandler ihandler =
+ Proxy.getInvocationHandler(args[0]);
+
+ if (ihandler == null ||
+ !(ihandler instanceof MBeanServerInvocationHandler)) {
+ return false;
+ }
+
+ final MBeanServerInvocationHandler handler =
+ (MBeanServerInvocationHandler)ihandler;
+
+ return connection.equals(handler.connection) &&
+ objectName.equals(handler.objectName) &&
+ proxy.getClass().equals(args[0].getClass());
+ } else if (methodName.equals("toString")) {
+ return (isMXBean() ? "MX" : "M") + "BeanProxy(" +
+ connection + "[" + objectName + "])";
+ } else if (methodName.equals("hashCode")) {
+ return objectName.hashCode()+connection.hashCode();
+ } else if (methodName.equals("finalize")) {
+ // ignore the finalizer invocation via proxy
+ return null;
+ }
+
+ throw new RuntimeException("Unexpected method name: " + methodName);
+ }
+
+ private static boolean isLocal(Object proxy, Method method) {
+ final Class>[] interfaces = proxy.getClass().getInterfaces();
+ if(interfaces == null) {
+ return true;
+ }
+
+ final String methodName = method.getName();
+ final Class>[] params = method.getParameterTypes();
+ for (Class> intf : interfaces) {
+ try {
+ intf.getMethod(methodName, params);
+ return false; // found method in one of our interfaces
+ } catch (NoSuchMethodException nsme) {
+ // OK.
+ }
+ }
+
+ return true; // did not find in any interface
+ }
+
+ private final MBeanServerConnection connection;
+ private final ObjectName objectName;
+ private final boolean isMXBean;
+}