/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source 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, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.jmx; import javax.management.*; import javax.management.openmbean.ArrayType; import javax.management.openmbean.OpenType; import javax.management.openmbean.SimpleType; import java.lang.annotation.Annotation; import java.lang.ref.SoftReference; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import com.caucho.util.L10N; /** * Resin implementation of StandardMBean. */ public class IntrospectionMBean implements DynamicMBean { private static final L10N L = new L10N(IntrospectionMBean.class); private static final Logger log = Logger.getLogger(IntrospectionMBean.class.getName()); private static final Class[] NULL_ARG = new Class[0]; private static final Class _descriptionAnn; private static final Class _nameAnn; private static final WeakHashMap<Class,SoftReference<MBeanInfo>> _cachedInfo = new WeakHashMap<Class,SoftReference<MBeanInfo>>(); private final Object _impl; private final Class _mbeanInterface; private final boolean _isLowercaseAttributeNames; private final MBeanInfo _mbeanInfo; private final HashMap<String,OpenModelMethod> _attrGetMap = new HashMap<String,OpenModelMethod>(); /** * Makes a DynamicMBean. */ public IntrospectionMBean(Object impl, Class mbeanInterface) throws NotCompliantMBeanException { this(impl, mbeanInterface, false); } /** * Makes a DynamicMBean. * * @param isLowercaseAttributeNames true if attributes should have first * letter lowercased */ public IntrospectionMBean(Object impl, Class mbeanInterface, boolean isLowercaseAttributeNames) throws NotCompliantMBeanException { if (impl == null) throw new NullPointerException(); _mbeanInterface = mbeanInterface; _isLowercaseAttributeNames = isLowercaseAttributeNames; _mbeanInfo = introspect(impl, mbeanInterface, isLowercaseAttributeNames); _impl = impl; } /** * Returns the implementation. */ public Object getImplementation() { return _impl; } /** * Returns an attribute value. */ public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { try { OpenModelMethod method = getGetMethod(attribute); if (method != null) return method.invoke(_impl, (Object []) null); else throw new AttributeNotFoundException(L.l("'{0}' is an unknown attribute in '{1}'", attribute, _mbeanInterface.getName())); } catch (IllegalAccessException e) { throw new MBeanException(e); } catch (InvocationTargetException e) { if (e.getCause() instanceof Exception) throw new ReflectionException((Exception) e.getCause()); else throw (Error) e.getCause(); } catch (Throwable e) { throw new RuntimeException(e); } } /** * Sets an attribute value. */ public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { try { Method method = getSetMethod(attribute.getName(), attribute.getValue()); if (method != null) method.invoke(_impl, new Object[] { attribute.getValue() }); else throw new AttributeNotFoundException(attribute.getName()); } catch (IllegalAccessException e) { throw new MBeanException(e); } catch (InvocationTargetException e) { throw new MBeanException(e); } catch (Throwable e) { throw new RuntimeException(e.toString()); } } /** * Returns matching attribute values. */ public AttributeList getAttributes(String []attributes) { AttributeList list = new AttributeList(); for (int i = 0; i < attributes.length; i++) { try { OpenModelMethod method = getGetMethod(attributes[i]); if (method != null) { Object value = method.invoke(_impl, (Object []) null); list.add(new Attribute(attributes[i], value)); } } catch (Throwable e) { log.log(Level.WARNING, e.toString(), e); } } return list; } /** * Sets attribute values. */ public AttributeList setAttributes(AttributeList attributes) { AttributeList list = new AttributeList(); for (int i = 0; i < attributes.size(); i++) { try { Attribute attr = (Attribute) attributes.get(i); Method method = getSetMethod(attr.getName(), attr.getValue()); if (method != null) { method.invoke(_impl, new Object[] { attr.getValue() }); list.add(new Attribute(attr.getName(), attr.getValue())); } } catch (Throwable e) { log.log(Level.WARNING, e.toString(), e); } } return list; } /** * Returns the set method matching the name. */ private OpenModelMethod getGetMethod(String name) { OpenModelMethod method; synchronized (_attrGetMap) { method = _attrGetMap.get(name); } if (method != null) return method; method = createGetMethod(name); if (method != null) { synchronized (_attrGetMap) { _attrGetMap.put(name, method); } } return method; } /** * Returns the get or is method matching the name. */ private OpenModelMethod createGetMethod(String name) { String methodName; if (_isLowercaseAttributeNames) { StringBuilder builder = new StringBuilder(name); builder.setCharAt(0, Character.toUpperCase(builder.charAt(0))); methodName = builder.toString(); } else methodName = name; String getName = "get" + methodName; String isName = "is" + methodName; Method []methods = _mbeanInterface.getMethods(); for (int i = 0; i < methods.length; i++) { if (! methods[i].getName().equals(getName) && ! methods[i].getName().equals(isName)) continue; Class []args = methods[i].getParameterTypes(); if (args.length == 0 && ! methods[i].getReturnType().equals(void.class)) { Class retType = methods[i].getReturnType(); return new OpenModelMethod(methods[i], createUnmarshall(retType)); } } return null; } /** v * Returns the open mbean unmarshaller for the given return type. */ private Unmarshall createUnmarshall(Class cl) { Unmarshall mbean = getMBeanObjectName(cl); if (mbean != null) return mbean; if (cl.isArray()) { Class componentType = cl.getComponentType(); mbean = getMBeanObjectName(componentType); if (mbean != null) return new UnmarshallArray(ObjectName.class, mbean); } return Unmarshall.IDENTITY; } private Unmarshall getMBeanObjectName(Class cl) { try { Method method = cl.getMethod("getObjectName"); if (method != null && ObjectName.class.equals(method.getReturnType())) return new UnmarshallMBean(method); } catch (NoSuchMethodException e) { } catch (Exception e) { log.log(Level.FINER, e.toString(), e); } return null; } /** * Returns the set method matching the name. */ private Method getSetMethod(String name, Object value) { String methodName; if (_isLowercaseAttributeNames) { StringBuilder builder = new StringBuilder(name); builder.setCharAt(0, Character.toUpperCase(builder.charAt(0))); methodName = builder.toString(); } else methodName = name; String setName = "set" + methodName; Method []methods = _mbeanInterface.getMethods(); for (int i = 0; i < methods.length; i++) { if (! methods[i].getName().equals(setName)) continue; Class []args = methods[i].getParameterTypes(); if (args.length != 1) continue; /* if (value != null && ! args[0].isAssignableFrom(value.getClass())) continue; */ return methods[i]; } return null; } /** * Invokes a method on the bean. */ public Object invoke(String actionName, Object []params, String []signature) throws MBeanException, ReflectionException { try { Method []methods = _mbeanInterface.getMethods(); int length = 0; if (signature != null) length = signature.length; if (params != null) length = params.length; for (int i = 0; i < methods.length; i++) { if (! methods[i].getName().equals(actionName)) continue; Class []args = methods[i].getParameterTypes(); if (args.length != length) continue; boolean isMatch = true; for (int j = length - 1; j >= 0; j--) { if (signature != null && ! args[j].getName().equals(signature[j])) isMatch = false; } if (isMatch) { return methods[i].invoke(_impl, params); } } if (actionName.equals("hashCode") && (signature == null || signature.length == 0)) return _impl.hashCode(); else if (actionName.equals("toString") && (signature == null || signature.length == 0)) return _impl.toString(); else return null; } catch (IllegalAccessException e) { throw new MBeanException(e); } catch (InvocationTargetException e) { if (e.getCause() instanceof Exception) throw new ReflectionException((Exception) e.getCause()); else throw (Error) e.getCause(); } } /** * Returns the introspection information for the MBean. */ public MBeanInfo getMBeanInfo() { return _mbeanInfo; } static MBeanInfo introspect(Object obj, Class cl, boolean isLowercaseAttributeNames) throws NotCompliantMBeanException { try { SoftReference<MBeanInfo> infoRef = _cachedInfo.get(cl); MBeanInfo info = null; if (infoRef != null && (info = infoRef.get()) != null) return info; String className = cl.getName(); HashMap<String,MBeanAttributeInfo> attributes = new HashMap<String,MBeanAttributeInfo>(); ArrayList<MBeanConstructorInfo> constructors = new ArrayList<MBeanConstructorInfo>(); ArrayList<MBeanOperationInfo> operations = new ArrayList<MBeanOperationInfo>(); Method []methods = cl.getMethods(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; if (method.getDeclaringClass() == Object.class) continue; if (Modifier.isStatic(method.getModifiers())) continue; String methodName = method.getName(); Class []args = method.getParameterTypes(); Class retType = method.getReturnType(); if (methodName.startsWith("get") && args.length == 0 && ! retType.equals(void.class)) { Method getter = method; String name = methodName.substring(3); Method setter = getSetter(methods, name, retType); String attributeName; if (isLowercaseAttributeNames) { StringBuilder builder = new StringBuilder(name); builder.setCharAt(0, Character.toLowerCase(builder.charAt(0))); attributeName = builder.toString(); } else attributeName = name; Class type = method.getReturnType(); MBeanAttributeInfo attr; attr = new MBeanAttributeInfo(attributeName, getDescription(method), getter, setter); /* Descriptor descriptor = attr.getDescriptor(); if (descriptor != null) { Object openType = getOpenType(type); if (openType != null) descriptor.setField("openType", openType); descriptor.setField("originalType", getTypeName(type)); attr.setDescriptor(descriptor); } */ if (attributes.get(attributeName) == null) attributes.put(attributeName, attr); } else if (methodName.startsWith("is") && args.length == 0 && (retType.equals(boolean.class) || retType.equals(Boolean.class))) { Method getter = method; String name = methodName.substring(2); Method setter = getSetter(methods, name, retType); String attributeName; if (isLowercaseAttributeNames) { StringBuilder builder = new StringBuilder(name); builder.setCharAt(0, Character.toLowerCase(builder.charAt(0))); attributeName = builder.toString(); } else attributeName = name; if (attributes.get(attributeName) == null) { attributes.put(attributeName, new MBeanAttributeInfo(attributeName, getDescription(method), getter, setter)); } } else if (methodName.startsWith("set") && args.length == 1) { Method setter = method; String name = methodName.substring(3); Method getter = getGetter(methods, name, args[0]); if (getter == null) { String attributeName; if (isLowercaseAttributeNames) { StringBuilder builder = new StringBuilder(name); builder.setCharAt(0, Character.toLowerCase(builder.charAt(0))); attributeName = builder.toString(); } else attributeName = name; if (attributes.get(attributeName) == null) { attributes.put(attributeName, new MBeanAttributeInfo(attributeName, getDescription(method), null, setter)); } } } else { operations.add(new MBeanOperationInfo(getName(method), getDescription(method), getSignature(method), method.getReturnType().getName(), MBeanOperationInfo.UNKNOWN)); } } ArrayList<MBeanNotificationInfo> notifications = new ArrayList<MBeanNotificationInfo>(); if (obj instanceof NotificationBroadcaster) { NotificationBroadcaster broadcaster; broadcaster = (NotificationBroadcaster) obj; MBeanNotificationInfo[] notifs = broadcaster.getNotificationInfo(); if (notifs != null) { for (int i = 0; i < notifs.length; i++) { MBeanNotificationInfo notif = notifs[i]; notifications.add((MBeanNotificationInfo) notifs[i].clone()); } } } Collections.sort(notifications, MBEAN_FEATURE_INFO_COMPARATOR); MBeanAttributeInfo []attrArray = new MBeanAttributeInfo[attributes.size()]; attributes.values().toArray(attrArray); Arrays.sort(attrArray, MBEAN_FEATURE_INFO_COMPARATOR); MBeanConstructorInfo []conArray = new MBeanConstructorInfo[constructors.size()]; constructors.toArray(conArray); MBeanOperationInfo []opArray = new MBeanOperationInfo[operations.size()]; operations.toArray(opArray); Arrays.sort(opArray, MBEAN_FEATURE_INFO_COMPARATOR); MBeanNotificationInfo []notifArray = new MBeanNotificationInfo[notifications.size()]; notifications.toArray(notifArray); Arrays.sort(notifArray, MBEAN_FEATURE_INFO_COMPARATOR); MBeanInfo modelInfo; modelInfo = new MBeanInfo(cl.getName(), getDescription(cl), attrArray, conArray, opArray, notifArray); /* Descriptor descriptor = modelInfo.getMBeanDescriptor(); if (descriptor != null) { descriptor.setField("mxbean", "true"); modelInfo.setMBeanDescriptor(descriptor); } */ info = modelInfo; _cachedInfo.put(cl, new SoftReference<MBeanInfo>(info)); return info; } catch (Exception e) { NotCompliantMBeanException exn; exn = new NotCompliantMBeanException(String.valueOf(e)); exn.initCause(e); throw exn; } } /** * Returns the matching setter. */ static Method getSetter(Method []methods, String property, Class type) { String name = "set" + property; for (int i = 0; i < methods.length; i++) { if (! methods[i].getName().equals(name)) continue; Class []args = methods[i].getParameterTypes(); if (args.length != 1 || ! args[0].equals(type)) continue; return methods[i]; } return null; } /** * Returns the matching getter. */ static Method getGetter(Method []methods, String property, Class type) { String getName = "get" + property; String isName = "is" + property; for (int i = 0; i < methods.length; i++) { if (! methods[i].getName().equals(getName) && ! methods[i].getName().equals(isName)) continue; Class []args = methods[i].getParameterTypes(); if (args.length != 0) continue; Class retType = methods[i].getReturnType(); if (! retType.equals(type)) continue; return methods[i]; } return null; } /** * Returns the class's description. */ static String getDescription(Class cl) { try { Description desc = (Description) cl.getAnnotation(_descriptionAnn); if (desc != null) return desc.value(); else return ""; } catch (Throwable e) { return ""; } } /** * Returns the method's description. */ static String getDescription(Method method) { try { Description desc = (Description) method.getAnnotation(_descriptionAnn); if (desc != null) return desc.value(); else return ""; } catch (Throwable e) { return ""; } } /** * Returns the method's name, the optional {@link Name} annotation overrides. */ static String getName(Method method) { try { Name name = (Name) method.getAnnotation(_nameAnn); if (name != null) return name.value(); else return method.getName(); } catch (Throwable e) { return method.getName(); } } private static MBeanParameterInfo[] getSignature(Method method) { Class[] params = method.getParameterTypes(); MBeanParameterInfo[] paramInfos = new MBeanParameterInfo[params.length]; for (int i = 0; i < params.length; i++) { Class cl = params[i]; String name = getName(method, i); String description = getDescription(method, i); paramInfos[i] = new MBeanParameterInfo(name, cl.getName(), description); } return paramInfos; } private static String getName(Method method, int i) { try { for (Annotation ann : method.getParameterAnnotations()[i]) { if (ann instanceof Name) return ((Name) ann).value(); } } catch (Throwable e) { log.log(Level.FINER, e.toString(), e); } return "p" + i; } private static String getDescription(Method method, int i) { try { for (Annotation ann : method.getParameterAnnotations()[i]) { if (ann instanceof Description) return ((Description) ann).value(); } } catch (Throwable e) { log.log(Level.FINER, e.toString(), e); } return ""; } private static String getTypeName(Class type) { if (type.isArray()) return getTypeName(type.getComponentType()) + "[]"; else return type.getName(); } private static OpenType getOpenType(Class type) { try { if (type.isArray()) { OpenType component = getOpenType(type.getComponentType()); if (component != null) return new ArrayType(1, component); else return null; } else if (type.getName().endsWith("MXBean") || type.getName().endsWith("MBean")) return SimpleType.OBJECTNAME; else if (void.class.equals(type)) return SimpleType.VOID; else if (boolean.class.equals(type) || Boolean.class.equals(type)) return SimpleType.BOOLEAN; else if (byte.class.equals(type) || Byte.class.equals(type)) return SimpleType.BYTE; else if (short.class.equals(type) || Short.class.equals(type)) return SimpleType.SHORT; else if (int.class.equals(type) || Integer.class.equals(type)) return SimpleType.INTEGER; else if (long.class.equals(type) || Long.class.equals(type)) return SimpleType.LONG; else if (float.class.equals(type) || Float.class.equals(type)) return SimpleType.FLOAT; else if (double.class.equals(type) || Double.class.equals(type)) return SimpleType.DOUBLE; else if (String.class.equals(type)) return SimpleType.STRING; else if (char.class.equals(type) || Character.class.equals(type)) return SimpleType.CHARACTER; else if (java.util.Date.class.equals(type)) return SimpleType.DATE; else if (java.util.Calendar.class.equals(type)) return SimpleType.DATE; else return null; // can't deal with more complex at the moment } catch (Exception e) { log.log(Level.FINER, e.toString(), e); return null; } } private static Class findClass(String name) { try { return Class.forName(name); } catch (Throwable e) { return null; } } private static final Comparator<MBeanFeatureInfo> MBEAN_FEATURE_INFO_COMPARATOR = new Comparator<MBeanFeatureInfo>() { public int compare(MBeanFeatureInfo o1, MBeanFeatureInfo o2) { return o1.getName().compareTo(o2.getName()); } }; static { _descriptionAnn = findClass("com.caucho.jmx.Description"); _nameAnn = findClass("com.caucho.jmx.Name"); } }