J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 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 | |
| 26 | package com.sun.jmx.mbeanserver; |
| 27 | |
| 28 | |
| 29 | import static com.sun.jmx.mbeanserver.Util.*; |
| 30 | |
| 31 | import java.lang.ref.WeakReference; |
| 32 | import java.lang.reflect.Array; |
| 33 | import java.lang.reflect.Constructor; |
| 34 | import java.lang.reflect.InvocationTargetException; |
| 35 | import java.lang.reflect.Method; |
| 36 | import java.lang.reflect.Type; |
| 37 | import java.util.List; |
| 38 | import java.util.WeakHashMap; |
| 39 | |
| 40 | import javax.management.Descriptor; |
| 41 | import javax.management.ImmutableDescriptor; |
| 42 | import javax.management.InvalidAttributeValueException; |
| 43 | import javax.management.MBeanAttributeInfo; |
| 44 | import javax.management.MBeanConstructorInfo; |
| 45 | import javax.management.MBeanException; |
| 46 | import javax.management.MBeanInfo; |
| 47 | import javax.management.MBeanNotificationInfo; |
| 48 | import javax.management.MBeanOperationInfo; |
| 49 | import javax.management.NotCompliantMBeanException; |
| 50 | import javax.management.NotificationBroadcaster; |
| 51 | import 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 | */ |
| 71 | abstract 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 | } |