You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
498 lines
20 KiB
498 lines
20 KiB
/*
|
|
* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
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;
|
|
|
|
/**
|
|
* <p>{@link InvocationHandler} that forwards methods in an MBean's
|
|
* management interface through the MBean server to the MBean.</p>
|
|
*
|
|
* <p>Given an {@link MBeanServerConnection}, the {@link ObjectName}
|
|
* of an MBean within that MBean server, and a Java interface
|
|
* <code>Intf</code> 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 <code>Intf</code> such that all of its
|
|
* methods are forwarded through the MBean server to the MBean.</p>
|
|
*
|
|
* <p>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<br>
|
|
|
|
* {@code public List<String> reverse(List<String> list);}<br>
|
|
|
|
* and given that the mapped type for {@code List<String>} is {@code
|
|
* String[]}, a call to {@code proxy.reverse(someList)} will convert
|
|
* {@code someList} from a {@code List<String>} to a {@code String[]},
|
|
* call the MBean operation {@code reverse}, then convert the returned
|
|
* {@code String[]} into a {@code List<String>}.</p>
|
|
*
|
|
* <p>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:
|
|
* <ul>
|
|
* <li>toString() returns a string representation of the proxy
|
|
* <li>hashCode() returns a hash code for the proxy such
|
|
* that two equal proxies have the same hash code
|
|
* <li>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.
|
|
* </ul>
|
|
*
|
|
* @since 1.5
|
|
*/
|
|
public class MBeanServerInvocationHandler implements InvocationHandler {
|
|
/**
|
|
* <p>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}.</p>
|
|
*
|
|
* <p>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)}.</p>
|
|
*
|
|
* @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);
|
|
}
|
|
|
|
/**
|
|
* <p>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}.</p>
|
|
*
|
|
* @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;
|
|
}
|
|
|
|
/**
|
|
* <p>The MBean server connection through which the methods of
|
|
* a proxy using this handler are forwarded.</p>
|
|
*
|
|
* @return the MBean server connection.
|
|
*
|
|
* @since 1.6
|
|
*/
|
|
public MBeanServerConnection getMBeanServerConnection() {
|
|
return connection;
|
|
}
|
|
|
|
/**
|
|
* <p>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;
|
|
}
|
|
|
|
/**
|
|
* <p>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;
|
|
}
|
|
|
|
/**
|
|
* <p>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.</p>
|
|
*
|
|
* <p>This method is equivalent to {@link Proxy#newProxyInstance
|
|
* Proxy.newProxyInstance}<code>(interfaceClass.getClassLoader(),
|
|
* interfaces, handler)</code>. Here <code>handler</code> is the
|
|
* result of {@link #MBeanServerInvocationHandler new
|
|
* MBeanServerInvocationHandler(connection, objectName)}, and
|
|
* <code>interfaces</code> is an array that has one element if
|
|
* <code>notificationBroadcaster</code> is false and two if it is
|
|
* true. The first element of <code>interfaces</code> is
|
|
* <code>interfaceClass</code> and the second, if present, is
|
|
* <code>NotificationEmitter.class</code>.
|
|
*
|
|
* @param connection the MBean server to forward to.
|
|
* @param objectName the name of the MBean within
|
|
* <code>connection</code> 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 <code>connection</code>. 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 <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.
|
|
*
|
|
* @see JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class, boolean)
|
|
*/
|
|
public static <T> T newProxyInstance(MBeanServerConnection connection,
|
|
ObjectName objectName,
|
|
Class<T> 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<MXBeanProxy> 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<MXBeanProxy>(p));
|
|
}
|
|
return p;
|
|
}
|
|
}
|
|
private static final WeakHashMap<Class<?>, WeakReference<MXBeanProxy>>
|
|
mxbeanProxies = new WeakHashMap<Class<?>, WeakReference<MXBeanProxy>>();
|
|
|
|
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;
|
|
}
|