FileDocCategorySizeDatePackage
Introspector.javaAPI DocJava SE 5 API18224Fri Aug 26 14:54:58 BST 2005com.sun.jmx.mbeanserver

Introspector.java

/*
 * @(#)Introspector.java	1.68 03/12/19
 * 
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.jmx.mbeanserver;


// Java import
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;

// RI Import
import javax.management.IntrospectionException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.NotCompliantMBeanException;


/**
 * This class contains the methods for performing all the tests needed to verify
 * that a class represents a JMX compliant MBean.
 *
 * @since 1.5
 */
public class Introspector {
    

    /*
     * ------------------------------------------
     *  PRIVATE VARIABLES
     * ------------------------------------------
     */

    private static final String attributeDescription =
	"Attribute exposed for management";
    private static final String operationDescription =
	"Operation exposed for management";
    private static final String constructorDescription =
	"Public constructor of the MBean";
    private static final String mbeanInfoDescription =
	"Information on the management interface of the MBean";
    

     /*
     * ------------------------------------------
     *  PRIVATE CONSTRUCTORS
     * ------------------------------------------
     */

    // private constructor defined to "hide" the default public constructor
    private Introspector() {

	// ------------------------------ 
	// ------------------------------
	
    }
    
    /*
     * ------------------------------------------
     *  PUBLIC METHODS
     * ------------------------------------------
     */

    /**
     * Tell whether a MBean of the given class is a Dynamic MBean.
     * This method does nothing more than returning
     * <pre>
     * javax.management.DynamicMBean.class.isAssignableFrom(c)
     * </pre>
     * This method does not check for any JMX MBean compliance:
     * <ul><li>If <code>true</code> is returned, then instances of 
     *     <code>c</code> are DynamicMBean.</li>
     *     <li>If <code>false</code> is returned, then no further
     *     assumption can be made on instances of <code>c</code>.
     *     In particular, instances of <code>c</code> may, or may not
     *     be JMX standard MBeans.</li>
     * </ul>
     * @param c The class of the MBean under examination.
     * @return <code>true</code> if instances of <code>c</code> are
     *         Dynamic MBeans, <code>false</code> otherwise. 
     *
     * @since.unbundled JMX RI 1.2
     **/
    public static final boolean isDynamic(final Class c) {
	// Check if the MBean implements the DynamicMBean interface
	return javax.management.DynamicMBean.class.isAssignableFrom(c);
    }

    /**
     * Basic method for testing that a MBean of a given class can be
     * instantiated by the MBean server.<p>
     * This method checks that:
     * <ul><li>The given class is a concrete class.</li>
     *     <li>The given class exposes at least one public constructor.</li>
     * </ul>
     * If these conditions are not met, throws a NotCompliantMBeanException.
     * @param c The class of the MBean we want to create.
     * @exception NotCompliantMBeanException if the MBean class makes it
     *            impossible to instantiate the MBean from within the
     *            MBeanServer.
     *
     * @since.unbundled JMX RI 1.2
     **/
    public static void testCreation(Class c) 
	throws NotCompliantMBeanException {
	// Check if the class is a concrete class
	final int mods = c.getModifiers(); 
	if (Modifier.isAbstract(mods) || Modifier.isInterface(mods)) {
	    throw new NotCompliantMBeanException("MBean class must be concrete");
	}

	// Check if the MBean has a public constructor 
	final Constructor[] consList = c.getConstructors();     
	if (consList.length == 0) {
	    throw new NotCompliantMBeanException("MBean class must have public constructor");
	}
    }
    
