src/java.management/share/classes/javax/management/MBeanServerInvocationHandler.java
changeset 47216 71c04702a3d5
parent 31701 b4d934543e0a
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     1 /*
       
     2  * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package javax.management;
       
    27 
       
    28 import com.sun.jmx.mbeanserver.MXBeanProxy;
       
    29 
       
    30 import java.lang.ref.WeakReference;
       
    31 import java.lang.reflect.InvocationHandler;
       
    32 import java.lang.reflect.Method;
       
    33 import java.lang.reflect.Proxy;
       
    34 import java.util.Arrays;
       
    35 import java.util.WeakHashMap;
       
    36 
       
    37 /**
       
    38  * <p>{@link InvocationHandler} that forwards methods in an MBean's
       
    39  * management interface through the MBean server to the MBean.</p>
       
    40  *
       
    41  * <p>Given an {@link MBeanServerConnection}, the {@link ObjectName}
       
    42  * of an MBean within that MBean server, and a Java interface
       
    43  * <code>Intf</code> that describes the management interface of the
       
    44  * MBean using the patterns for a Standard MBean or an MXBean, this
       
    45  * class can be used to construct a proxy for the MBean.  The proxy
       
    46  * implements the interface <code>Intf</code> such that all of its
       
    47  * methods are forwarded through the MBean server to the MBean.</p>
       
    48  *
       
    49  * <p>If the {@code InvocationHandler} is for an MXBean, then the parameters of
       
    50  * a method are converted from the type declared in the MXBean
       
    51  * interface into the corresponding mapped type, and the return value
       
    52  * is converted from the mapped type into the declared type.  For
       
    53  * example, with the method<br>
       
    54 
       
    55  * {@code public List<String> reverse(List<String> list);}<br>
       
    56 
       
    57  * and given that the mapped type for {@code List<String>} is {@code
       
    58  * String[]}, a call to {@code proxy.reverse(someList)} will convert
       
    59  * {@code someList} from a {@code List<String>} to a {@code String[]},
       
    60  * call the MBean operation {@code reverse}, then convert the returned
       
    61  * {@code String[]} into a {@code List<String>}.</p>
       
    62  *
       
    63  * <p>The method Object.toString(), Object.hashCode(), or
       
    64  * Object.equals(Object), when invoked on a proxy using this
       
    65  * invocation handler, is forwarded to the MBean server as a method on
       
    66  * the proxied MBean only if it appears in one of the proxy's
       
    67  * interfaces.  For a proxy created with {@link
       
    68  * JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class)
       
    69  * JMX.newMBeanProxy} or {@link
       
    70  * JMX#newMXBeanProxy(MBeanServerConnection, ObjectName, Class)
       
    71  * JMX.newMXBeanProxy}, this means that the method must appear in the
       
    72  * Standard MBean or MXBean interface.  Otherwise these methods have
       
    73  * the following behavior:
       
    74  * <ul>
       
    75  * <li>toString() returns a string representation of the proxy
       
    76  * <li>hashCode() returns a hash code for the proxy such
       
    77  * that two equal proxies have the same hash code
       
    78  * <li>equals(Object)
       
    79  * returns true if and only if the Object argument is of the same
       
    80  * proxy class as this proxy, with an MBeanServerInvocationHandler
       
    81  * that has the same MBeanServerConnection and ObjectName; if one
       
    82  * of the {@code MBeanServerInvocationHandler}s was constructed with
       
    83  * a {@code Class} argument then the other must have been constructed
       
    84  * with the same {@code Class} for {@code equals} to return true.
       
    85  * </ul>
       
    86  *
       
    87  * @since 1.5
       
    88  */
       
    89 public class MBeanServerInvocationHandler implements InvocationHandler {
       
    90     /**
       
    91      * <p>Invocation handler that forwards methods through an MBean
       
    92      * server to a Standard MBean.  This constructor may be called
       
    93      * instead of relying on {@link
       
    94      * JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class)
       
    95      * JMX.newMBeanProxy}, for instance if you need to supply a
       
    96      * different {@link ClassLoader} to {@link Proxy#newProxyInstance
       
    97      * Proxy.newProxyInstance}.</p>
       
    98      *
       
    99      * <p>This constructor is not appropriate for an MXBean.  Use
       
   100      * {@link #MBeanServerInvocationHandler(MBeanServerConnection,
       
   101      * ObjectName, boolean)} for that.  This constructor is equivalent
       
   102      * to {@code new MBeanServerInvocationHandler(connection,
       
   103      * objectName, false)}.</p>
       
   104      *
       
   105      * @param connection the MBean server connection through which all
       
   106      * methods of a proxy using this handler will be forwarded.
       
   107      *
       
   108      * @param objectName the name of the MBean within the MBean server
       
   109      * to which methods will be forwarded.
       
   110      */
       
   111     public MBeanServerInvocationHandler(MBeanServerConnection connection,
       
   112                                         ObjectName objectName) {
       
   113 
       
   114         this(connection, objectName, false);
       
   115     }
       
   116 
       
   117     /**
       
   118      * <p>Invocation handler that can forward methods through an MBean
       
   119      * server to a Standard MBean or MXBean.  This constructor may be called
       
   120      * instead of relying on {@link
       
   121      * JMX#newMXBeanProxy(MBeanServerConnection, ObjectName, Class)
       
   122      * JMX.newMXBeanProxy}, for instance if you need to supply a
       
   123      * different {@link ClassLoader} to {@link Proxy#newProxyInstance
       
   124      * Proxy.newProxyInstance}.</p>
       
   125      *
       
   126      * @param connection the MBean server connection through which all
       
   127      * methods of a proxy using this handler will be forwarded.
       
   128      *
       
   129      * @param objectName the name of the MBean within the MBean server
       
   130      * to which methods will be forwarded.
       
   131      *
       
   132      * @param isMXBean if true, the proxy is for an {@link MXBean}, and
       
   133      * appropriate mappings will be applied to method parameters and return
       
   134      * values.
       
   135      *
       
   136      * @since 1.6
       
   137      */
       
   138     public MBeanServerInvocationHandler(MBeanServerConnection connection,
       
   139                                         ObjectName objectName,
       
   140                                         boolean isMXBean) {
       
   141         if (connection == null) {
       
   142             throw new IllegalArgumentException("Null connection");
       
   143         }
       
   144         if (Proxy.isProxyClass(connection.getClass())) {
       
   145             if (MBeanServerInvocationHandler.class.isAssignableFrom(
       
   146                     Proxy.getInvocationHandler(connection).getClass())) {
       
   147                 throw new IllegalArgumentException("Wrapping MBeanServerInvocationHandler");
       
   148             }
       
   149         }
       
   150         if (objectName == null) {
       
   151             throw new IllegalArgumentException("Null object name");
       
   152         }
       
   153         this.connection = connection;
       
   154         this.objectName = objectName;
       
   155         this.isMXBean = isMXBean;
       
   156     }
       
   157 
       
   158     /**
       
   159      * <p>The MBean server connection through which the methods of
       
   160      * a proxy using this handler are forwarded.</p>
       
   161      *
       
   162      * @return the MBean server connection.
       
   163      *
       
   164      * @since 1.6
       
   165      */
       
   166     public MBeanServerConnection getMBeanServerConnection() {
       
   167         return connection;
       
   168     }
       
   169 
       
   170     /**
       
   171      * <p>The name of the MBean within the MBean server to which methods
       
   172      * are forwarded.
       
   173      *
       
   174      * @return the object name.
       
   175      *
       
   176      * @since 1.6
       
   177      */
       
   178     public ObjectName getObjectName() {
       
   179         return objectName;
       
   180     }
       
   181 
       
   182     /**
       
   183      * <p>If true, the proxy is for an MXBean, and appropriate mappings
       
   184      * are applied to method parameters and return values.
       
   185      *
       
   186      * @return whether the proxy is for an MXBean.
       
   187      *
       
   188      * @since 1.6
       
   189      */
       
   190     public boolean isMXBean() {
       
   191         return isMXBean;
       
   192     }
       
   193 
       
   194     /**
       
   195      * <p>Return a proxy that implements the given interface by
       
   196      * forwarding its methods through the given MBean server to the
       
   197      * named MBean.  As of 1.6, the methods {@link
       
   198      * JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class)} and
       
   199      * {@link JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class,
       
   200      * boolean)} are preferred to this method.</p>
       
   201      *
       
   202      * <p>This method is equivalent to {@link Proxy#newProxyInstance
       
   203      * Proxy.newProxyInstance}<code>(interfaceClass.getClassLoader(),
       
   204      * interfaces, handler)</code>.  Here <code>handler</code> is the
       
   205      * result of {@link #MBeanServerInvocationHandler new
       
   206      * MBeanServerInvocationHandler(connection, objectName)}, and
       
   207      * <code>interfaces</code> is an array that has one element if
       
   208      * <code>notificationBroadcaster</code> is false and two if it is
       
   209      * true.  The first element of <code>interfaces</code> is
       
   210      * <code>interfaceClass</code> and the second, if present, is
       
   211      * <code>NotificationEmitter.class</code>.
       
   212      *
       
   213      * @param connection the MBean server to forward to.
       
   214      * @param objectName the name of the MBean within
       
   215      * <code>connection</code> to forward to.
       
   216      * @param interfaceClass the management interface that the MBean
       
   217      * exports, which will also be implemented by the returned proxy.
       
   218      * @param notificationBroadcaster make the returned proxy
       
   219      * implement {@link NotificationEmitter} by forwarding its methods
       
   220      * via <code>connection</code>. A call to {@link
       
   221      * NotificationBroadcaster#addNotificationListener} on the proxy will
       
   222      * result in a call to {@link
       
   223      * MBeanServerConnection#addNotificationListener(ObjectName,
       
   224      * NotificationListener, NotificationFilter, Object)}, and likewise
       
   225      * for the other methods of {@link NotificationBroadcaster} and {@link
       
   226      * NotificationEmitter}.
       
   227      *
       
   228      * @param <T> allows the compiler to know that if the {@code
       
   229      * interfaceClass} parameter is {@code MyMBean.class}, for example,
       
   230      * then the return type is {@code MyMBean}.
       
   231      *
       
   232      * @return the new proxy instance.
       
   233      *
       
   234      * @see JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class, boolean)
       
   235      */
       
   236     public static <T> T newProxyInstance(MBeanServerConnection connection,
       
   237                                          ObjectName objectName,
       
   238                                          Class<T> interfaceClass,
       
   239                                          boolean notificationBroadcaster) {
       
   240         return JMX.newMBeanProxy(connection, objectName, interfaceClass, notificationBroadcaster);
       
   241     }
       
   242 
       
   243     public Object invoke(Object proxy, Method method, Object[] args)
       
   244             throws Throwable {
       
   245         final Class<?> methodClass = method.getDeclaringClass();
       
   246 
       
   247         if (methodClass.equals(NotificationBroadcaster.class)
       
   248             || methodClass.equals(NotificationEmitter.class))
       
   249             return invokeBroadcasterMethod(proxy, method, args);
       
   250 
       
   251         // local or not: equals, toString, hashCode
       
   252         if (shouldDoLocally(proxy, method))
       
   253             return doLocally(proxy, method, args);
       
   254 
       
   255         try {
       
   256             if (isMXBean()) {
       
   257                 MXBeanProxy p = findMXBeanProxy(methodClass);
       
   258                 return p.invoke(connection, objectName, method, args);
       
   259             } else {
       
   260                 final String methodName = method.getName();
       
   261                 final Class<?>[] paramTypes = method.getParameterTypes();
       
   262                 final Class<?> returnType = method.getReturnType();
       
   263 
       
   264                 /* Inexplicably, InvocationHandler specifies that args is null
       
   265                    when the method takes no arguments rather than a
       
   266                    zero-length array.  */
       
   267                 final int nargs = (args == null) ? 0 : args.length;
       
   268 
       
   269                 if (methodName.startsWith("get")
       
   270                     && methodName.length() > 3
       
   271                     && nargs == 0
       
   272                     && !returnType.equals(Void.TYPE)) {
       
   273                     return connection.getAttribute(objectName,
       
   274                         methodName.substring(3));
       
   275                 }
       
   276 
       
   277                 if (methodName.startsWith("is")
       
   278                     && methodName.length() > 2
       
   279                     && nargs == 0
       
   280                     && (returnType.equals(Boolean.TYPE)
       
   281                     || returnType.equals(Boolean.class))) {
       
   282                     return connection.getAttribute(objectName,
       
   283                         methodName.substring(2));
       
   284                 }
       
   285 
       
   286                 if (methodName.startsWith("set")
       
   287                     && methodName.length() > 3
       
   288                     && nargs == 1
       
   289                     && returnType.equals(Void.TYPE)) {
       
   290                     Attribute attr = new Attribute(methodName.substring(3), args[0]);
       
   291                     connection.setAttribute(objectName, attr);
       
   292                     return null;
       
   293                 }
       
   294 
       
   295                 final String[] signature = new String[paramTypes.length];
       
   296                 for (int i = 0; i < paramTypes.length; i++)
       
   297                     signature[i] = paramTypes[i].getName();
       
   298                 return connection.invoke(objectName, methodName,
       
   299                                          args, signature);
       
   300             }
       
   301         } catch (MBeanException e) {
       
   302             throw e.getTargetException();
       
   303         } catch (RuntimeMBeanException re) {
       
   304             throw re.getTargetException();
       
   305         } catch (RuntimeErrorException rre) {
       
   306             throw rre.getTargetError();
       
   307         }
       
   308         /* The invoke may fail because it can't get to the MBean, with
       
   309            one of the these exceptions declared by
       
   310            MBeanServerConnection.invoke:
       
   311            - RemoteException: can't talk to MBeanServer;
       
   312            - InstanceNotFoundException: objectName is not registered;
       
   313            - ReflectionException: objectName is registered but does not
       
   314              have the method being invoked.
       
   315            In all of these cases, the exception will be wrapped by the
       
   316            proxy mechanism in an UndeclaredThrowableException unless
       
   317            it happens to be declared in the "throws" clause of the
       
   318            method being invoked on the proxy.
       
   319          */
       
   320     }
       
   321 
       
   322     private static MXBeanProxy findMXBeanProxy(Class<?> mxbeanInterface) {
       
   323         synchronized (mxbeanProxies) {
       
   324             WeakReference<MXBeanProxy> proxyRef =
       
   325                     mxbeanProxies.get(mxbeanInterface);
       
   326             MXBeanProxy p = (proxyRef == null) ? null : proxyRef.get();
       
   327             if (p == null) {
       
   328                 try {
       
   329                     p = new MXBeanProxy(mxbeanInterface);
       
   330                 } catch (IllegalArgumentException e) {
       
   331                     String msg = "Cannot make MXBean proxy for " +
       
   332                             mxbeanInterface.getName() + ": " + e.getMessage();
       
   333                     IllegalArgumentException iae =
       
   334                             new IllegalArgumentException(msg, e.getCause());
       
   335                     iae.setStackTrace(e.getStackTrace());
       
   336                     throw iae;
       
   337                 }
       
   338                 mxbeanProxies.put(mxbeanInterface,
       
   339                                   new WeakReference<MXBeanProxy>(p));
       
   340             }
       
   341             return p;
       
   342         }
       
   343     }
       
   344     private static final WeakHashMap<Class<?>, WeakReference<MXBeanProxy>>
       
   345             mxbeanProxies = new WeakHashMap<Class<?>, WeakReference<MXBeanProxy>>();
       
   346 
       
   347     private Object invokeBroadcasterMethod(Object proxy, Method method,
       
   348                                            Object[] args) throws Exception {
       
   349         final String methodName = method.getName();
       
   350         final int nargs = (args == null) ? 0 : args.length;
       
   351 
       
   352         if (methodName.equals("addNotificationListener")) {
       
   353             /* The various throws of IllegalArgumentException here
       
   354                should not happen, since we know what the methods in
       
   355                NotificationBroadcaster and NotificationEmitter
       
   356                are.  */
       
   357             if (nargs != 3) {
       
   358                 final String msg =
       
   359                     "Bad arg count to addNotificationListener: " + nargs;
       
   360                 throw new IllegalArgumentException(msg);
       
   361             }
       
   362             /* Other inconsistencies will produce ClassCastException
       
   363                below.  */
       
   364 
       
   365             NotificationListener listener = (NotificationListener) args[0];
       
   366             NotificationFilter filter = (NotificationFilter) args[1];
       
   367             Object handback = args[2];
       
   368             connection.addNotificationListener(objectName,
       
   369                                                listener,
       
   370                                                filter,
       
   371                                                handback);
       
   372             return null;
       
   373 
       
   374         } else if (methodName.equals("removeNotificationListener")) {
       
   375 
       
   376             /* NullPointerException if method with no args, but that
       
   377                shouldn't happen because removeNL does have args.  */
       
   378             NotificationListener listener = (NotificationListener) args[0];
       
   379 
       
   380             switch (nargs) {
       
   381             case 1:
       
   382                 connection.removeNotificationListener(objectName, listener);
       
   383                 return null;
       
   384 
       
   385             case 3:
       
   386                 NotificationFilter filter = (NotificationFilter) args[1];
       
   387                 Object handback = args[2];
       
   388                 connection.removeNotificationListener(objectName,
       
   389                                                       listener,
       
   390                                                       filter,
       
   391                                                       handback);
       
   392                 return null;
       
   393 
       
   394             default:
       
   395                 final String msg =
       
   396                     "Bad arg count to removeNotificationListener: " + nargs;
       
   397                 throw new IllegalArgumentException(msg);
       
   398             }
       
   399 
       
   400         } else if (methodName.equals("getNotificationInfo")) {
       
   401 
       
   402             if (args != null) {
       
   403                 throw new IllegalArgumentException("getNotificationInfo has " +
       
   404                                                    "args");
       
   405             }
       
   406 
       
   407             MBeanInfo info = connection.getMBeanInfo(objectName);
       
   408             return info.getNotifications();
       
   409 
       
   410         } else {
       
   411             throw new IllegalArgumentException("Bad method name: " +
       
   412                                                methodName);
       
   413         }
       
   414     }
       
   415 
       
   416     private boolean shouldDoLocally(Object proxy, Method method) {
       
   417         final String methodName = method.getName();
       
   418         if ((methodName.equals("hashCode") || methodName.equals("toString"))
       
   419             && method.getParameterTypes().length == 0
       
   420             && isLocal(proxy, method))
       
   421             return true;
       
   422         if (methodName.equals("equals")
       
   423             && Arrays.equals(method.getParameterTypes(),
       
   424                              new Class<?>[] {Object.class})
       
   425             && isLocal(proxy, method))
       
   426             return true;
       
   427         if (methodName.equals("finalize")
       
   428             && method.getParameterTypes().length == 0) {
       
   429             return true;
       
   430         }
       
   431         return false;
       
   432     }
       
   433 
       
   434     private Object doLocally(Object proxy, Method method, Object[] args) {
       
   435         final String methodName = method.getName();
       
   436 
       
   437         if (methodName.equals("equals")) {
       
   438 
       
   439             if (this == args[0]) {
       
   440                 return true;
       
   441             }
       
   442 
       
   443             if (!(args[0] instanceof Proxy)) {
       
   444                 return false;
       
   445             }
       
   446 
       
   447             final InvocationHandler ihandler =
       
   448                 Proxy.getInvocationHandler(args[0]);
       
   449 
       
   450             if (ihandler == null ||
       
   451                 !(ihandler instanceof MBeanServerInvocationHandler)) {
       
   452                 return false;
       
   453             }
       
   454 
       
   455             final MBeanServerInvocationHandler handler =
       
   456                 (MBeanServerInvocationHandler)ihandler;
       
   457 
       
   458             return connection.equals(handler.connection) &&
       
   459                 objectName.equals(handler.objectName) &&
       
   460                 proxy.getClass().equals(args[0].getClass());
       
   461         } else if (methodName.equals("toString")) {
       
   462             return (isMXBean() ? "MX" : "M") + "BeanProxy(" +
       
   463                 connection + "[" + objectName + "])";
       
   464         } else if (methodName.equals("hashCode")) {
       
   465             return objectName.hashCode()+connection.hashCode();
       
   466         } else if (methodName.equals("finalize")) {
       
   467             // ignore the finalizer invocation via proxy
       
   468             return null;
       
   469         }
       
   470 
       
   471         throw new RuntimeException("Unexpected method name: " + methodName);
       
   472     }
       
   473 
       
   474     private static boolean isLocal(Object proxy, Method method) {
       
   475         final Class<?>[] interfaces = proxy.getClass().getInterfaces();
       
   476         if(interfaces == null) {
       
   477             return true;
       
   478         }
       
   479 
       
   480         final String methodName = method.getName();
       
   481         final Class<?>[] params = method.getParameterTypes();
       
   482         for (Class<?> intf : interfaces) {
       
   483             try {
       
   484                 intf.getMethod(methodName, params);
       
   485                 return false; // found method in one of our interfaces
       
   486             } catch (NoSuchMethodException nsme) {
       
   487                 // OK.
       
   488             }
       
   489         }
       
   490 
       
   491         return true;  // did not find in any interface
       
   492     }
       
   493 
       
   494     private final MBeanServerConnection connection;
       
   495     private final ObjectName objectName;
       
   496     private final boolean isMXBean;
       
   497 }