A simple java JMX bean

JMX has fallen out of fashion a little recently. But it’s still a great way of getting live data from deep inside your Java application at runtime. Create a JMX bean, run up the app, launch Mission Control, and there’s your data. There are other ways of doing this sort of thing, Spring analytics etc. But here is some code for managing tabular data in a Java JMX Bean:


import java.lang.management.ManagementFactory;
import java.util.HashMap;
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.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenMBeanAttributeInfoSupport;
import javax.management.openmbean.OpenMBeanConstructorInfoSupport;
import javax.management.openmbean.OpenMBeanInfoSupport;
import javax.management.openmbean.OpenMBeanOperationInfoSupport;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import javax.management.openmbean.TabularData;
import javax.management.openmbean.TabularDataSupport;
import javax.management.openmbean.TabularType;
/**
 * Example DynamicMBean using Open MBean types
 * Run the main method then open JMC (java mission control) or JConsole etc
 * Find this bean and see how it appears
 * Be aware that the main method will run forever, so make sure you run it in a way that you can close it (in a separate console, or in eclipse etc.)
 * @author richard.senior@gmail.com
 */
public class ComplexTypeExampleMBean implements DynamicMBean {
	/** This will hold the data we're actually going to expose to JMX **/
	private Map>data = new HashMap>();
	/** these hold the names and descriptions that will be seen in Mission Control, or JConsole etc. **/
	private static String itemsTableName = "items";
	private static String itemsTableDescription = "a table of items";
	private static String[] attributeNames = {"item","subitems"};
	private static String[] attributeDescriptions = {"an item", "a collection of sub-items"};	
	private static TabularType itemsType,subItemsType;
	private static CompositeType itemRowType,subItemRowType;	
	private static String subItemsTableName = "subitems";
	private static String subItemsTableDescription = "a table of sub-items";
	private static String[] subAttributeNames = {"subitem","subitem_value"};
	private static String[] subAttributeDescriptions = {"an sub-item", "the value of this sub-item"};
	private static OpenType[] subAttributeTypes = {SimpleType.STRING,SimpleType.STRING};
	//we have to do this because for some bizarre reason the constructors of these types throw checked exceptions
	static {		
		try {
			subItemRowType = new CompositeType(
					subAttributeNames[0],
					subAttributeDescriptions[0],
					subAttributeNames,
					subAttributeDescriptions,
					subAttributeTypes				
			);
			subItemsType = new TabularType (
					subItemsTableName,
					subItemsTableDescription,
					subItemRowType,
					new String[] {subAttributeNames[0]}				
			);				
			itemRowType = new CompositeType(
					attributeNames[0],
					attributeDescriptions[0],
					attributeNames,
					attributeDescriptions,
					new OpenType[] {SimpleType.STRING,subItemsType}
			);	
			itemsType = new TabularType (
					itemsTableName,
					itemsTableDescription,
					itemRowType,
					new String[] {attributeNames[0]}					
			);			
		} catch (OpenDataException e) {
			throw new RuntimeException("failed to create necessary open jmx types");
		}
	}

	private TabularData getItems() throws OpenDataException {
		TabularDataSupport items = new TabularDataSupport(itemsType);
		for (Map.Entry>e:data.entrySet()) {
			//Create the sub-tems Table
			TabularDataSupport subItems = new TabularDataSupport(subItemsType);
			for (Map.Entryse:e.getValue().entrySet()) {
				CompositeData subItemrow = new CompositeDataSupport(				
						subItemRowType,
						subAttributeNames,
						new Object[] {se.getKey(),se.getValue()}
					);
				subItems.put(subItemrow);				
			}
			//now create the item itself
			CompositeData row = new CompositeDataSupport(				
					itemRowType,
					attributeNames,
					new Object[] {e.getKey(),subItems}
				);
			items.put(row);
		}								
		return items;
	}
	
	@Override
	public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {
		if (attribute==null||attribute.length()==0) throw new AttributeNotFoundException("no attribute matching that name");
		if (itemsTableName.equals(attribute)) {
			try {
				return getItems();
			} catch (OpenDataException e) {
				throw new MBeanException(e);
			}
		} else {
			throw new AttributeNotFoundException("no attribute matching the name " + attribute);
		}
	}
	
	/**
	 * {@inheritDoc}
	 * This is boilerplate code, which is frustrating 
	 */
	@Override
	public AttributeList getAttributes(String[] attributes) {
		AttributeList list = new AttributeList();
		if (attributes==null||attributes.length==0) return list;		
		for (int i = 0; i < attributes.length; i++) {
		    String name = attributes[i];
		    try {
				list.add(getAttribute(name));
			} catch (Exception e) {}
		}
		return list;
	}
	
	@Override
	public MBeanInfo getMBeanInfo() {
		try {										
			OpenMBeanInfoSupport info = new OpenMBeanInfoSupport(
					 this.getClass().getName(),
					 itemsTableDescription, 
			         new OpenMBeanAttributeInfoSupport[] {new OpenMBeanAttributeInfoSupport(itemsTableName,itemsTableDescription,itemsType,true, false, false)},	
			         new OpenMBeanConstructorInfoSupport[0],
			         new OpenMBeanOperationInfoSupport[0],
			         new MBeanNotificationInfo[0]); 
			return info;
		} catch (Throwable t) {
			throw new RuntimeException("failed to get mbean info!");
		}
	}

	/**
	 * This method can be called to actually register the MBean @see #Main(String[])
	 * You can potentially call this method at the end of your constructor, but if you're using Spring etc.
	 * it's probably safer to call this method after you're sure that everything is properly instantiated
	 */
	public void registerMBean() {
		MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
		try {
			String name = this.getClass().getName() + ":name=" + this.getClass().getSimpleName(); 
			mbs.registerMBean((Object) this, new ObjectName(name));
		} catch (Exception e) {
			throw new RuntimeException("failed to register the mbean",e);
		}
	}

	//These three methods allow interaction. We're not interested in that we just want to expose some read only data. So we can ignore these methods.
	@Override
	public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {}	
	@Override
	public AttributeList setAttributes(AttributeList attributes) {return null;}
	@Override
	public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException {return null;}
			
	public static void main(final String[] args) throws Exception {
		ComplexTypeExampleMBean b = new ComplexTypeExampleMBean();
		MapsubItems = new HashMap();
		subItems.put("one","1");
		subItems.put("two","2");
		b.data.put("numbers",subItems);
		subItems = new HashMap();
		subItems.put("black","000000");
		subItems.put("white","FFFFFF");
		b.data.put("colours",subItems);
		
		b.registerMBean();
		
		while (1==1) {
			try {Thread.sleep(10000l);} catch (Throwable t) {}
		}
	}
}