SimpleMBean.java
package com.ziesemer.utils.jmx.server;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.JMException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.ReflectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>A high-performance, "simpler" replacement to
* {@link javax.management.StandardMBean}.</p>
* <p>Unlike {@link javax.management.StandardMBean}, this class does not implement
* {@link javax.management.MBeanRegistration}. However, there is nothing
* to prevent a subclass from doing so, which is encouraged if necessary.</p>
* <p>This class is written rather "fail-fast".
* {@link Method}s for getters, setters, and operations are all initialized
* during instance creation, then reused for performance over the lifetime of the bean.
* Any exceptions thrown during this process will be thrown immediately,
* causing the constructor to fail and no instance created.
* This is preferred over having a partially-functional bean.</p>
* @author Mark A. Ziesemer
* <a href="http://www.ziesemer.com."><www.ziesemer.com></a>
*/
public class SimpleMBean implements DynamicMBean{
protected static final Logger LOGGER = LoggerFactory.getLogger(SimpleMBean.class);
// Similar to code used in java.io.ObjectInputStream.
// http://java.sun.com./docs/books/tutorial/java/nutsandbolts/datatypes.html
protected static final Map<String, Class<?>> PRIMITIVE_CLASSES;
static{
final List<Class<?>> pcl = Arrays.<Class<?>>asList(
boolean.class, byte.class, char.class, short.class, int.class,
long.class, float.class, double.class, void.class);
final Map<String, Class<?>> pc = new HashMap<String, Class<?>>(pcl.size(), 1.0F);
for(final Class<?> c : pcl){
pc.put(c.getName(), c);
}
PRIMITIVE_CLASSES = Collections.unmodifiableMap(pc);
}
protected Object implementation;
protected final Class<?> mBeanInterface;
protected MBeanInfo mBeanInfo;
protected final Map<String, Method> getters = new HashMap<String, Method>();
protected final Map<String, Method> setters = new HashMap<String, Method>();
/**
* Map Tree: method name, # of parameters.
*/
protected final Map<String, Map<Integer, List<MethodAndSig>>> operations
= new HashMap<String, Map<Integer, List<MethodAndSig>>>();
public <T> SimpleMBean(final Object implementation, final Class<T> mBeanInterface) throws JMException{
this.mBeanInterface = mBeanInterface;
this.mBeanInfo = buildMBeanInfo();
setImplementation(implementation);
buildAttributes();
buildOperations();
}
protected MBeanInfo buildMBeanInfo() throws JMException{
return new MBeanInfoBuilder().build(mBeanInterface);
}
protected void buildAttributes() throws JMException{
try{
for(final MBeanAttributeInfo attribute : mBeanInfo.getAttributes()){
final String name = attribute.getName();
if(attribute.isReadable()){
getters.put(name,
mBeanInterface.getMethod(
attribute.isIs() ? "is" + name : "get" + name));
}
if(attribute.isWritable()){
setters.put(name,
mBeanInterface.getMethod(
"set" + name, new Class[]{getClassFromType(attribute.getType())}));
}
}
}catch(final ClassNotFoundException cnfe){
throw new ReflectionException(cnfe);
}catch(final NoSuchMethodException nsme){
throw new ReflectionException(nsme);
}
}
protected void buildOperations() throws JMException{
try{
for(final MBeanOperationInfo operation : mBeanInfo.getOperations()){
final String actionName = operation.getName();
final MBeanParameterInfo[] beanSignature = operation.getSignature();
final int signatureLength = beanSignature.length;
final Class<?>[] signature = new Class[signatureLength];
final String[] signatureStr = new String[signatureLength];
for(int i=0; i < signatureLength; i++){
final String typeName = beanSignature[i].getType();
signature[i] = getClassFromType(typeName);
signatureStr[i] = typeName;
}
final MethodAndSig mas = new MethodAndSig();
mas.method = mBeanInterface.getMethod(actionName, signature);
mas.signature = signatureStr;
Map<Integer, List<MethodAndSig>> ol = operations.get(actionName);
if(ol == null){
ol = new HashMap<Integer, List<MethodAndSig>>();
operations.put(actionName, ol);
}
List<MethodAndSig> olc = ol.get(signatureLength);
if(olc == null){
olc = new LinkedList<MethodAndSig>();
ol.put(signatureLength, olc);
}
olc.add(mas);
}
}catch(final ClassNotFoundException cnfe){
throw new ReflectionException(cnfe);
}catch(final NoSuchMethodException nsme){
throw new ReflectionException(nsme);
}
}
public MBeanInfo getMBeanInfo(){
return mBeanInfo;
}
public Object getAttribute(final String attribute)
throws AttributeNotFoundException, MBeanException, ReflectionException{
final Method m = getters.get(attribute);
if(m == null){
final AttributeNotFoundException anfe = new AttributeNotFoundException(
"\"" + attribute + "\" not read accessible.");
logClientException(anfe.getMessage());
throw anfe;
}
try{
return m.invoke(implementation);
}catch(final InvocationTargetException ite){
throw new ReflectionException(ite);
}catch(final IllegalAccessException iae){
throw new ReflectionException(iae);
}
}
public AttributeList getAttributes(final String[] attributes){
final AttributeList result = new AttributeList(attributes.length);
for(final String attribute : attributes){
try{
result.add(new Attribute(attribute, getAttribute(attribute)));
}catch(final AttributeNotFoundException anfe){
// Already logged by child call.
}catch(final Exception ex){
LOGGER.warn(ex.getMessage(), ex);
}
}
return result;
}
public void setAttribute(final Attribute attribute)
throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException{
if(attribute == null){
throw new IllegalArgumentException("attribute cannot be null.");
}
final Method m = setters.get(attribute.getName());
if(m == null){
final AttributeNotFoundException anfe = new AttributeNotFoundException(
"\"" + attribute.getName() + "\" not write accessible.");
logClientException(anfe.getMessage());
throw anfe;
}
try{
m.invoke(implementation, attribute.getValue());
}catch(final InvocationTargetException ite){
throw new ReflectionException(ite);
}catch(final IllegalAccessException iae){
throw new ReflectionException(iae);
}
}
public AttributeList setAttributes(final AttributeList attributes){
final AttributeList result = new AttributeList(attributes.size());
for(final Object attributeObj : attributes){
// AttributeList should use generics when extending ArrayList.
final Attribute attribute = (Attribute)attributeObj;
try{
setAttribute(attribute);
result.add(attribute);
}catch(final AttributeNotFoundException anfe){
// Already logged by child call.
}catch(final Exception ex){
LOGGER.warn(ex.getMessage(), ex);
}
}
return result;
}
public Object invoke(final String actionName, final Object[] params, final String[] signature)
throws MBeanException, ReflectionException{
final Map<Integer, List<MethodAndSig>> ol = operations.get(actionName);
if(ol == null){
throw createNoSuchMethodReflectionException(
"The operation with name \"" + actionName + "\" could not be found.");
}
final int signatureLength = signature == null ? 0 : signature.length;
final List<MethodAndSig> olc = ol.get(signatureLength);
if(olc == null){
throw createNoSuchMethodReflectionException(
"The operation with name \"" + actionName + "\" and " + signatureLength + " parameters could not be found.");
}
Method m = null;
for(final MethodAndSig mas : olc){
if(Arrays.equals(mas.signature, signature)
|| (signatureLength == 0 && mas.signature.length == 0)){
m = mas.method;
break;
}
}
if(m == null){
throw createNoSuchMethodReflectionException(
"The operation with name \"" + actionName + "\" and signature "
+ (signature == null ? null : Arrays.asList(signature)) + " could not be found.");
}
try{
return m.invoke(implementation, params);
}catch(final InvocationTargetException ite){
LOGGER.warn(ite.toString(), ite);
throw new ReflectionException(ite, ite.getMessage());
}catch(final IllegalAccessException iae){
LOGGER.warn(iae.toString(), iae);
throw new ReflectionException(iae, iae.getMessage());
}
}
protected ReflectionException createNoSuchMethodReflectionException(final String msg){
final NoSuchMethodException nsme = new NoSuchMethodException(msg);
logClientException(msg);
return new ReflectionException(nsme, msg);
}
public Object getImplementation(){
return implementation;
}
public void setImplementation(final Object implementation){
if(!mBeanInterface.isInstance(implementation)){
if(implementation == null){
throw new IllegalArgumentException("Implementation cannot be null.");
}
throw new IllegalArgumentException("Implementation must match interface.");
}
this.implementation = implementation;
// Update implementation class name in MBeanInfo
this.mBeanInfo = new MBeanInfo(implementation.getClass().getName(),
mBeanInfo.getDescription(), mBeanInfo.getAttributes(),
mBeanInfo.getConstructors(), mBeanInfo.getOperations(),
mBeanInfo.getNotifications());
}
public Class<?> getMBeanInterface(){
return mBeanInterface;
}
protected Class<?> getClassFromType(final String name) throws ClassNotFoundException{
final Class<?> c = PRIMITIVE_CLASSES.get(name);
if(c != null){
return c;
}
return Class.forName(name);
}
protected void logClientException(final Object msg){
LOGGER.warn("{}: {}", implementation.getClass().getName(), msg);
}
protected static class MethodAndSig{
public Method method;
public String[] signature;
}
}