|
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 } |