| /* |
| * 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; |
| |
| /** |
| * <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; |
| } |