blob: c191005d6219cd1b91cd8d0db6cd53819be1642d [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2005-2006 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 com.sun.jmx.mbeanserver;
27
28
29import static com.sun.jmx.mbeanserver.Util.*;
30
31import java.lang.ref.WeakReference;
32import java.lang.reflect.Array;
33import java.lang.reflect.Constructor;
34import java.lang.reflect.InvocationTargetException;
35import java.lang.reflect.Method;
36import java.lang.reflect.Type;
37import java.util.List;
38import java.util.WeakHashMap;
39
40import javax.management.Descriptor;
41import javax.management.ImmutableDescriptor;
42import javax.management.InvalidAttributeValueException;
43import javax.management.MBeanAttributeInfo;
44import javax.management.MBeanConstructorInfo;
45import javax.management.MBeanException;
46import javax.management.MBeanInfo;
47import javax.management.MBeanNotificationInfo;
48import javax.management.MBeanOperationInfo;
49import javax.management.NotCompliantMBeanException;
50import javax.management.NotificationBroadcaster;
51import javax.management.ReflectionException;
52
53/**
54 * An introspector for MBeans of a certain type. There is one instance
55 * of this class for Standard MBeans and one for MXBeans, characterized
56 * by the two concrete subclasses of this abstract class.
57 *
58 * @param <M> the representation of methods for this kind of MBean:
59 * Method for Standard MBeans, ConvertingMethod for MXBeans.
60 *
61 * @since 1.6
62 */
63/*
64 * Using a type parameter <M> allows us to deal with the fact that
65 * Method and ConvertingMethod have no useful common ancestor, on
66 * which we could call getName, getGenericReturnType, etc. A simpler approach
67 * would be to wrap every Method in an object that does have a common
68 * ancestor with ConvertingMethod. But that would mean an extra object
69 * for every Method in every Standard MBean interface.
70 */
71abstract class MBeanIntrospector<M> {
72 static final class PerInterfaceMap<M>
73 extends WeakHashMap<Class<?>, WeakReference<PerInterface<M>>> {}
74
75 /** The map from interface to PerInterface for this type of MBean. */
76 abstract PerInterfaceMap<M> getPerInterfaceMap();
77 /**
78 * The map from concrete implementation class and interface to
79 * MBeanInfo for this type of MBean.
80 */
81 abstract MBeanInfoMap getMBeanInfoMap();
82
83 /** Make an interface analyzer for this type of MBean. */
84 abstract MBeanAnalyzer<M> getAnalyzer(Class<?> mbeanInterface)
85 throws NotCompliantMBeanException;
86
87 /** True if MBeans with this kind of introspector are MXBeans. */
88 abstract boolean isMXBean();
89
90 /** Find the M corresponding to the given Method. */
91 abstract M mFrom(Method m);
92
93 /** Get the name of this method. */
94 abstract String getName(M m);
95
96 /**
97 * Get the return type of this method. This is the return type
98 * of a method in a Java interface, so for MXBeans it is the
99 * declared Java type, not the mapped Open Type.
100 */
101 abstract Type getGenericReturnType(M m);
102
103 /**
104 * Get the parameter types of this method in the Java interface
105 * it came from.
106 */
107 abstract Type[] getGenericParameterTypes(M m);
108
109 /**
110 * Get the signature of this method as a caller would have to supply
111 * it in MBeanServer.invoke. For MXBeans, the named types will be
112 * the mapped Open Types for the parameters.
113 */
114 abstract String[] getSignature(M m);
115
116 /**
117 * Check that this method is valid. For example, a method in an
118 * MXBean interface is not valid if one of its parameters cannot be
119 * mapped to an Open Type.
120 */
121 abstract void checkMethod(M m) throws IllegalArgumentException;
122
123 /**
124 * Invoke the method with the given target and arguments.
125 *
126 * @param cookie Additional information about the target. For an
127 * MXBean, this is the MXBeanLookup associated with the MXBean.
128 */
129 /*
130 * It would be cleaner if the type of the cookie were a
131 * type parameter to this class, but that would involve a lot of
132 * messy type parameter propagation just to avoid a couple of casts.
133 */
134 abstract Object invokeM2(M m, Object target, Object[] args, Object cookie)
135 throws InvocationTargetException, IllegalAccessException,
136 MBeanException;
137
138 /**
139 * Test whether the given value is valid for the given parameter of this
140 * M.
141 */
142 abstract boolean validParameter(M m, Object value, int paramNo,
143 Object cookie);
144
145 /**
146 * Construct an MBeanAttributeInfo for the given attribute based on the
147 * given getter and setter. One but not both of the getter and setter
148 * may be null.
149 */
150 abstract MBeanAttributeInfo getMBeanAttributeInfo(String attributeName,
151 M getter, M setter);
152 /**
153 * Construct an MBeanOperationInfo for the given operation based on
154 * the M it was derived from.
155 */
156 abstract MBeanOperationInfo getMBeanOperationInfo(String operationName,
157 M operation);
158
159 /**
160 * Get a Descriptor containing fields that MBeans of this kind will
161 * always have. For example, MXBeans will always have "mxbean=true".
162 */
163 abstract Descriptor getBasicMBeanDescriptor();
164
165 /**
166 * Get a Descriptor containing additional fields beyond the ones
167 * from getBasicMBeanDescriptor that MBeans whose concrete class
168 * is resourceClass will always have.
169 */
170 abstract Descriptor getMBeanDescriptor(Class<?> resourceClass);
171
172
173 final PerInterface<M> getPerInterface(Class<?> mbeanInterface)
174 throws NotCompliantMBeanException {
175 PerInterfaceMap<M> map = getPerInterfaceMap();
176 synchronized (map) {
177 WeakReference<PerInterface<M>> wr = map.get(mbeanInterface);
178 PerInterface<M> pi = (wr == null) ? null : wr.get();
179 if (pi == null) {
180 try {
181 MBeanAnalyzer<M> analyzer = getAnalyzer(mbeanInterface);
182 MBeanInfo mbeanInfo =
183 makeInterfaceMBeanInfo(mbeanInterface, analyzer);
184 pi = new PerInterface<M>(mbeanInterface, this, analyzer,
185 mbeanInfo);
186 wr = new WeakReference<PerInterface<M>>(pi);
187 map.put(mbeanInterface, wr);
188 } catch (Exception x) {
189 throw Introspector.throwException(mbeanInterface,x);
190 }
191 }
192 return pi;
193 }
194 }
195
196 /**
197 * Make the MBeanInfo skeleton for the given MBean interface using
198 * the given analyzer. This will never be the MBeanInfo of any real
199 * MBean (because the getClassName() must be a concrete class), but
200 * its MBeanAttributeInfo[] and MBeanOperationInfo[] can be inserted
201 * into such an MBeanInfo, and its Descriptor can be the basis for
202 * the MBeanInfo's Descriptor.
203 */
204 private MBeanInfo makeInterfaceMBeanInfo(Class<?> mbeanInterface,
205 MBeanAnalyzer<M> analyzer) {
206 final MBeanInfoMaker maker = new MBeanInfoMaker();
207 analyzer.visit(maker);
208 final String description =
209 "Information on the management interface of the MBean";
210 return maker.makeMBeanInfo(mbeanInterface, description);
211 }
212
213 /** True if the given getter and setter are consistent. */
214 final boolean consistent(M getter, M setter) {
215 return (getter == null || setter == null ||
216 getGenericReturnType(getter).equals(getGenericParameterTypes(setter)[0]));
217 }
218
219 /**
220 * Invoke the given M on the given target with the given args and cookie.
221 * Wrap exceptions appropriately.
222 */
223 final Object invokeM(M m, Object target, Object[] args, Object cookie)
224 throws MBeanException, ReflectionException {
225 try {
226 return invokeM2(m, target, args, cookie);
227 } catch (InvocationTargetException e) {
228 unwrapInvocationTargetException(e);
229 throw new RuntimeException(e); // not reached
230 } catch (IllegalAccessException e) {
231 throw new ReflectionException(e, e.toString());
232 }
233 /* We do not catch and wrap RuntimeException or Error,
234 * because we're in a DynamicMBean, so the logic for DynamicMBeans
235 * will do the wrapping.
236 */
237 }
238
239 /**
240 * Invoke the given setter on the given target with the given argument
241 * and cookie. Wrap exceptions appropriately.
242 */
243 /* If the value is of the wrong type for the method we are about to
244 * invoke, we are supposed to throw an InvalidAttributeValueException.
245 * Rather than making the check always, we invoke the method, then
246 * if it throws an exception we check the type to see if that was
247 * what caused the exception. The assumption is that an exception
248 * from an invalid type will arise before any user method is ever
249 * called (either in reflection or in OpenConverter).
250 */
251 final void invokeSetter(String name, M setter, Object target, Object arg,
252 Object cookie)
253 throws MBeanException, ReflectionException,
254 InvalidAttributeValueException {
255 try {
256 invokeM2(setter, target, new Object[] {arg}, cookie);
257 } catch (IllegalAccessException e) {
258 throw new ReflectionException(e, e.toString());
259 } catch (RuntimeException e) {
260 maybeInvalidParameter(name, setter, arg, cookie);
261 throw e;
262 } catch (InvocationTargetException e) {
263 maybeInvalidParameter(name, setter, arg, cookie);
264 unwrapInvocationTargetException(e);
265 }
266 }
267
268 private void maybeInvalidParameter(String name, M setter, Object arg,
269 Object cookie)
270 throws InvalidAttributeValueException {
271 if (!validParameter(setter, arg, 0, cookie)) {
272 final String msg =
273 "Invalid value for attribute " + name + ": " + arg;
274 throw new InvalidAttributeValueException(msg);
275 }
276 }
277
278 static boolean isValidParameter(Method m, Object value, int paramNo) {
279 Class<?> c = m.getParameterTypes()[paramNo];
280 try {
281 // Following is expensive but we only call this method to determine
282 // if an exception is due to an incompatible parameter type.
283 // Plain old c.isInstance doesn't work for primitive types.
284 Object a = Array.newInstance(c, 1);
285 Array.set(a, 0, value);
286 return true;
287 } catch (IllegalArgumentException e) {
288 return false;
289 }
290 }
291
292 private static void
293 unwrapInvocationTargetException(InvocationTargetException e)
294 throws MBeanException {
295 Throwable t = e.getCause();
296 if (t instanceof RuntimeException)
297 throw (RuntimeException) t;
298 else if (t instanceof Error)
299 throw (Error) t;
300 else
301 throw new MBeanException((Exception) t,
302 (t == null ? null : t.toString()));
303 }
304
305 /** A visitor that constructs the per-interface MBeanInfo. */
306 private class MBeanInfoMaker implements MBeanAnalyzer.MBeanVisitor<M> {
307
308 public void visitAttribute(String attributeName,
309 M getter,
310 M setter) {
311 MBeanAttributeInfo mbai =
312 getMBeanAttributeInfo(attributeName, getter, setter);
313
314 attrs.add(mbai);
315 }
316
317 public void visitOperation(String operationName,
318 M operation) {
319 MBeanOperationInfo mboi =
320 getMBeanOperationInfo(operationName, operation);
321
322 ops.add(mboi);
323 }
324
325 /** Make an MBeanInfo based on the attributes and operations
326 * found in the interface. */
327 MBeanInfo makeMBeanInfo(Class<?> mbeanInterface,
328 String description) {
329 final MBeanAttributeInfo[] attrArray =
330 attrs.toArray(new MBeanAttributeInfo[0]);
331 final MBeanOperationInfo[] opArray =
332 ops.toArray(new MBeanOperationInfo[0]);
333 final String interfaceClassName =
334 "interfaceClassName=" + mbeanInterface.getName();
335 final Descriptor interfDescriptor =
336 new ImmutableDescriptor(interfaceClassName);
337 final Descriptor mbeanDescriptor = getBasicMBeanDescriptor();
338 final Descriptor annotatedDescriptor =
339 Introspector.descriptorForElement(mbeanInterface);
340 final Descriptor descriptor =
341 DescriptorCache.getInstance().union(interfDescriptor,
342 mbeanDescriptor,
343 annotatedDescriptor);
344
345 return new MBeanInfo(mbeanInterface.getName(),
346 description,
347 attrArray,
348 null,
349 opArray,
350 null,
351 descriptor);
352 }
353
354 private final List<MBeanAttributeInfo> attrs = newList();
355 private final List<MBeanOperationInfo> ops = newList();
356 }
357
358 /*
359 * Looking up the MBeanInfo for a given base class (implementation class)
360 * is complicated by the fact that we may use the same base class with
361 * several different explicit MBean interfaces via the
362 * javax.management.StandardMBean class. It is further complicated
363 * by the fact that we have to be careful not to retain a strong reference
364 * to any Class object for fear we would prevent a ClassLoader from being
365 * garbage-collected. So we have a first lookup from the base class
366 * to a map for each interface that base class might specify giving
367 * the MBeanInfo constructed for that base class and interface.
368 */
369 static class MBeanInfoMap
370 extends WeakHashMap<Class<?>, WeakHashMap<Class<?>, MBeanInfo>> {
371 }
372
373 /**
374 * Return the MBeanInfo for the given resource, based on the given
375 * per-interface data.
376 */
377 final MBeanInfo getMBeanInfo(Object resource, PerInterface<M> perInterface) {
378 MBeanInfo mbi =
379 getClassMBeanInfo(resource.getClass(), perInterface);
380 MBeanNotificationInfo[] notifs = findNotifications(resource);
381 if (notifs == null || notifs.length == 0)
382 return mbi;
383 else {
384 return new MBeanInfo(mbi.getClassName(),
385 mbi.getDescription(),
386 mbi.getAttributes(),
387 mbi.getConstructors(),
388 mbi.getOperations(),
389 notifs,
390 mbi.getDescriptor());
391 }
392 }
393
394 /**
395 * Return the basic MBeanInfo for resources of the given class and
396 * per-interface data. This MBeanInfo might not be the final MBeanInfo
397 * for instances of the class, because if the class is a
398 * NotificationBroadcaster then each instance gets to decide what
399 * MBeanNotificationInfo[] to put in its own MBeanInfo.
400 */
401 final MBeanInfo getClassMBeanInfo(Class<?> resourceClass,
402 PerInterface<M> perInterface) {
403 MBeanInfoMap map = getMBeanInfoMap();
404 synchronized (map) {
405 WeakHashMap<Class<?>, MBeanInfo> intfMap = map.get(resourceClass);
406 if (intfMap == null) {
407 intfMap = new WeakHashMap<Class<?>, MBeanInfo>();
408 map.put(resourceClass, intfMap);
409 }
410 Class<?> intfClass = perInterface.getMBeanInterface();
411 MBeanInfo mbi = intfMap.get(intfClass);
412 if (mbi == null) {
413 MBeanInfo imbi = perInterface.getMBeanInfo();
414 Descriptor descriptor =
415 ImmutableDescriptor.union(imbi.getDescriptor(),
416 getMBeanDescriptor(resourceClass));
417 mbi = new MBeanInfo(resourceClass.getName(),
418 imbi.getDescription(),
419 imbi.getAttributes(),
420 findConstructors(resourceClass),
421 imbi.getOperations(),
422 (MBeanNotificationInfo[]) null,
423 descriptor);
424 intfMap.put(intfClass, mbi);
425 }
426 return mbi;
427 }
428 }
429
430 static MBeanNotificationInfo[] findNotifications(Object moi) {
431 if (!(moi instanceof NotificationBroadcaster))
432 return null;
433 MBeanNotificationInfo[] mbn =
434 ((NotificationBroadcaster) moi).getNotificationInfo();
435 if (mbn == null)
436 return null;
437 MBeanNotificationInfo[] result =
438 new MBeanNotificationInfo[mbn.length];
439 for (int i = 0; i < mbn.length; i++) {
440 MBeanNotificationInfo ni = mbn[i];
441 if (ni.getClass() != MBeanNotificationInfo.class)
442 ni = (MBeanNotificationInfo) ni.clone();
443 result[i] = ni;
444 }
445 return result;
446 }
447
448 private static MBeanConstructorInfo[] findConstructors(Class<?> c) {
449 Constructor[] cons = c.getConstructors();
450 MBeanConstructorInfo[] mbc = new MBeanConstructorInfo[cons.length];
451 for (int i = 0; i < cons.length; i++) {
452 final String descr = "Public constructor of the MBean";
453 mbc[i] = new MBeanConstructorInfo(descr, cons[i]);
454 }
455 return mbc;
456 }
457
458}