    /**
     * Basic method for testing if a given class is a JMX compliant MBean.
     *
     * @param baseClass The class to be tested
     *
     * @return <code>null</code> if the MBean is a DynamicMBean, 
     *         the computed {@link javax.management.MBeanInfo} otherwise.
     * @exception NotCompliantMBeanException The specified class is not a 
     *            JMX compliant MBean
     */    
    public static MBeanInfo testCompliance(Class baseClass) 
	throws NotCompliantMBeanException {

	// ------------------------------ 
	// ------------------------------
	
	// Check if the MBean implements the MBean or the Dynamic 
	// MBean interface
	if (isDynamic(baseClass)) 
	    return null;
	
	return testCompliance(baseClass, null);
    }

    
    /**
     * Basic method for testing if a given class is a JMX compliant MBean.
     *
     * @param baseClass The class to be tested
     *
     * @return <code>null</code> if the MBean is a DynamicMBean, 
     *         the computed {@link javax.management.MBeanInfo} otherwise.
     * @exception NotCompliantMBeanException The specified class is not a 
     *            JMX compliant MBean
     */    
    static MBeanInfo testCompliance(final Class baseClass,
				    Class mbeanInterface) 
	    throws NotCompliantMBeanException {
	
	if (baseClass.isInterface()) 
	    throw new NotCompliantMBeanException(baseClass.getName() + 
						 " must be a class.");
	// ------------------------------ 
	// ------------------------------
	if (mbeanInterface == null)
	    // No interface specified: look for default MBean interface.
	    mbeanInterface = getStandardMBeanInterface(baseClass);
	else if (! mbeanInterface.isAssignableFrom(baseClass)) {
	    // specified interface not implemented by given class
	    final String msg =
		baseClass.getName() + " does not implement the " + 
		mbeanInterface.getName() + " interface";
	    throw new NotCompliantMBeanException(msg);
	} else if (! mbeanInterface.isInterface()) {
	    // Base class X, but XMBean is not an interface
	    final String msg =
		baseClass.getName() + ": " + mbeanInterface.getName() + 
		" is not an interface";
	    throw new NotCompliantMBeanException(msg);
	}


	if (mbeanInterface == null) {
	    // Error: MBean does not implement javax.management.DynamicMBean 
	    // nor MBean interface
	    final String baseClassName = baseClass.getName();
	    final String msg =
		baseClassName + " does not implement the " + baseClassName +
		"MBean interface or the DynamicMBean interface";
	    throw new NotCompliantMBeanException(msg);
	}
	
	final int mods = mbeanInterface.getModifiers();
	if (!Modifier.isPublic(mods)) 
	    throw new NotCompliantMBeanException(mbeanInterface.getName() + 
						 " implemented by " +
						 baseClass.getName() + 
						 " must be public");

	return (introspect(baseClass, mbeanInterface));
    }

    
    /**
     * Get the MBean interface implemented by a JMX standard MBean 
     * class.
     *
     * @param baseClass The class to be tested
     * 
     * @return The MBean interface implemented by the MBean. 
     *         Return <code>null</code> if the MBean is a DynamicMBean, 
     *         or if no MBean interface is found.
     *
     */
    public static Class getMBeanInterface(Class baseClass) {

	// ------------------------------ 
	// ------------------------------
	
	// Check if the MBean implements the MBean or the Dynamic 
	// MBean interface
	if (isDynamic(baseClass)) return null;

	return getStandardMBeanInterface(baseClass);     
    }
    
    /**
     * Get the MBean interface implemented by a JMX standard MBean 
     * class.
     *
     * @param baseClass The class to be tested
     * 
     * @return The MBean interface implemented by the MBean. 
     *         Return <code>null</code> if no MBean interface is found.
     *         Does not check whether the MBean is a DynamicMBean.
     *
     */
    static Class getStandardMBeanInterface(Class baseClass) {

	// ------------------------------ 
	// ------------------------------
	
	Class current = baseClass;
	Class mbeanInterface = null;
	
	while (current != null) {
	    mbeanInterface = 
		findMBeanInterface(current, current.getName());
	    if (mbeanInterface != null) break;
	    current = current.getSuperclass();
	}
	return mbeanInterface;
    }

    /*
     * ------------------------------------------
     *  PRIVATE METHODS
     * ------------------------------------------
     */
    

    /**
     * Try to find the MBean interface corresponding to the class aName 
     * - i.e. <i>aName</i>MBean, from within aClass and its superclasses.
     **/
    private static Class findMBeanInterface(Class aClass, String aName) {
	Class current = aClass;
	while (current != null) {
	    final Class[] interfaces = current.getInterfaces();   
	    final int len = interfaces.length;
	    for (int i=0;i<len;i++)  {	     
		final Class inter = 
		    implementsMBean(interfaces[i], aName);
		if (inter != null) return inter;
	    }
	    current = current.getSuperclass();
	}
	return null;     
    }


