001 /*
002 * Copyright 2002-2007 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.management;
027
028 import com.sun.jmx.mbeanserver.MXBeanProxy;
029
030 import java.lang.ref.WeakReference;
031 import java.lang.reflect.InvocationHandler;
032 import java.lang.reflect.Method;
033 import java.lang.reflect.Proxy;
034 import java.util.Arrays;
035 import java.util.WeakHashMap;
036
037 /**
038 * <p>{@link InvocationHandler} that forwards methods in an MBean's
039 * management interface through the MBean server to the MBean.</p>
040 *
041 * <p>Given an {@link MBeanServerConnection}, the {@link ObjectName}
042 * of an MBean within that MBean server, and a Java interface
043 * <code>Intf</code> that describes the management interface of the
044 * MBean using the patterns for a Standard MBean or an MXBean, this
045 * class can be used to construct a proxy for the MBean. The proxy
046 * implements the interface <code>Intf</code> such that all of its
047 * methods are forwarded through the MBean server to the MBean.</p>
048 *
049 * <p>If the {@code InvocationHandler} is for an MXBean, then the parameters of
050 * a method are converted from the type declared in the MXBean
051 * interface into the corresponding mapped type, and the return value
052 * is converted from the mapped type into the declared type. For
053 * example, with the method<br>
054
055 * {@code public List<String> reverse(List<String> list);}<br>
056
057 * and given that the mapped type for {@code List<String>} is {@code
058 * String[]}, a call to {@code proxy.reverse(someList)} will convert
059 * {@code someList} from a {@code List<String>} to a {@code String[]},
060 * call the MBean operation {@code reverse}, then convert the returned
061 * {@code String[]} into a {@code List<String>}.</p>
062 *
063 * <p>The method Object.toString(), Object.hashCode(), or
064 * Object.equals(Object), when invoked on a proxy using this
065 * invocation handler, is forwarded to the MBean server as a method on
066 * the proxied MBean only if it appears in one of the proxy's
067 * interfaces. For a proxy created with {@link
068 * JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class)
069 * JMX.newMBeanProxy} or {@link
070 * JMX#newMXBeanProxy(MBeanServerConnection, ObjectName, Class)
071 * JMX.newMXBeanProxy}, this means that the method must appear in the
072 * Standard MBean or MXBean interface. Otherwise these methods have
073 * the following behavior:
074 * <ul>
075 * <li>toString() returns a string representation of the proxy
076 * <li>hashCode() returns a hash code for the proxy such
077 * that two equal proxies have the same hash code
078 * <li>equals(Object)
079 * returns true if and only if the Object argument is of the same
080 * proxy class as this proxy, with an MBeanServerInvocationHandler
081 * that has the same MBeanServerConnection and ObjectName; if one
082 * of the {@code MBeanServerInvocationHandler}s was constructed with
083 * a {@code Class} argument then the other must have been constructed
084 * with the same {@code Class} for {@code equals} to return true.
085 * </ul>
086 *
087 * @since 1.5
088 */
089 public class MBeanServerInvocationHandler implements InvocationHandler {
090 /**
091 * <p>Invocation handler that forwards methods through an MBean
092 * server to a Standard MBean. This constructor may be called
093 * instead of relying on {@link
094 * JMX#newMBeanProxy(MBeanServerConnection, ObjectName, Class)
095 * JMX.newMBeanProxy}, for instance if you need to supply a
096 * different {@link ClassLoader} to {@link Proxy#newProxyInstance
097 * Proxy.newProxyInstance}.</p>
098 *
099 * <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(
112 MBeanServerConnection connection, 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(
139 MBeanServerConnection connection, 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(
231 MBeanServerConnection connection, ObjectName objectName,
232 Class<T> interfaceClass, boolean notificationBroadcaster) {
233 final InvocationHandler handler = new MBeanServerInvocationHandler(
234 connection, objectName);
235 final Class[] interfaces;
236 if (notificationBroadcaster) {
237 interfaces = new Class[] { interfaceClass,
238 NotificationEmitter.class };
239 } else
240 interfaces = new Class[] { interfaceClass };
241
242 Object proxy = Proxy.newProxyInstance(interfaceClass
243 .getClassLoader(), interfaces, handler);
244 return interfaceClass.cast(proxy);
245 }
246
247 public Object invoke(Object proxy, Method method, Object[] args)
248 throws Throwable {
249 final Class methodClass = method.getDeclaringClass();
250
251 if (methodClass.equals(NotificationBroadcaster.class)
252 || methodClass.equals(NotificationEmitter.class))
253 return invokeBroadcasterMethod(proxy, method, args);
254
255 // local or not: equals, toString, hashCode
256 if (shouldDoLocally(proxy, method))
257 return doLocally(proxy, method, args);
258
259 try {
260 if (isMXBean) {
261 MXBeanProxy p = findMXBeanProxy(methodClass);
262 return p.invoke(connection, objectName, method, args);
263 } else {
264 final String methodName = method.getName();
265 final Class[] paramTypes = method.getParameterTypes();
266 final Class returnType = method.getReturnType();
267
268 /* Inexplicably, InvocationHandler specifies that args is null
269 when the method takes no arguments rather than a
270 zero-length array. */
271 final int nargs = (args == null) ? 0 : args.length;
272
273 if (methodName.startsWith("get")
274 && methodName.length() > 3 && nargs == 0
275 && !returnType.equals(Void.TYPE)) {
276 return connection.getAttribute(objectName,
277 methodName.substring(3));
278 }
279
280 if (methodName.startsWith("is")
281 && methodName.length() > 2
282 && nargs == 0
283 && (returnType.equals(Boolean.TYPE) || returnType
284 .equals(Boolean.class))) {
285 return connection.getAttribute(objectName,
286 methodName.substring(2));
287 }
288
289 if (methodName.startsWith("set")
290 && methodName.length() > 3 && nargs == 1
291 && returnType.equals(Void.TYPE)) {
292 Attribute attr = new Attribute(methodName
293 .substring(3), args[0]);
294 connection.setAttribute(objectName, attr);
295 return null;
296 }
297
298 final String[] signature = new String[paramTypes.length];
299 for (int i = 0; i < paramTypes.length; i++)
300 signature[i] = paramTypes[i].getName();
301 return connection.invoke(objectName, methodName, args,
302 signature);
303 }
304 } catch (MBeanException e) {
305 throw e.getTargetException();
306 } catch (RuntimeMBeanException re) {
307 throw re.getTargetException();
308 } catch (RuntimeErrorException rre) {
309 throw rre.getTargetError();
310 }
311 /* The invoke may fail because it can't get to the MBean, with
312 one of the these exceptions declared by
313 MBeanServerConnection.invoke:
314 - RemoteException: can't talk to MBeanServer;
315 - InstanceNotFoundException: objectName is not registered;
316 - ReflectionException: objectName is registered but does not
317 have the method being invoked.
318 In all of these cases, the exception will be wrapped by the
319 proxy mechanism in an UndeclaredThrowableException unless
320 it happens to be declared in the "throws" clause of the
321 method being invoked on the proxy.
322 */
323 }
324
325 private static MXBeanProxy findMXBeanProxy(Class<?> mxbeanInterface) {
326 synchronized (mxbeanProxies) {
327 WeakReference<MXBeanProxy> proxyRef = mxbeanProxies
328 .get(mxbeanInterface);
329 MXBeanProxy p = (proxyRef == null) ? null : proxyRef.get();
330 if (p == null) {
331 p = new MXBeanProxy(mxbeanInterface);
332 mxbeanProxies.put(mxbeanInterface,
333 new WeakReference<MXBeanProxy>(p));
334 }
335 return p;
336 }
337 }
338
339 private static final WeakHashMap<Class<?>, WeakReference<MXBeanProxy>> mxbeanProxies = new WeakHashMap<Class<?>, WeakReference<MXBeanProxy>>();
340
341 private Object invokeBroadcasterMethod(Object proxy, Method method,
342 Object[] args) throws Exception {
343 final String methodName = method.getName();
344 final int nargs = (args == null) ? 0 : args.length;
345
346 if (methodName.equals("addNotificationListener")) {
347 /* The various throws of IllegalArgumentException here
348 should not happen, since we know what the methods in
349 NotificationBroadcaster and NotificationEmitter
350 are. */
351 if (nargs != 3) {
352 final String msg = "Bad arg count to addNotificationListener: "
353 + nargs;
354 throw new IllegalArgumentException(msg);
355 }
356 /* Other inconsistencies will produce ClassCastException
357 below. */
358
359 NotificationListener listener = (NotificationListener) args[0];
360 NotificationFilter filter = (NotificationFilter) args[1];
361 Object handback = args[2];
362 connection.addNotificationListener(objectName, listener,
363 filter, handback);
364 return null;
365
366 } else if (methodName.equals("removeNotificationListener")) {
367
368 /* NullPointerException if method with no args, but that
369 shouldn't happen because removeNL does have args. */
370 NotificationListener listener = (NotificationListener) args[0];
371
372 switch (nargs) {
373 case 1:
374 connection.removeNotificationListener(objectName,
375 listener);
376 return null;
377
378 case 3:
379 NotificationFilter filter = (NotificationFilter) args[1];
380 Object handback = args[2];
381 connection.removeNotificationListener(objectName,
382 listener, filter, handback);
383 return null;
384
385 default:
386 final String msg = "Bad arg count to removeNotificationListener: "
387 + nargs;
388 throw new IllegalArgumentException(msg);
389 }
390
391 } else if (methodName.equals("getNotificationInfo")) {
392
393 if (args != null) {
394 throw new IllegalArgumentException(
395 "getNotificationInfo has " + "args");
396 }
397
398 MBeanInfo info = connection.getMBeanInfo(objectName);
399 return info.getNotifications();
400
401 } else {
402 throw new IllegalArgumentException("Bad method name: "
403 + methodName);
404 }
405 }
406
407 private boolean shouldDoLocally(Object proxy, Method method) {
408 final String methodName = method.getName();
409 if ((methodName.equals("hashCode") || methodName
410 .equals("toString"))
411 && method.getParameterTypes().length == 0
412 && isLocal(proxy, method))
413 return true;
414 if (methodName.equals("equals")
415 && Arrays.equals(method.getParameterTypes(),
416 new Class[] { Object.class })
417 && isLocal(proxy, method))
418 return true;
419 return false;
420 }
421
422 private Object doLocally(Object proxy, Method method, Object[] args) {
423 final String methodName = method.getName();
424
425 if (methodName.equals("equals")) {
426
427 if (this == args[0]) {
428 return true;
429 }
430
431 if (!(args[0] instanceof Proxy)) {
432 return false;
433 }
434
435 final InvocationHandler ihandler = Proxy
436 .getInvocationHandler(args[0]);
437
438 if (ihandler == null
439 || !(ihandler instanceof MBeanServerInvocationHandler)) {
440 return false;
441 }
442
443 final MBeanServerInvocationHandler handler = (MBeanServerInvocationHandler) ihandler;
444
445 return connection.equals(handler.connection)
446 && objectName.equals(handler.objectName)
447 && proxy.getClass().equals(args[0].getClass());
448 } else if (methodName.equals("toString")) {
449 return (isMXBean ? "MX" : "M") + "BeanProxy(" + connection
450 + "[" + objectName + "])";
451 } else if (methodName.equals("hashCode")) {
452 return objectName.hashCode() + connection.hashCode();
453 }
454
455 throw new RuntimeException("Unexpected method name: "
456 + methodName);
457 }
458
459 private static boolean isLocal(Object proxy, Method method) {
460 final Class<?>[] interfaces = proxy.getClass().getInterfaces();
461 if (interfaces == null) {
462 return true;
463 }
464
465 final String methodName = method.getName();
466 final Class<?>[] params = method.getParameterTypes();
467 for (Class<?> intf : interfaces) {
468 try {
469 intf.getMethod(methodName, params);
470 return false; // found method in one of our interfaces
471 } catch (NoSuchMethodException nsme) {
472 // OK.
473 }
474 }
475
476 return true; // did not find in any interface
477 }
478
479 private final MBeanServerConnection connection;
480 private final ObjectName objectName;
481 private final boolean isMXBean;
482 }
|