Save This Page
Home » openjdk-7 » javax » management » [javadoc | source]
    1   /*
    2    * Copyright 2002-2007 Sun Microsystems, Inc.  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.  Sun designates this
    8    * particular file as subject to the "Classpath" exception as provided
    9    * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   22    * CA 95054 USA or visit www.sun.com if you need additional information or
   23    * have any 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 (objectName == null) {
  145               throw new IllegalArgumentException("Null object name");
  146           }
  147           this.connection = connection;
  148           this.objectName = objectName;
  149           this.isMXBean = isMXBean;
  150       }
  151   
  152       /**
  153        * <p>The MBean server connection through which the methods of
  154        * a proxy using this handler are forwarded.</p>
  155        *
  156        * @return the MBean server connection.
  157        *
  158        * @since 1.6
  159        */
  160       public MBeanServerConnection getMBeanServerConnection() {
  161           return connection;
  162       }
  163   
  164       /**
  165        * <p>The name of the MBean within the MBean server to which methods
  166        * are forwarded.
  167        *
  168        * @return the object name.
  169        *
  170        * @since 1.6
  171        */
  172       public ObjectName getObjectName() {
  173           return objectName;
  174       }
  175   
  176       /**
  177        * <p>If true, the proxy is for an MXBean, and appropriate mappings
  178        * are applied to method parameters and return values.
  179        *
  180        * @return whether the proxy is for an MXBean.
  181        *
  182        * @since 1.6
  183        */
  184       public boolean isMXBean() {
  185           return isMXBean;
  186       }
  187   
  188       /**
  189        * <p>Return a proxy that implements the given interface by
  190        * forwarding its methods through the given MBean server to the
  191        * named MBean.  As of 1.6, the methods {@link
  192        * JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class)} and
  193        * {@link JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class,
  194        * boolean)} are preferred to this method.</p>
  195        *
  196        * <p>This method is equivalent to {@link Proxy#newProxyInstance
  197        * Proxy.newProxyInstance}<code>(interfaceClass.getClassLoader(),
  198        * interfaces, handler)</code>.  Here <code>handler</code> is the
  199        * result of {@link #MBeanServerInvocationHandler new
  200        * MBeanServerInvocationHandler(connection, objectName)}, and
  201        * <code>interfaces</code> is an array that has one element if
  202        * <code>notificationBroadcaster</code> is false and two if it is
  203        * true.  The first element of <code>interfaces</code> is
  204        * <code>interfaceClass</code> and the second, if present, is
  205        * <code>NotificationEmitter.class</code>.
  206        *
  207        * @param connection the MBean server to forward to.
  208        * @param objectName the name of the MBean within
  209        * <code>connection</code> to forward to.
  210        * @param interfaceClass the management interface that the MBean
  211        * exports, which will also be implemented by the returned proxy.
  212        * @param notificationBroadcaster make the returned proxy
  213        * implement {@link NotificationEmitter} by forwarding its methods
  214        * via <code>connection</code>. A call to {@link
  215        * NotificationBroadcaster#addNotificationListener} on the proxy will
  216        * result in a call to {@link
  217        * MBeanServerConnection#addNotificationListener(ObjectName,
  218        * NotificationListener, NotificationFilter, Object)}, and likewise
  219        * for the other methods of {@link NotificationBroadcaster} and {@link
  220        * NotificationEmitter}.
  221        *
  222        * @param <T> allows the compiler to know that if the {@code
  223        * interfaceClass} parameter is {@code MyMBean.class}, for example,
  224        * then the return type is {@code MyMBean}.
  225        *
  226        * @return the new proxy instance.
  227        *
  228        * @see JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class)
  229        */
  230       public static <T> T newProxyInstance(MBeanServerConnection connection,
  231                                            ObjectName objectName,
  232                                            Class<T> interfaceClass,
  233                                            boolean notificationBroadcaster) {
  234           final InvocationHandler handler =
  235               new MBeanServerInvocationHandler(connection, objectName);
  236           final Class[] interfaces;
  237           if (notificationBroadcaster) {
  238               interfaces =
  239                   new Class[] {interfaceClass, NotificationEmitter.class};
  240           } else
  241               interfaces = new Class[] {interfaceClass};
  242   
  243           Object proxy =
  244               Proxy.newProxyInstance(interfaceClass.getClassLoader(),
  245                                      interfaces,
  246                                      handler);
  247           return interfaceClass.cast(proxy);
  248       }
  249   
  250       public Object invoke(Object proxy, Method method, Object[] args)
  251               throws Throwable {
  252           final Class methodClass = method.getDeclaringClass();
  253   
  254           if (methodClass.equals(NotificationBroadcaster.class)
  255               || methodClass.equals(NotificationEmitter.class))
  256               return invokeBroadcasterMethod(proxy, method, args);
  257   
  258           // local or not: equals, toString, hashCode
  259           if (shouldDoLocally(proxy, method))
  260               return doLocally(proxy, method, args);
  261   
  262           try {
  263               if (isMXBean) {
  264                   MXBeanProxy p = findMXBeanProxy(methodClass);
  265                   return p.invoke(connection, objectName, method, args);
  266               } else {
  267                   final String methodName = method.getName();
  268                   final Class[] paramTypes = method.getParameterTypes();
  269                   final Class returnType = method.getReturnType();
  270   
  271                   /* Inexplicably, InvocationHandler specifies that args is null
  272                      when the method takes no arguments rather than a
  273                      zero-length array.  */
  274                   final int nargs = (args == null) ? 0 : args.length;
  275   
  276                   if (methodName.startsWith("get")
  277                       && methodName.length() > 3
  278                       && nargs == 0
  279                       && !returnType.equals(Void.TYPE)) {
  280                       return connection.getAttribute(objectName,
  281                           methodName.substring(3));
  282                   }
  283   
  284                   if (methodName.startsWith("is")
  285                       && methodName.length() > 2
  286                       && nargs == 0
  287                       && (returnType.equals(Boolean.TYPE)
  288                       || returnType.equals(Boolean.class))) {
  289                       return connection.getAttribute(objectName,
  290                           methodName.substring(2));
  291                   }
  292   
  293                   if (methodName.startsWith("set")
  294                       && methodName.length() > 3
  295                       && nargs == 1
  296                       && returnType.equals(Void.TYPE)) {
  297                       Attribute attr = new Attribute(methodName.substring(3), args[0]);
  298                       connection.setAttribute(objectName, attr);
  299                       return null;
  300                   }
  301   
  302                   final String[] signature = new String[paramTypes.length];
  303                   for (int i = 0; i < paramTypes.length; i++)
  304                       signature[i] = paramTypes[i].getName();
  305                   return connection.invoke(objectName, methodName,
  306                                            args, signature);
  307               }
  308           } catch (MBeanException e) {
  309               throw e.getTargetException();
  310           } catch (RuntimeMBeanException re) {
  311               throw re.getTargetException();
  312           } catch (RuntimeErrorException rre) {
  313               throw rre.getTargetError();
  314           }
  315           /* The invoke may fail because it can't get to the MBean, with
  316              one of the these exceptions declared by
  317              MBeanServerConnection.invoke:
  318              - RemoteException: can't talk to MBeanServer;
  319              - InstanceNotFoundException: objectName is not registered;
  320              - ReflectionException: objectName is registered but does not
  321                have the method being invoked.
  322              In all of these cases, the exception will be wrapped by the
  323              proxy mechanism in an UndeclaredThrowableException unless
  324              it happens to be declared in the "throws" clause of the
  325              method being invoked on the proxy.
  326            */
  327       }
  328   
  329       private static MXBeanProxy findMXBeanProxy(Class<?> mxbeanInterface) {
  330           synchronized (mxbeanProxies) {
  331               WeakReference<MXBeanProxy> proxyRef =
  332                       mxbeanProxies.get(mxbeanInterface);
  333               MXBeanProxy p = (proxyRef == null) ? null : proxyRef.get();
  334               if (p == null) {
  335                   p = new MXBeanProxy(mxbeanInterface);
  336                   mxbeanProxies.put(mxbeanInterface,
  337                                     new WeakReference<MXBeanProxy>(p));
  338               }
  339               return p;
  340           }
  341       }
  342       private static final WeakHashMap<Class<?>, WeakReference<MXBeanProxy>>
  343               mxbeanProxies = new WeakHashMap<Class<?>, WeakReference<MXBeanProxy>>();
  344   
  345       private Object invokeBroadcasterMethod(Object proxy, Method method,
  346                                              Object[] args) throws Exception {
  347           final String methodName = method.getName();
  348           final int nargs = (args == null) ? 0 : args.length;
  349   
  350           if (methodName.equals("addNotificationListener")) {
  351               /* The various throws of IllegalArgumentException here
  352                  should not happen, since we know what the methods in
  353                  NotificationBroadcaster and NotificationEmitter
  354                  are.  */
  355               if (nargs != 3) {
  356                   final String msg =
  357                       "Bad arg count to addNotificationListener: " + nargs;
  358                   throw new IllegalArgumentException(msg);
  359               }
  360               /* Other inconsistencies will produce ClassCastException
  361                  below.  */
  362   
  363               NotificationListener listener = (NotificationListener) args[0];
  364               NotificationFilter filter = (NotificationFilter) args[1];
  365               Object handback = args[2];
  366               connection.addNotificationListener(objectName,
  367                                                  listener,
  368                                                  filter,
  369                                                  handback);
  370               return null;
  371   
  372           } else if (methodName.equals("removeNotificationListener")) {
  373   
  374               /* NullPointerException if method with no args, but that
  375                  shouldn't happen because removeNL does have args.  */
  376               NotificationListener listener = (NotificationListener) args[0];
  377   
  378               switch (nargs) {
  379               case 1:
  380                   connection.removeNotificationListener(objectName, listener);
  381                   return null;
  382   
  383               case 3:
  384                   NotificationFilter filter = (NotificationFilter) args[1];
  385                   Object handback = args[2];
  386                   connection.removeNotificationListener(objectName,
  387                                                         listener,
  388                                                         filter,
  389                                                         handback);
  390                   return null;
  391   
  392               default:
  393                   final String msg =
  394                       "Bad arg count to removeNotificationListener: " + nargs;
  395                   throw new IllegalArgumentException(msg);
  396               }
  397   
  398           } else if (methodName.equals("getNotificationInfo")) {
  399   
  400               if (args != null) {
  401                   throw new IllegalArgumentException("getNotificationInfo has " +
  402                                                      "args");
  403               }
  404   
  405               MBeanInfo info = connection.getMBeanInfo(objectName);
  406               return info.getNotifications();
  407   
  408           } else {
  409               throw new IllegalArgumentException("Bad method name: " +
  410                                                  methodName);
  411           }
  412       }
  413   
  414       private boolean shouldDoLocally(Object proxy, Method method) {
  415           final String methodName = method.getName();
  416           if ((methodName.equals("hashCode") || methodName.equals("toString"))
  417               && method.getParameterTypes().length == 0
  418               && isLocal(proxy, method))
  419               return true;
  420           if (methodName.equals("equals")
  421               && Arrays.equals(method.getParameterTypes(),
  422                                new Class[] {Object.class})
  423               && isLocal(proxy, method))
  424               return true;
  425           return false;
  426       }
  427   
  428       private Object doLocally(Object proxy, Method method, Object[] args) {
  429           final String methodName = method.getName();
  430   
  431           if (methodName.equals("equals")) {
  432   
  433               if (this == args[0]) {
  434                   return true;
  435               }
  436   
  437               if (!(args[0] instanceof Proxy)) {
  438                   return false;
  439               }
  440   
  441               final InvocationHandler ihandler =
  442                   Proxy.getInvocationHandler(args[0]);
  443   
  444               if (ihandler == null ||
  445                   !(ihandler instanceof MBeanServerInvocationHandler)) {
  446                   return false;
  447               }
  448   
  449               final MBeanServerInvocationHandler handler =
  450                   (MBeanServerInvocationHandler)ihandler;
  451   
  452               return connection.equals(handler.connection) &&
  453                   objectName.equals(handler.objectName) &&
  454                   proxy.getClass().equals(args[0].getClass());
  455           } else if (methodName.equals("toString")) {
  456               return (isMXBean ? "MX" : "M") + "BeanProxy(" +
  457                   connection + "[" + objectName + "])";
  458           } else if (methodName.equals("hashCode")) {
  459               return objectName.hashCode()+connection.hashCode();
  460           }
  461   
  462           throw new RuntimeException("Unexpected method name: " + methodName);
  463       }
  464   
  465       private static boolean isLocal(Object proxy, Method method) {
  466           final Class<?>[] interfaces = proxy.getClass().getInterfaces();
  467           if(interfaces == null) {
  468               return true;
  469           }
  470   
  471           final String methodName = method.getName();
  472           final Class<?>[] params = method.getParameterTypes();
  473           for (Class<?> intf : interfaces) {
  474               try {
  475                   intf.getMethod(methodName, params);
  476                   return false; // found method in one of our interfaces
  477               } catch (NoSuchMethodException nsme) {
  478                   // OK.
  479               }
  480           }
  481   
  482           return true;  // did not find in any interface
  483       }
  484   
  485       private final MBeanServerConnection connection;
  486       private final ObjectName objectName;
  487       private final boolean isMXBean;
  488   }

Save This Page
Home » openjdk-7 » javax » management » [javadoc | source]