    /**
     * Discovers the getters, setters, operations of the class
     *
     * @param baseClass The XX base class.
     * @param beanClass The XXMBean interface implemented by the tested class.
     *
     * @exception NotCompliantMBeanException The tested class is not a
     * JMX compliant MBean
     */    
    private static MBeanInfo introspect(Class baseClass, Class beanClass)
	    throws NotCompliantMBeanException {

	// ------------------------------ 
	// ------------------------------

	List/*<MBeanAttributeInfo>*/ attributes =
	    new ArrayList/*<MBeanAttributeInfo>*/();
	List/*<MBeanOperationInfo>*/ operations =
	    new ArrayList/*<MBeanOperationInfo>*/();
	
	Method methodList[] = beanClass.getMethods();
	
	// Now analyze each method.        
	for (int i = 0; i < methodList.length; i++) { 
	    Method method = methodList[i];
	    String name = method.getName();            
	    Class argTypes[] = method.getParameterTypes();
	    Class resultType = method.getReturnType();
	    int argCount = argTypes.length;

	    try {
		final MBeanAttributeInfo attr;

		if (name.startsWith("get") && !name.equals("get")
		    && argCount == 0 && !resultType.equals(void.class)) {
		    // if the method is "T getX()" it is a getter
		    attr = new MBeanAttributeInfo(name.substring(3),
						  attributeDescription,
						  method, null);
		} else if (name.startsWith("set") && !name.equals("set")
			   && argCount == 1 && resultType.equals(void.class)) {
		    // if the method is "void setX(T x)" it is a setter
		    attr = new MBeanAttributeInfo(name.substring(3),
						  attributeDescription,
						  null, method);
		} else if (name.startsWith("is") && !name.equals("is")
			   && argCount == 0
			   && resultType.equals(boolean.class)) {
		    // if the method is "boolean isX()" it is a getter
		    attr = new MBeanAttributeInfo(name.substring(2),
						  attributeDescription,
						  method, null);
		} else {
		    // in all other cases it is an operation
		    attr = null;
		}

		if (attr != null) {
		    if (testConsistency(attributes, attr))
			attributes.add(attr);
		} else {
		    final MBeanOperationInfo oper =
			new MBeanOperationInfo(operationDescription, method);
		    operations.add(oper);
		}
	    } catch (IntrospectionException e) {
		// Should not happen (MBeanAttributeInfo constructor)
		error("introspect", e);
	    }
	}

	return constructResult(baseClass, attributes, operations); 
    }

    /**
     * Checks if the types and the signatures of
     * getters/setters/operations are conform to the MBean design
     * patterns.
     *
     * Error cases:
     * 	-  It exposes a method void Y getXX() AND a method void setXX(Z)
     *     (parameter type mismatch) 
     * 	-  It exposes a method void setXX(Y) AND a method void setXX(Z)
     *     (parameter type mismatch) 
     *  -  It exposes a  boolean isXX() method AND a YY getXX() or a void setXX(Y).
     * Returns false if the attribute is already in attributes List
     */    
    private static boolean testConsistency(List/*<MBeanAttributeInfo>*/attributes,
					   MBeanAttributeInfo attr)
	throws NotCompliantMBeanException {
	for (Iterator it = attributes.iterator(); it.hasNext(); ) {
	    MBeanAttributeInfo mb = (MBeanAttributeInfo) it.next();
	    if (mb.getName().equals(attr.getName())) {
		if ((attr.isReadable() && mb.isReadable()) && 
		    (attr.isIs() != mb.isIs())) {
		    final String msg =
			"Conflicting getters for attribute " + mb.getName();
		    throw new NotCompliantMBeanException(msg);
		}  
		if (!mb.getType().equals(attr.getType())) {
		    if (mb.isWritable() && attr.isWritable()) {
			final String msg =
			    "Type mismatch between parameters of set" +
			    mb.getName() + " methods";
			throw new NotCompliantMBeanException(msg);
		    } else {
			final String msg =
			    "Type mismatch between parameters of get or is" +
			    mb.getName() + ", set" + mb.getName() + " methods";
			throw new NotCompliantMBeanException(msg);
		    }
		}
		if (attr.isReadable() && mb.isReadable()) {
		    return false;
		}
		if (attr.isWritable() && mb.isWritable()) {
		    return false;
		}
	    }
	}
	return true;
    }

    /**
     * Discovers the constructors of the MBean
     */
    static MBeanConstructorInfo[] getConstructors(Class baseClass) {
	Constructor[] consList = baseClass.getConstructors();
	List constructors = new ArrayList();
	
	// Now analyze each Constructor.        
	for (int i = 0; i < consList.length; i++) {
	    Constructor constructor = consList[i];    	    
	    MBeanConstructorInfo mc = null;
	    try {               
		mc = new MBeanConstructorInfo(constructorDescription, constructor);		     		                
	    } catch (Exception ex) {
		mc = null;
	    }
	    if (mc != null) {
		constructors.add(mc);
	    }
	}
	// Allocate and populate the result array.
	MBeanConstructorInfo[] resultConstructors =
	    new MBeanConstructorInfo[constructors.size()];
	constructors.toArray(resultConstructors);
	return resultConstructors;
    }
    
