MBeanInfoBuilder.java

package com.ziesemer.utils.jmx.server;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.NotCompliantMBeanException;
import javax.management.OperationsException;

import com.ziesemer.utils.jmx.beanInfo.MBeanDescription;
import com.ziesemer.utils.jmx.beanInfo.MBeanOperation;
import com.ziesemer.utils.jmx.beanInfo.MBeanParameter;

/**
 * <p>This class currently provides only limited customization, currently
 * 	focusing on performance.  Additionally, it does not yet handle
 * 	constructors ({@link javax.management.MBeanConstructorInfo}),
 * 	notifications ({@link javax.management.MBeanNotificationInfo})
 * 	or descriptors ({@link javax.management.Descriptor}),
 * 	so the non-public methods are subject to (and probably will) change.</p>
 * @author Mark A. Ziesemer
 * 	<a href="http://www.ziesemer.com.">&lt;www.ziesemer.com&gt;</a>
 */
public class MBeanInfoBuilder{
	
	public MBeanInfo build(final Class<?> c) throws OperationsException{
		
		final String className = mapName(c);
		final String description = mapDescription(c);
		
		// Attributes & Operations
		
		final List<MBeanOperationInfo> operations = new ArrayList<MBeanOperationInfo>();
		
		final AttributeSetBuilder asb = new AttributeSetBuilder();
		for(final Method method : c.getMethods()){
			// This section of code based on
			// 	com.sun.jmx.mbeanserver.Introspector.introspect(Class baseClass, Class beanClass)
			// 	in JDK 1.5.
			final String name = method.getName();
			final Class<?>[] paramTypes = method.getParameterTypes();
			final Class<?> returnType = method.getReturnType();
			final int paramCount = paramTypes.length;
			
			if(name.startsWith("get") && !"get".equals(name)
					&& paramCount == 0 && !void.class.equals(returnType)){
				asb.setGetter(name.substring(3), method);
			}else if(name.startsWith("set") && !"set".equals(name)
					&& paramCount == 1 && void.class.equals(returnType)){
				asb.get(name.substring(3)).setter = method;
			}else if(name.startsWith("is") && !"is".equals(name)
					&& paramCount == 0 && boolean.class.equals(returnType)){
				asb.setGetter(name.substring(2), method);
			}else{
				operations.add(mapOperation(method, name, paramTypes));
			}
		}
		
		final MBeanAttributeInfo[] attributes = mapAttributes(asb);
		
		return new MBeanInfo(className, description, attributes, null,
			operations.toArray(new MBeanOperationInfo[operations.size()]),
			null);
	}
	
	protected String mapName(final Class<?> c){
		return c.getName();
	}
	
	protected String mapDescription(final Class<?> c){
		String description = mapName(c);
		final MBeanDescription descriptionA = c.getAnnotation(MBeanDescription.class);
		if(descriptionA != null){
			description = descriptionA.value();
		}
		return description;
	}
	
	protected MBeanAttributeInfo[] mapAttributes(final AttributeSetBuilder asb)
			throws OperationsException{
		final MBeanAttributeInfo[] attributes = new MBeanAttributeInfo[asb.attributeSets.size()];
		int attributeIdx = 0;
		for(final Map.Entry<String, AttributeSet> ase : asb.attributeSets.entrySet()){
			final AttributeSet as = ase.getValue();
			
			MBeanDescription descAtt = null;
			if(as.getter != null){
				descAtt = as.getter.getAnnotation(MBeanDescription.class);
			}
			if(as.setter != null){
				final MBeanDescription setDescAtt = as.setter.getAnnotation(MBeanDescription.class);
				if(setDescAtt != null){
					if(descAtt != null){
						throw new NotCompliantMBeanException(MBeanDescription.class.getName()
							+ " must be set on getter when both getter and setter exist.");
					}
					descAtt = setDescAtt;
				}
			}
			
			final String description = descAtt == null ? ase.getKey() : descAtt.value();
			
			// This MBeanAttributeInfo constructor does a bit of additional rule checking.
			attributes[attributeIdx++] = new MBeanAttributeInfo(ase.getKey(), description, as.getter, as.setter);
		}
		return attributes;
	}
	
	protected MBeanOperationInfo mapOperation(final Method m, final String name, final Class<?>[] paramTypes)
			throws OperationsException{
		String description = name;
		final MBeanParameterInfo[] signature = new MBeanParameterInfo[paramTypes.length];
		int impact = MBeanOperationInfo.UNKNOWN;
		
		final MBeanDescription descAtt = m.getAnnotation(MBeanDescription.class);
		if(descAtt != null){
			description = descAtt.value();
		}
		
		final MBeanOperation operAtt = m.getAnnotation(MBeanOperation.class);
		boolean signaturePopulated = false;
		if(operAtt != null){
			impact = operAtt.impact();
			final MBeanParameter[] paramAtts = operAtt.value();
			if(paramAtts.length > 0){
				if(paramAtts.length != signature.length){
					throw new NotCompliantMBeanException(MBeanParameter.class.getName()
						+ " length of " + paramAtts.length + " does not match signature: " + m + ".");
				}
				for(int i=0; i < paramTypes.length; i++){
					final MBeanParameter paramAtt = paramAtts[i];
					String paramDesc = paramAtt.description();
					if(paramDesc == null || paramDesc.trim().length() == 0){
						paramDesc = paramAtt.value();
					}
					signature[i] = new MBeanParameterInfo(
						paramAtt.value(), paramTypes[i].getName(), paramDesc);
				}
				signaturePopulated = true;
			}
		}
		if(!signaturePopulated){
			for(int i=0; i < paramTypes.length; i++){
				final String paramName = "p" + (i + 1);
				signature[i] = new MBeanParameterInfo(
					paramName, paramTypes[i].getName(), paramName);
			}
		}
		
		return new MBeanOperationInfo(
			name, description,
			signature, m.getReturnType().getName(), impact);
	}
	
	protected static class AttributeSet{
		public Method getter;
		public Method setter;
	}
	
	protected static class AttributeSetBuilder{
		public final Map<String, AttributeSet> attributeSets = new HashMap<String, AttributeSet>();
		
		public AttributeSet get(final String name){
			AttributeSet s = attributeSets.get(name);
			if(s == null){
				s = new AttributeSet();
				attributeSets.put(name, s);
			}
			return s;
		}
		
		public void setGetter(final String name, final Method getter) throws OperationsException{
			final AttributeSet s = get(name);
			if(s.getter != null){
				throw new NotCompliantMBeanException("Conflicting getters for attribute " + name);
			}
			s.getter = getter;
		}
	}
	
}