blob: fb5d4bd25e048c1a43100551a7620ecbf094fb89 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
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
26package javax.management;
27
28import com.sun.jmx.mbeanserver.MXBeanProxy;
29
30import java.lang.ref.WeakReference;
31import java.lang.reflect.InvocationHandler;
32import java.lang.reflect.Method;
33import java.lang.reflect.Proxy;
34import java.util.Arrays;
35import 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 */
89public 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}