| /* |
| * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package com.sun.jmx.mbeanserver; |
| |
| |
| import static com.sun.jmx.mbeanserver.Util.*; |
| |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Type; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.WeakHashMap; |
| |
| import javax.management.Descriptor; |
| import javax.management.ImmutableDescriptor; |
| import javax.management.IntrospectionException; |
| import javax.management.InvalidAttributeValueException; |
| import javax.management.MBeanAttributeInfo; |
| import javax.management.MBeanConstructorInfo; |
| import javax.management.MBeanException; |
| import javax.management.MBeanInfo; |
| import javax.management.MBeanNotificationInfo; |
| import javax.management.MBeanOperationInfo; |
| import javax.management.NotCompliantMBeanException; |
| import javax.management.NotificationBroadcaster; |
| import javax.management.ReflectionException; |
| import sun.reflect.misc.ReflectUtil; |
| |
| /** |
| * An introspector for MBeans of a certain type. There is one instance |
| * of this class for Standard MBeans, and one for every MXBeanMappingFactory; |
| * these two cases correspond to the two concrete subclasses of this abstract |
| * class. |
| * |
| * @param <M> the representation of methods for this kind of MBean: |
| * Method for Standard MBeans, ConvertingMethod for MXBeans. |
| * |
| * @since 1.6 |
| */ |
| /* |
| * Using a type parameter <M> allows us to deal with the fact that |
| * Method and ConvertingMethod have no useful common ancestor, on |
| * which we could call getName, getGenericReturnType, etc. A simpler approach |
| * would be to wrap every Method in an object that does have a common |
| * ancestor with ConvertingMethod. But that would mean an extra object |
| * for every Method in every Standard MBean interface. |
| */ |
| abstract class MBeanIntrospector<M> { |
| static final class PerInterfaceMap<M> |
| extends WeakHashMap<Class<?>, WeakReference<PerInterface<M>>> {} |
| |
| /** The map from interface to PerInterface for this type of MBean. */ |
| abstract PerInterfaceMap<M> getPerInterfaceMap(); |
| /** |
| * The map from concrete implementation class and interface to |
| * MBeanInfo for this type of MBean. |
| */ |
| abstract MBeanInfoMap getMBeanInfoMap(); |
| |
| /** Make an interface analyzer for this type of MBean. */ |
| abstract MBeanAnalyzer<M> getAnalyzer(Class<?> mbeanInterface) |
| throws NotCompliantMBeanException; |
| |
| /** True if MBeans with this kind of introspector are MXBeans. */ |
| abstract boolean isMXBean(); |
| |
| /** Find the M corresponding to the given Method. */ |
| abstract M mFrom(Method m); |
| |
| /** Get the name of this method. */ |
| abstract String getName(M m); |
| |
| /** |
| * Get the return type of this method. This is the return type |
| * of a method in a Java interface, so for MXBeans it is the |
| * declared Java type, not the mapped Open Type. |
| */ |
| abstract Type getGenericReturnType(M m); |
| |
| /** |
| * Get the parameter types of this method in the Java interface |
| * it came from. |
| */ |
| abstract Type[] getGenericParameterTypes(M m); |
| |
| /** |
| * Get the signature of this method as a caller would have to supply |
| * it in MBeanServer.invoke. For MXBeans, the named types will be |
| * the mapped Open Types for the parameters. |
| */ |
| abstract String[] getSignature(M m); |
| |
| /** |
| * Check that this method is valid. For example, a method in an |
| * MXBean interface is not valid if one of its parameters cannot be |
| * mapped to an Open Type. |
| */ |
| abstract void checkMethod(M m); |
| |
| /** |
| * Invoke the method with the given target and arguments. |
| * |
| * @param cookie Additional information about the target. For an |
| * MXBean, this is the MXBeanLookup associated with the MXBean. |
| */ |
| /* |
| * It would be cleaner if the type of the cookie were a |
| * type parameter to this class, but that would involve a lot of |
| * messy type parameter propagation just to avoid a couple of casts. |
| */ |
| abstract Object invokeM2(M m, Object target, Object[] args, Object cookie) |
| throws InvocationTargetException, IllegalAccessException, |
| MBeanException; |
| |
| /** |
| * Test whether the given value is valid for the given parameter of this |
| * M. |
| */ |
| abstract boolean validParameter(M m, Object value, int paramNo, |
| Object cookie); |
| |
| /** |
| * Construct an MBeanAttributeInfo for the given attribute based on the |
| * given getter and setter. One but not both of the getter and setter |
| * may be null. |
| */ |
| abstract MBeanAttributeInfo getMBeanAttributeInfo(String attributeName, |
| M getter, M setter); |
| /** |
| * Construct an MBeanOperationInfo for the given operation based on |
| * the M it was derived from. |
| */ |
| abstract MBeanOperationInfo getMBeanOperationInfo(String operationName, |
| M operation); |
| |
| /** |
| * Get a Descriptor containing fields that MBeans of this kind will |
| * always have. For example, MXBeans will always have "mxbean=true". |
| */ |
| abstract Descriptor getBasicMBeanDescriptor(); |
| |
| /** |
| * Get a Descriptor containing additional fields beyond the ones |
| * from getBasicMBeanDescriptor that MBeans whose concrete class |
| * is resourceClass will always have. |
| */ |
| abstract Descriptor getMBeanDescriptor(Class<?> resourceClass); |
| |
| /** |
| * Get the methods to be analyzed to build the MBean interface. |
| */ |
| final List<Method> getMethods(final Class<?> mbeanType) { |
| ReflectUtil.checkPackageAccess(mbeanType); |
| return Arrays.asList(mbeanType.getMethods()); |
| } |
| |
| final PerInterface<M> getPerInterface(Class<?> mbeanInterface) |
| throws NotCompliantMBeanException { |
| PerInterfaceMap<M> map = getPerInterfaceMap(); |
| synchronized (map) { |
| WeakReference<PerInterface<M>> wr = map.get(mbeanInterface); |
| PerInterface<M> pi = (wr == null) ? null : wr.get(); |
| if (pi == null) { |
| try { |
| MBeanAnalyzer<M> analyzer = getAnalyzer(mbeanInterface); |
| MBeanInfo mbeanInfo = |
| makeInterfaceMBeanInfo(mbeanInterface, analyzer); |
| pi = new PerInterface<M>(mbeanInterface, this, analyzer, |
| mbeanInfo); |
| wr = new WeakReference<PerInterface<M>>(pi); |
| map.put(mbeanInterface, wr); |
| } catch (Exception x) { |
| throw Introspector.throwException(mbeanInterface,x); |
| } |
| } |
| return pi; |
| } |
| } |
| |
| /** |
| * Make the MBeanInfo skeleton for the given MBean interface using |
| * the given analyzer. This will never be the MBeanInfo of any real |
| * MBean (because the getClassName() must be a concrete class), but |
| * its MBeanAttributeInfo[] and MBeanOperationInfo[] can be inserted |
| * into such an MBeanInfo, and its Descriptor can be the basis for |
| * the MBeanInfo's Descriptor. |
| */ |
| private MBeanInfo makeInterfaceMBeanInfo(Class<?> mbeanInterface, |
| MBeanAnalyzer<M> analyzer) { |
| final MBeanInfoMaker maker = new MBeanInfoMaker(); |
| analyzer.visit(maker); |
| final String description = |
| "Information on the management interface of the MBean"; |
| return maker.makeMBeanInfo(mbeanInterface, description); |
| } |
| |
| /** True if the given getter and setter are consistent. */ |
| final boolean consistent(M getter, M setter) { |
| return (getter == null || setter == null || |
| getGenericReturnType(getter).equals(getGenericParameterTypes(setter)[0])); |
| } |
| |
| /** |
| * Invoke the given M on the given target with the given args and cookie. |
| * Wrap exceptions appropriately. |
| */ |
| final Object invokeM(M m, Object target, Object[] args, Object cookie) |
| throws MBeanException, ReflectionException { |
| try { |
| return invokeM2(m, target, args, cookie); |
| } catch (InvocationTargetException e) { |
| unwrapInvocationTargetException(e); |
| throw new RuntimeException(e); // not reached |
| } catch (IllegalAccessException e) { |
| throw new ReflectionException(e, e.toString()); |
| } |
| /* We do not catch and wrap RuntimeException or Error, |
| * because we're in a DynamicMBean, so the logic for DynamicMBeans |
| * will do the wrapping. |
| */ |
| } |
| |
| /** |
| * Invoke the given setter on the given target with the given argument |
| * and cookie. Wrap exceptions appropriately. |
| */ |
| /* If the value is of the wrong type for the method we are about to |
| * invoke, we are supposed to throw an InvalidAttributeValueException. |
| * Rather than making the check always, we invoke the method, then |
| * if it throws an exception we check the type to see if that was |
| * what caused the exception. The assumption is that an exception |
| * from an invalid type will arise before any user method is ever |
| * called (either in reflection or in OpenConverter). |
| */ |
| final void invokeSetter(String name, M setter, Object target, Object arg, |
| Object cookie) |
| throws MBeanException, ReflectionException, |
| InvalidAttributeValueException { |
| try { |
| invokeM2(setter, target, new Object[] {arg}, cookie); |
| } catch (IllegalAccessException e) { |
| throw new ReflectionException(e, e.toString()); |
| } catch (RuntimeException e) { |
| maybeInvalidParameter(name, setter, arg, cookie); |
| throw e; |
| } catch (InvocationTargetException e) { |
| maybeInvalidParameter(name, setter, arg, cookie); |
| unwrapInvocationTargetException(e); |
| } |
| } |
| |
| private void maybeInvalidParameter(String name, M setter, Object arg, |
| Object cookie) |
| throws InvalidAttributeValueException { |
| if (!validParameter(setter, arg, 0, cookie)) { |
| final String msg = |
| "Invalid value for attribute " + name + ": " + arg; |
| throw new InvalidAttributeValueException(msg); |
| } |
| } |
| |
| static boolean isValidParameter(Method m, Object value, int paramNo) { |
| Class<?> c = m.getParameterTypes()[paramNo]; |
| try { |
| // Following is expensive but we only call this method to determine |
| // if an exception is due to an incompatible parameter type. |
| // Plain old c.isInstance doesn't work for primitive types. |
| Object a = Array.newInstance(c, 1); |
| Array.set(a, 0, value); |
| return true; |
| } catch (IllegalArgumentException e) { |
| return false; |
| } |
| } |
| |
| private static void |
| unwrapInvocationTargetException(InvocationTargetException e) |
| throws MBeanException { |
| Throwable t = e.getCause(); |
| if (t instanceof RuntimeException) |
| throw (RuntimeException) t; |
| else if (t instanceof Error) |
| throw (Error) t; |
| else |
| throw new MBeanException((Exception) t, |
| (t == null ? null : t.toString())); |
| } |
| |
| /** A visitor that constructs the per-interface MBeanInfo. */ |
| private class MBeanInfoMaker |
| implements MBeanAnalyzer.MBeanVisitor<M> { |
| |
| public void visitAttribute(String attributeName, |
| M getter, |
| M setter) { |
| MBeanAttributeInfo mbai = |
| getMBeanAttributeInfo(attributeName, getter, setter); |
| |
| attrs.add(mbai); |
| } |
| |
| public void visitOperation(String operationName, |
| M operation) { |
| MBeanOperationInfo mboi = |
| getMBeanOperationInfo(operationName, operation); |
| |
| ops.add(mboi); |
| } |
| |
| /** Make an MBeanInfo based on the attributes and operations |
| * found in the interface. */ |
| MBeanInfo makeMBeanInfo(Class<?> mbeanInterface, |
| String description) { |
| final MBeanAttributeInfo[] attrArray = |
| attrs.toArray(new MBeanAttributeInfo[0]); |
| final MBeanOperationInfo[] opArray = |
| ops.toArray(new MBeanOperationInfo[0]); |
| final String interfaceClassName = |
| "interfaceClassName=" + mbeanInterface.getName(); |
| final Descriptor classNameDescriptor = |
| new ImmutableDescriptor(interfaceClassName); |
| final Descriptor mbeanDescriptor = getBasicMBeanDescriptor(); |
| final Descriptor annotatedDescriptor = |
| Introspector.descriptorForElement(mbeanInterface); |
| final Descriptor descriptor = |
| DescriptorCache.getInstance().union( |
| classNameDescriptor, |
| mbeanDescriptor, |
| annotatedDescriptor); |
| |
| return new MBeanInfo(mbeanInterface.getName(), |
| description, |
| attrArray, |
| null, |
| opArray, |
| null, |
| descriptor); |
| } |
| |
| private final List<MBeanAttributeInfo> attrs = newList(); |
| private final List<MBeanOperationInfo> ops = newList(); |
| } |
| |
| /* |
| * Looking up the MBeanInfo for a given base class (implementation class) |
| * is complicated by the fact that we may use the same base class with |
| * several different explicit MBean interfaces via the |
| * javax.management.StandardMBean class. It is further complicated |
| * by the fact that we have to be careful not to retain a strong reference |
| * to any Class object for fear we would prevent a ClassLoader from being |
| * garbage-collected. So we have a first lookup from the base class |
| * to a map for each interface that base class might specify giving |
| * the MBeanInfo constructed for that base class and interface. |
| */ |
| static class MBeanInfoMap |
| extends WeakHashMap<Class<?>, WeakHashMap<Class<?>, MBeanInfo>> { |
| } |
| |
| /** |
| * Return the MBeanInfo for the given resource, based on the given |
| * per-interface data. |
| */ |
| final MBeanInfo getMBeanInfo(Object resource, PerInterface<M> perInterface) { |
| MBeanInfo mbi = |
| getClassMBeanInfo(resource.getClass(), perInterface); |
| MBeanNotificationInfo[] notifs = findNotifications(resource); |
| if (notifs == null || notifs.length == 0) |
| return mbi; |
| else { |
| return new MBeanInfo(mbi.getClassName(), |
| mbi.getDescription(), |
| mbi.getAttributes(), |
| mbi.getConstructors(), |
| mbi.getOperations(), |
| notifs, |
| mbi.getDescriptor()); |
| } |
| } |
| |
| /** |
| * Return the basic MBeanInfo for resources of the given class and |
| * per-interface data. This MBeanInfo might not be the final MBeanInfo |
| * for instances of the class, because if the class is a |
| * NotificationBroadcaster then each instance gets to decide what |
| * MBeanNotificationInfo[] to put in its own MBeanInfo. |
| */ |
| final MBeanInfo getClassMBeanInfo(Class<?> resourceClass, |
| PerInterface<M> perInterface) { |
| MBeanInfoMap map = getMBeanInfoMap(); |
| synchronized (map) { |
| WeakHashMap<Class<?>, MBeanInfo> intfMap = map.get(resourceClass); |
| if (intfMap == null) { |
| intfMap = new WeakHashMap<Class<?>, MBeanInfo>(); |
| map.put(resourceClass, intfMap); |
| } |
| Class<?> intfClass = perInterface.getMBeanInterface(); |
| MBeanInfo mbi = intfMap.get(intfClass); |
| if (mbi == null) { |
| MBeanInfo imbi = perInterface.getMBeanInfo(); |
| Descriptor descriptor = |
| ImmutableDescriptor.union(imbi.getDescriptor(), |
| getMBeanDescriptor(resourceClass)); |
| mbi = new MBeanInfo(resourceClass.getName(), |
| imbi.getDescription(), |
| imbi.getAttributes(), |
| findConstructors(resourceClass), |
| imbi.getOperations(), |
| (MBeanNotificationInfo[]) null, |
| descriptor); |
| intfMap.put(intfClass, mbi); |
| } |
| return mbi; |
| } |
| } |
| |
| static MBeanNotificationInfo[] findNotifications(Object moi) { |
| if (!(moi instanceof NotificationBroadcaster)) |
| return null; |
| MBeanNotificationInfo[] mbn = |
| ((NotificationBroadcaster) moi).getNotificationInfo(); |
| if (mbn == null) |
| return null; |
| MBeanNotificationInfo[] result = |
| new MBeanNotificationInfo[mbn.length]; |
| for (int i = 0; i < mbn.length; i++) { |
| MBeanNotificationInfo ni = mbn[i]; |
| if (ni.getClass() != MBeanNotificationInfo.class) |
| ni = (MBeanNotificationInfo) ni.clone(); |
| result[i] = ni; |
| } |
| return result; |
| } |
| |
| private static MBeanConstructorInfo[] findConstructors(Class<?> c) { |
| Constructor<?>[] cons = c.getConstructors(); |
| MBeanConstructorInfo[] mbc = new MBeanConstructorInfo[cons.length]; |
| for (int i = 0; i < cons.length; i++) { |
| final String descr = "Public constructor of the MBean"; |
| mbc[i] = new MBeanConstructorInfo(descr, cons[i]); |
| } |
| return mbc; |
| } |
| |
| } |