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