/* * JBoss, Home of Professional Open Source. * Copyright 2000 - 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.cache.jmx; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.cache.jmx.annotations.MBean; import org.jboss.cache.jmx.annotations.ManagedAttribute; import org.jboss.cache.jmx.annotations.ManagedOperation; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.DynamicMBean; import javax.management.MBeanAttributeInfo; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; import javax.management.ReflectionException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * This class was entirely copied from jgroups 2.7 (same name there). * Couldn't simply reuse it because jgroups does not ship with MBean, ManagedAttribute and ManagedOperation. * Once jgroups will ship these classes, the code can be dinalmically reused from there. * * @author Mircea.Markus@jboss.com * @since 3.0 */ public class ResourceDMBean implements DynamicMBean { private static final Class[] primitives = {int.class, byte.class, short.class, long.class, float.class, double.class, boolean.class, char.class}; private static final String MBEAN_DESCRITION = "Dynamic MBean Description"; private final Log log = LogFactory.getLog(ResourceDMBean.class); private final Object obj; private String description = ""; private final MBeanAttributeInfo[] attrInfo; private final MBeanOperationInfo[] opInfo; private final HashMap atts = new HashMap(); private final List ops = new ArrayList(); public ResourceDMBean(Object instance) { if (instance == null) throw new NullPointerException("Cannot make an MBean wrapper for null instance"); this.obj = instance; findDescription(); findFields(); findMethods(); attrInfo = new MBeanAttributeInfo[atts.size()]; int i = 0; if (log.isTraceEnabled()) { log.trace("Processing class " + instance.getClass()); log.trace("Number of attributes: " + atts.size()); } MBeanAttributeInfo info; for (AttributeEntry entry : atts.values()) { info = entry.getInfo(); attrInfo[i++] = info; if (log.isTraceEnabled()) { log.trace("Attribute " + info.getName() + "[r=" + info.isReadable() + ",w=" + info.isWritable() + ",is=" + info.isIs() + ",type=" + info.getType() + "]"); } } opInfo = new MBeanOperationInfo[ops.size()]; ops.toArray(opInfo); if (log.isTraceEnabled()) { if (ops.size() > 0) log.trace("Operations are:"); for (MBeanOperationInfo op : opInfo) { log.trace("Operation " + op.getReturnType() + " " + op.getName()); } } } Object getObject() { return obj; } private void findDescription() { MBean mbean = getObject().getClass().getAnnotation(MBean.class); if (mbean != null && mbean.description() != null && mbean.description().trim().length() > 0) { description = mbean.description(); if (log.isTraceEnabled()) { log.trace("@MBean description set - " + mbean.description()); } MBeanAttributeInfo info = new MBeanAttributeInfo(MBEAN_DESCRITION, "java.lang.String", "@MBean description", true, false, false); try { atts.put(MBEAN_DESCRITION, new FieldAttributeEntry(info, getClass().getDeclaredField("description"))); } catch (NoSuchFieldException e) { //this should not happen unless somebody removes description field log.warn("Could not reflect field description of this class. Was it removed?"); } } } public synchronized MBeanInfo getMBeanInfo() { return new MBeanInfo(getObject().getClass().getCanonicalName(), description, attrInfo, null, opInfo, null); } public synchronized Object getAttribute(String name) { if (name == null || name.length() == 0) throw new NullPointerException("Invalid attribute requested " + name); if (log.isTraceEnabled()) { log.trace("getAttribute called for " + name); } Attribute attr = getNamedAttribute(name); if (log.isTraceEnabled()) { log.trace("getAttribute value found " + attr.getValue()); } return attr.getValue(); } public synchronized void setAttribute(Attribute attribute) { if (attribute == null || attribute.getName() == null) throw new NullPointerException("Invalid attribute requested " + attribute); setNamedAttribute(attribute); } public synchronized AttributeList getAttributes(String[] names) { AttributeList al = new AttributeList(); for (String name : names) { Attribute attr = getNamedAttribute(name); if (attr != null) { al.add(attr); } else { log.warn("Did not find attribute " + name); } } return al; } public synchronized AttributeList setAttributes(AttributeList list) { AttributeList results = new AttributeList(); for (int i = 0; i < list.size(); i++) { Attribute attr = (Attribute) list.get(i); if (log.isTraceEnabled()) { log.trace("Attribute name " + attr.getName() + " new value is " + attr.getValue()); } if (setNamedAttribute(attr)) { results.add(attr); } else { if (log.isTraceEnabled()) { log.trace("Failed to update attribute name " + attr.getName() + " with value " + attr.getValue()); } } } return results; } public Object invoke(String name, Object[] args, String[] sig) throws MBeanException, ReflectionException { try { if (log.isTraceEnabled()) { log.trace("Invoke method called on " + name); } Class[] classes = new Class[sig.length]; for (int i = 0; i < classes.length; i++) { classes[i] = getClassForName(sig[i]); } Method method = getObject().getClass().getMethod(name, classes); return method.invoke(getObject(), args); } catch (Exception e) { throw new MBeanException(e); } } public static Class getClassForName(String name) throws ClassNotFoundException { try { Class c = Class.forName(name); return c; } catch (ClassNotFoundException cnfe) { //Could be a primitive - let's check for (int i = 0; i < primitives.length; i++) { if (name.equals(primitives[i].getName())) { return primitives[i]; } } } throw new ClassNotFoundException("Class " + name + " cannot be found"); } private void findMethods() { //find all methods but don't include methods from Object class List methods = new ArrayList(Arrays.asList(getObject().getClass().getMethods())); List objectMethods = new ArrayList(Arrays.asList(Object.class.getMethods())); methods.removeAll(objectMethods); for (Method method : methods) { //does method have @ManagedAttribute annotation? ManagedAttribute attr = method.getAnnotation(ManagedAttribute.class); if (attr != null) { String methodName = method.getName(); if (!methodName.startsWith("get") && !methodName.startsWith("set") && !methodName.startsWith("is")) { if (log.isWarnEnabled()) { log.warn("method name " + methodName + " doesn't start with \"get\", \"set\", or \"is\"" + ", but is annotated with @ManagedAttribute: will be ignored"); } } else { MBeanAttributeInfo info = null; //Is name field of @ManagedAttributed used? String attributeName = attr.name().length() > 0 ? attr.name().trim() : null; boolean writeAttribute = false; if (isSetMethod(method)) { // setter attributeName = (attributeName == null) ? methodName.substring(3) : attributeName; info = new MBeanAttributeInfo(attributeName, method.getParameterTypes()[0].getCanonicalName(), attr.description(), true, true, false); writeAttribute = true; } else { // getter if (method.getParameterTypes().length == 0 && method.getReturnType() != java.lang.Void.TYPE) { boolean hasSetter = atts.containsKey(attributeName); //we found is method if (methodName.startsWith("is")) { attributeName = (attributeName == null) ? methodName.substring(2) : attributeName; info = new MBeanAttributeInfo(attributeName, method.getReturnType().getCanonicalName(), attr.description(), true, hasSetter, true); } else { //this has to be get attributeName = (attributeName == null) ? methodName.substring(3) : attributeName; info = new MBeanAttributeInfo(attributeName, method.getReturnType().getCanonicalName(), attr.description(), true, hasSetter, false); } } else { if (log.isWarnEnabled()) { log.warn("Method " + method.getName() + " must have a valid return type and zero parameters"); } continue; } } if (log.isTraceEnabled()) { log.trace("@Attr found for method " + method.getName() + " and registered as " + attributeName); } AttributeEntry ae = atts.get(attributeName); //is it a read method? if (!writeAttribute) { //we already have annotated field as read if (ae instanceof FieldAttributeEntry && ae.getInfo().isReadable()) { log.warn("not adding annotated method " + method + " since we already have read attribute"); } //we already have annotated set method else if (ae instanceof MethodAttributeEntry) { MethodAttributeEntry mae = (MethodAttributeEntry) ae; if (mae.hasSetMethod()) { atts.put(attributeName, new MethodAttributeEntry(mae.getInfo(), mae.getSetMethod(), method)); } } //we don't have such entry else { atts.put(attributeName, new MethodAttributeEntry(info, null, method)); } }//is it a set method? else { if (ae instanceof FieldAttributeEntry) { //we already have annotated field as write if (ae.getInfo().isWritable()) { log.warn("Not adding annotated method " + methodName + " since we already have writable attribute"); } else { //we already have annotated field as read //lets make the field writable Field f = ((FieldAttributeEntry) ae).getField(); MBeanAttributeInfo i = new MBeanAttributeInfo(ae.getInfo().getName(), f.getType().getCanonicalName(), attr.description(), true, Modifier.isFinal(f.getModifiers()) ? false : true, false); atts.put(attributeName, new FieldAttributeEntry(i, f)); } } //we already have annotated getOrIs method else if (ae instanceof MethodAttributeEntry) { MethodAttributeEntry mae = (MethodAttributeEntry) ae; if (mae.hasIsOrGetMethod()) { atts.put(attributeName, new MethodAttributeEntry(info, method, mae.getIsOrGetMethod())); } } //we don't have such entry else { atts.put(attributeName, new MethodAttributeEntry(info, method, null)); } } } } else if (method.isAnnotationPresent(ManagedOperation.class) || isMBeanAnnotationPresentWithExposeAll()) { ManagedOperation op = method.getAnnotation(ManagedOperation.class); String attName = method.getName(); if (isSetMethod(method) || isGetMethod(method)) { attName = attName.substring(3); } else if (isIsMethod(method)) { attName = attName.substring(2); } //expose unless we already exposed matching attribute field boolean isAlreadyExposed = atts.containsKey(attName); if (!isAlreadyExposed) { ops.add(new MBeanOperationInfo(op != null ? op.description() : "", method)); if (log.isTraceEnabled()) { log.trace("@Operation found for method " + method.getName()); } } } } } private boolean isSetMethod(Method method) { return (method.getName().startsWith("set") && method.getParameterTypes().length == 1 && method.getReturnType() == java.lang.Void.TYPE); } private boolean isGetMethod(Method method) { return (method.getParameterTypes().length == 0 && method.getReturnType() != java.lang.Void.TYPE && method.getName().startsWith("get")); } private boolean isIsMethod(Method method) { return (method.getParameterTypes().length == 0 && (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class) && method.getName().startsWith("is")); } private void findFields() { //traverse class hierarchy and find all annotated fields for (Class clazz = getObject().getClass(); clazz != null; clazz = clazz.getSuperclass()) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { ManagedAttribute attr = field.getAnnotation(ManagedAttribute.class); if (attr != null) { String fieldName = renameToJavaCodingConvention(field.getName()); MBeanAttributeInfo info = new MBeanAttributeInfo(fieldName, field.getType().getCanonicalName(), attr.description(), true, Modifier.isFinal(field.getModifiers()) ? false : attr.writable(), false); atts.put(fieldName, new FieldAttributeEntry(info, field)); if (log.isTraceEnabled()) { log.trace("@Attr found for field " + field.getName()); } } } } } private Attribute getNamedAttribute(String name) { Attribute result = null; if (name.equals(MBEAN_DESCRITION)) { result = new Attribute(MBEAN_DESCRITION, this.description); } else { AttributeEntry entry = atts.get(name); if (entry != null) { MBeanAttributeInfo i = entry.getInfo(); try { result = new Attribute(name, entry.invoke(null)); if (log.isTraceEnabled()) { log.trace("Attribute " + name + " has r=" + i.isReadable() + ",w=" + i.isWritable() + ",is=" + i.isIs() + " and value " + result.getValue()); } } catch (Exception e) { log.warn("Exception while reading value of attribute " + name, e); } } else { log.warn("Did not find queried attribute with name " + name); } } return result; } private boolean setNamedAttribute(Attribute attribute) { boolean result = false; if (log.isTraceEnabled()) { log.trace("Invoking set on attribute " + attribute.getName() + " with value " + attribute.getValue()); } AttributeEntry entry = atts.get(attribute.getName()); if (entry != null) { try { entry.invoke(attribute); result = true; } catch (Exception e) { log.warn("Exception while writing value for attribute " + attribute.getName(), e); } } else { log.warn("Could not invoke set on attribute " + attribute.getName() + " with value " + attribute.getValue()); } return result; } private String renameToJavaCodingConvention(String fieldName) { if (fieldName.contains("_")) { Pattern p = Pattern.compile("_."); Matcher m = p.matcher(fieldName); StringBuffer sb = new StringBuffer(); while (m.find()) { m.appendReplacement(sb, fieldName.substring(m.end() - 1, m.end()).toUpperCase()); } m.appendTail(sb); char first = sb.charAt(0); if (Character.isLowerCase(first)) { sb.setCharAt(0, Character.toUpperCase(first)); } return sb.toString(); } else { if (Character.isLowerCase(fieldName.charAt(0))) { return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); } else { return fieldName; } } } private boolean isMBeanAnnotationPresentWithExposeAll() { Class c = getObject().getClass(); return c.isAnnotationPresent(MBean.class) && c.getAnnotation(MBean.class).exposeAll(); } private class MethodAttributeEntry implements AttributeEntry { final MBeanAttributeInfo info; final Method isOrGetmethod; final Method setMethod; public MethodAttributeEntry(final MBeanAttributeInfo info, final Method setMethod, final Method isOrGetMethod) { super(); this.info = info; this.setMethod = setMethod; this.isOrGetmethod = isOrGetMethod; } public Object invoke(Attribute a) throws Exception { if (a == null && isOrGetmethod != null) return isOrGetmethod.invoke(getObject(), new Object[]{}); else if (a != null && setMethod != null) return setMethod.invoke(getObject(), new Object[]{a.getValue()}); else return null; } public MBeanAttributeInfo getInfo() { return info; } public boolean hasIsOrGetMethod() { return isOrGetmethod != null; } public boolean hasSetMethod() { return setMethod != null; } public Method getIsOrGetMethod() { return isOrGetmethod; } public Method getSetMethod() { return setMethod; } } private class FieldAttributeEntry implements AttributeEntry { private final MBeanAttributeInfo info; private final Field field; public FieldAttributeEntry(final MBeanAttributeInfo info, final Field field) { super(); this.info = info; this.field = field; if (!field.isAccessible()) { field.setAccessible(true); } } public Field getField() { return field; } public Object invoke(Attribute a) throws Exception { if (a == null) { return field.get(getObject()); } else { field.set(getObject(), a.getValue()); return null; } } public MBeanAttributeInfo getInfo() { return info; } } private interface AttributeEntry { public Object invoke(Attribute a) throws Exception; public MBeanAttributeInfo getInfo(); } public boolean isManagedResource() { return !atts.isEmpty() || !ops.isEmpty(); } public String getObjectName() { MBean mBean = obj.getClass().getAnnotation(MBean.class); if (mBean != null && mBean.objectName() != null && mBean.objectName().trim().length() > 0) { return mBean.objectName(); } return obj.getClass().getSimpleName(); } public boolean isOperationRegistred(String operationName) { for (MBeanOperationInfo opInfo : this.ops) { if (opInfo.getName().equals(operationName)) { return true; } } return false; } }