import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.MBeanServerConnection; import javax.management.MBeanServerInvocationHandler; import javax.management.ObjectName; /** * A Factory class for generating MBean proxies */ public class ProxyFactory { /** * obtain a SnapshotProxy for a given MBean * @param connection MBeanServerConnection * @param objectName ObjectName of the MBean * @param interfaceClass MBean's interface class * @return a SnapshotProxy for the MBean */ public Object getSnapshotProxy(MBeanServerConnection connection, ObjectName objectName, Class interfaceClass) { final InvocationHandler handler = new SnapshotProxy(connection, objectName, interfaceClass); final Class[] interfaces = new Class[] {interfaceClass}; return Proxy.newProxyInstance(interfaceClass.getClassLoader(), interfaces, handler); } /** * Implementation class of the SnapshotProxy */ private static class SnapshotProxy extends MBeanServerInvocationHandler { private MBeanServerConnection connection; private ObjectName objectName; private Class interfaceClass; private MapattributeMap = null; /** * Constructor * @param connection MBeanServerConnection * @param objectName ObjectName of the MBean * @param interfaceClass Class defining MBean's interface */ private SnapshotProxy(MBeanServerConnection connection, ObjectName objectName, Class interfaceClass) { super(connection, objectName); this.connection = connection; this.objectName = objectName; this.interfaceClass = interfaceClass; } /* Javadoc inherited from MBeanServerInvocationHandler */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { final String methodName = method.getName(); final Class[] paramTypes = method.getParameterTypes(); final Class returnType = method.getReturnType(); /* Inexplicably, InvocationHandler specifies that args is null when the method takes no arguments rather than a zero-length array. */ final int nargs = (args == null) ? 0 : args.length; String attributeName = decodeAttributeName(methodName, nargs, returnType); /* We only implement the returning of attribute values */ if (attributeName == null) { throw new RuntimeException("Proxy does not implement method "+method.getName()); } /* If we've never been called before, fetch all the attributes */ if (attributeMap == null) { fillAttributeMap(); } if (attributeMap == null || !attributeMap.containsKey(attributeName)) { // If we were unable to snapshot the attribute due // to some kind of error, fallback on default proxy return super.invoke(proxy, method, args); } return attributeMap.get(attributeName); } /** * Fill the internal map of attribute name to attribute value, done * as a single snapshot using a call to getAttributes */ private void fillAttributeMap() { String[] attributeNames = getAttributeNames(interfaceClass); try { Map myMap = new HashMap(); AttributeList attributeList = connection.getAttributes(objectName, attributeNames); for (Iterator i = attributeList.iterator(); i.hasNext(); ) { Attribute attr = i.next(); myMap.put(attr.getName(), attr); } attributeMap = myMap; } catch (Exception e) { // leave map uninitialized in case of error } } /** * Return the names of all the attributes defined by this interface and * any interfaces this interface has extended. * @param interfaceClass The MBean's interface class * @return A String[] containing all the attribute names. */ private String[] getAttributeNames(Class interfaceClass) { Set nameSet = new HashSet(); addAttributeNames(nameSet, interfaceClass); // recurse over all other interfaces implemented by this interface Class[] interfaces = interfaceClass.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { addAttributeNames(nameSet, interfaces[i]); } return (String[])nameSet.toArray(new String[] {}); } /** * Add the names of all the attributes defined in this interface to * a given Set * @param nameSet All discovered names are added into this set. * @param interfaceClass class to introspect */ private void addAttributeNames(Set nameSet, Class interfaceClass) { Method[] methods = interfaceClass.getDeclaredMethods(); for (int i = 0 ; i < methods.length; i++) { // Only consider public attributes if (!Modifier.isPublic(methods[i].getModifiers())) continue; String methodName = methods[i].getName(); final Class[] paramTypes = methods[i].getParameterTypes(); final Class returnType = methods[i].getReturnType(); String attrName = decodeAttributeName(methodName, paramTypes.length, returnType); if (attrName != null) { nameSet.add(attrName); } } return; } /** * Maps from a method name to the attribute name, or returns null if * method is not an attribute "getter". * @param methodName name of method * @param argCount number of arguments the method takes * @param returnType the return type of the method * @return the JMX MBean's attribute name, if this was a read-only * accessor to an attribute, or null if not. */ private String decodeAttributeName(String methodName, int argCount, Class returnType) { String attributeName = null; if (methodName.startsWith("get") && methodName.length() > 3 && argCount == 0 && !returnType.equals(Void.TYPE)) { attributeName = methodName.substring(3); } else if (methodName.startsWith("is") && methodName.length() > 2 && argCount == 0 && returnType.equals(Boolean.TYPE)) { attributeName = methodName.substring(2); } return attributeName; } } }