| /* | |
| * Copyright (c) 2002, 2022, 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; | |
| /** | |
| * <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<>(p)); | |
| } | |
| return p; | |
| } | |
| } | |
| private static final WeakHashMap<Class<?>, WeakReference<MXBeanProxy>> | |
| mxbeanProxies = new WeakHashMap<>(); | |
| 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; | |
| } |