/* * 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; /** * 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. */ List<Method> getMethods(final Class<?> 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; } }