/* * JBoss, Home of Professional Open Source. * Copyright 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.mx.metadata; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import javax.management.IntrospectionException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanConstructorInfo; import javax.management.MBeanInfo; import javax.management.MBeanNotificationInfo; import javax.management.MBeanOperationInfo; import javax.management.NotCompliantMBeanException; import javax.management.NotificationBroadcaster; /** * This metadata builder implementation builds a MBean info based on the * naming rules of the Standard MBeans. The MBean server uses this builder * to generate the metadata for Standard MBeans. <p> * * In cooperation with the * {@link MBeanInfoConversion#toModelMBeanInfo MBeanInfoConversion} class you * can use this builder as a migration tool from Standard to Model MBeans, or * for cases where you want the management interface be based on a compile-time * type safe interface. It is also possible to subclass this builder * implementation to extend it to support more sophisticated introspection rules * such as adding descriptors to management interface elements. * * @author <a href="mailto:juha@jboss.org">Juha Lindfors</a>. * @author <a href="mailto:trevor@protocool.com">Trevor Squires</a>. * @author <a href="mailto:thomas.diesler@jboss.com">Thomas Diesler</a>. * @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>. */ public class StandardMetaData extends AbstractBuilder { // Attributes ---------------------------------------------------- /** * The MBean object instance. * Can be <tt>null</tt>. */ private Object mbeanInstance = null; /** * The class of the MBean instance. */ private Class mbeanClass = null; /** * The interface used as a basis for constructing the MBean metadata. */ private Class mbeanInterface = null; // Static -------------------------------------------------------- /** * Locates an interface for a class that matches the Standard MBean naming * convention. * * @param mbeanClass the class to investigate * * @return the Standard MBean interface class or <tt>null</tt> if not found */ public static Class findStandardInterface(Class mbeanClass) { Class concrete = mbeanClass; Class stdInterface = null; while (null != concrete) { stdInterface = findStandardInterface(concrete, concrete.getInterfaces()); if (null != stdInterface) { return stdInterface; } concrete = concrete.getSuperclass(); } return null; } private static Class findStandardInterface(Class concrete, Class[] interfaces) { String stdName = concrete.getName() + "MBean"; Class retval = null; // look to see if this class implements MBean std interface for (int i = 0; i < interfaces.length; ++i) { if (interfaces[i].getName().equals(stdName)) { retval = interfaces[i]; break; } } return retval; } // Constructors -------------------------------------------------- /** * Initializes the Standard metadata builder. The JMX metadata is based * on the class of the given resource instance. * * @param mbeanInstance MBean instance */ public StandardMetaData(Object mbeanInstance) throws NotCompliantMBeanException { this(mbeanInstance.getClass()); this.mbeanInstance = mbeanInstance; } /** * Initializes the Standard metadata builder. The JMX metadata is based * on the given class. * * @param mbeanClass resource class that implements an interface * adhering to the Standard MBean naming conventions */ public StandardMetaData(Class mbeanClass) throws NotCompliantMBeanException { this.mbeanClass = mbeanClass; this.mbeanInterface = StandardMetaData.findStandardInterface(mbeanClass); if (this.mbeanInterface == null) throw new NotCompliantMBeanException("Cannot obtain management interface for: " + mbeanClass); } /** * Initializes the Standard metadata builder. The JMX metadata is based * on the passed mbean interface. * * @param mbInstance MBean instance * @param mbInterface the management interface */ public StandardMetaData(Object mbInstance, Class mbInterface) throws NotCompliantMBeanException { this.mbeanInstance = mbInstance; this.mbeanClass = mbInstance.getClass(); this.mbeanInterface = mbInterface; // search for it if (this.mbeanInterface == null) this.mbeanInterface = StandardMetaData.findStandardInterface(mbeanClass); if (this.mbeanInterface == null) throw new NotCompliantMBeanException("Cannot obtain management interface for: " + mbeanClass); if (this.mbeanInterface.isInterface() == false) throw new NotCompliantMBeanException("Management interface is not an interface: " + mbeanInterface); } /** * Retrieve the management interface */ public Class getMBeanInterface() { return mbeanInterface; } // MetaDataBuilder implementation -------------------------------- public MBeanInfo build() throws NotCompliantMBeanException { try { // First check the mbean instance implements the interface if (mbeanInterface == null) throw new NotCompliantMBeanException("The mbean does not implement a management interface"); if (mbeanInstance != null && mbeanInterface.isInstance(mbeanInstance) == false) throw new NotCompliantMBeanException("The mbean does not implement its management interface " + mbeanInterface.getName()); // First build the constructors Constructor[] constructors = mbeanClass.getConstructors(); MBeanConstructorInfo[] constructorInfo = new MBeanConstructorInfo[constructors.length]; for (int i = 0; i < constructors.length; ++i) { constructorInfo[i] = new MBeanConstructorInfo("MBean Constructor.", constructors[i]); } // Next we have to figure out how the methods in the mbean class map // to attributes and operations Method[] methods = mbeanInterface.getMethods(); HashMap getters = new HashMap(); HashMap setters = new HashMap(); HashMap operInfo = new HashMap(); List attrInfo = new ArrayList(); for (int i = 0; i < methods.length; ++i) { String methodName = methods[i].getName(); Class[] signature = methods[i].getParameterTypes(); Class returnType = methods[i].getReturnType(); if (methodName.startsWith("set") && methodName.length() > 3 && signature.length == 1 && returnType == Void.TYPE) { String key = methodName.substring(3, methodName.length()); Method setter = (Method) setters.get(key); if (setter != null && setter.getParameterTypes()[0].equals(signature[0]) == false) { throw new IntrospectionException("overloaded type for attribute set: " + key); } setters.put(key, methods[i]); } else if (methodName.startsWith("get") && methodName.length() > 3 && signature.length == 0 && returnType != Void.TYPE) { String key = methodName.substring(3, methodName.length()); Method getter = (Method) getters.get(key); if (getter != null && getter.getName().startsWith("is")) { throw new IntrospectionException("mixed use of get/is for attribute " + key); } getters.put(key, methods[i]); } else if (methodName.startsWith("is") && methodName.length() > 2 && signature.length == 0 && isBooleanReturn(returnType)) { String key = methodName.substring(2, methodName.length()); Method getter = (Method) getters.get(key); if (getter != null && getter.getName().startsWith("get")) { throw new IntrospectionException("mixed use of get/is for attribute " + key); } getters.put(key, methods[i]); } else { MBeanOperationInfo info = new MBeanOperationInfo("MBean Operation.", methods[i]); operInfo.put(getSignatureString(methods[i]), info); } } Object[] keys = getters.keySet().toArray(); for (int i = 0; i < keys.length; ++i) { String attrName = (String) keys[i]; Method getter = (Method) getters.remove(attrName); Method setter = (Method) setters.remove(attrName); MBeanAttributeInfo info = new MBeanAttributeInfo(attrName, "MBean Attribute.", getter, setter); attrInfo.add(info); } Iterator it = setters.keySet().iterator(); while (it.hasNext()) { String attrName = (String) it.next(); Method setter = (Method) setters.get(attrName); MBeanAttributeInfo info = new MBeanAttributeInfo(attrName, "MBean Attribute.", null, setter); attrInfo.add(info); } // save away the attribute and operation info objects MBeanAttributeInfo[] attributeInfo = (MBeanAttributeInfo[]) attrInfo.toArray(new MBeanAttributeInfo[0]); MBeanOperationInfo[] operationInfo = (MBeanOperationInfo[]) operInfo.values().toArray(new MBeanOperationInfo[0]); // if the builder was initialized with the resource instance, check if // it is a notification broadcaster, and add the appropriate notifications // to the interface. MBeanNotificationInfo[] notifications = null; if (mbeanInstance instanceof NotificationBroadcaster) { notifications = ((NotificationBroadcaster) mbeanInstance).getNotificationInfo(); } else { notifications = new MBeanNotificationInfo[0]; } return new MBeanInfo(mbeanClass.getName(), "Management Bean.", attributeInfo, constructorInfo, operationInfo, notifications); } catch (IntrospectionException e) { throw new NotCompliantMBeanException(e.getMessage()); } } /** * JMX standard specifies that only "boolean isX()" style methods * represent attributes. "Boolean isX()" methods map to operations. */ private boolean isBooleanReturn(Class returnType) { return returnType == Boolean.TYPE; } protected String getSignatureString(Method method) { String name = method.getName(); Class[] signature = method.getParameterTypes(); StringBuffer buffer = new StringBuffer(512); buffer.append(name); buffer.append("("); if (signature != null) { for (int i = 0; i < signature.length; i++) { buffer.append(signature[i].getName()); if (i < signature.length-1) buffer.append(","); } } buffer.append(")"); return buffer.toString(); } }