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 */ MBeanServerInvocationHandler(MBeanServerConnection connection, ObjectName objectName)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 */ MBeanServerInvocationHandler(MBeanServerConnection connection, ObjectName objectName, boolean isMXBean)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 */ getMBeanServerConnection()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 */ getObjectName()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 */ isMXBean()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 */ newProxyInstance(MBeanServerConnection connection, ObjectName objectName, Class<T> interfaceClass, boolean notificationBroadcaster)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 invoke(Object proxy, Method method, Object[] args)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 findMXBeanProxy(Class<?> mxbeanInterface)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 invokeBroadcasterMethod(Object proxy, Method method, Object[] args)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 shouldDoLocally(Object proxy, Method method)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 doLocally(Object proxy, Method method, Object[] args)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 isLocal(Object proxy, Method method)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 } 498