    /**
     * Constructs the MBeanInfo of the MBean.
     */
    private static MBeanInfo constructResult(Class baseClass,
					     List/*<MBeanAttributeInfo>*/ attributes,
					     List/*<MBeanOperationInfo>*/ operations) {
	
	final int len = attributes.size();
	final MBeanAttributeInfo[] attrlist = new MBeanAttributeInfo[len];
	attributes.toArray(attrlist);
	final ArrayList mergedAttributes = new ArrayList();
	
	for (int i=0;i<len;i++) {
	    final MBeanAttributeInfo bi = attrlist[i];
	    
	    // bi can be null if it has already been eliminated
	    // by the loop below at an earlier iteration
	    // (cf. attrlist[j]=null;) In this case, just skip it.
	    //
	    if (bi == null) continue;

	    // Placeholder for the final attribute info we're going to
	    // keep.
	    //
	    MBeanAttributeInfo att = bi;

	    // The loop below will try to find whether bi is also present
	    // elsewhere further down the list. 
	    // If it is not, att will be left unchanged.
	    // Otherwise, the found attribute info will be merged with
	    // att and `removed' from the array by setting them to `null'
	    //
            for (int j=i+1;j<len;j++) {
		MBeanAttributeInfo mi = attrlist[j];
		
		// mi can be null if it has already been eliminated
		// by this loop at an earlier iteration.
		// (cf. attrlist[j]=null;) In this case, just skip it.
		//
		if (mi == null) continue;
                if ((mi.getName().compareTo(bi.getName()) == 0)) {
		    // mi and bi have the same name, which means that 
		    // that the attribute has been inserted twice in 
		    // the list, which means that it is a read-write
		    // attribute.
		    // So we're going to replace att with a new 
		    // attribute info with read-write mode.
		    // We also set attrlist[j] to null in order to avoid
		    // duplicates (attrlist[j] and attrlist[i] are now
		    // merged into att).
		    //
		    attrlist[j]=null;
		    att = new MBeanAttributeInfo(bi.getName(), 
						 bi.getType(), 
						 attributeDescription, 
						 true, true, bi.isIs());
		    // I think we could break, but it is probably
		    // safer not to...
		    //
		    // break;
		}
	    }
                
	    // Now all attributes info which had the same name than bi
	    // have been merged together in att. 
	    // Simply add att to the merged list.
	    //
	    mergedAttributes.add(att);	    
        }
 
        final MBeanAttributeInfo[] resultAttributes =
	    new MBeanAttributeInfo[mergedAttributes.size()];
	mergedAttributes.toArray(resultAttributes);

        final MBeanOperationInfo[] resultOperations =
	    new MBeanOperationInfo[operations.size()];
	operations.toArray(resultOperations);

	final MBeanConstructorInfo[] resultConstructors =
	    getConstructors(baseClass);     

        final MBeanInfo resultMBeanInfo =
	    new MBeanInfo(baseClass.getName(), mbeanInfoDescription, 
			  resultAttributes, resultConstructors, 
			  resultOperations, null);
	return resultMBeanInfo;
    }

    /**
     * Returns the XXMBean interface or null if no such interface exists
     *
     * @param c The interface to be tested
     * @param clName The name of the class implementing this interface
     */
    static Class implementsMBean(Class c, String clName) {
	if (c.getName().compareTo(clName + "MBean") == 0) {
	    return c;
	}   
	Class current = c;
	Class[] interfaces = c.getInterfaces();
	for (int i = 0;i < interfaces.length; i++) {

	    try {
		if (interfaces[i].getName().compareTo(clName + "MBean") == 0) {
		    return interfaces[i];
		}     
	    } catch (Exception e) {
		return null;
	    }  	
	}
	
	return null;
    }

    private static void error(String method,Throwable t) {
	com.sun.jmx.trace.Trace.send(com.sun.jmx.trace.Trace.LEVEL_ERROR,
				     com.sun.jmx.trace.Trace.INFO_MBEANSERVER,
				     "Introspector",
				     method,
				     t);
				     
    }
}