OpenMBeans, rocket science from the 70s

(Sorry for the messed up layout, Scribefire destroied the post and I had to fix it manually)

Nope, no Scala in here. This time it is good old java.
I was working on small project of mine to get some size information about wicket pages (for the
interested: it’s here).
One would think things like AspectJ or figuring out the best strategy for estimating object sizes would be the hard things on such a project.
I can still hear the distant laughter of Murphy.

Up to now I never had to/wanted to display table data in JMX so this was the first time tackled OpenMBeans.
To get a decent tutorial for these is a frickin nightmare. People (Developers are people, too, just to make sure …) seem to always insist on demonstrating things using a higly intricated usecase to show off their coding skills.
If it didn’t come across what I wanted to say:

IF YOU WANT TO SHOW SOME TECHNOLOGY DON’T REQUIRE ME TO UNDERSTAND YOUR F…ING USECASE.

A SIMPLE OpenMBeanExample.

OpenMBeans feel weird as the way you build them is different from anything you normally do in Java (at least as of today).
Looking at the amount of imports required for a very small OpenMBean might give you an idea on what lies ahead. That’s why I am to go through this step by step.

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.ReflectionException;
import javax.management.RuntimeOperationsException;
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;

To get started we need to implement DynamicMBean

public class PageSizeResultOpenMBean implements DynamicMBean {

The next step is to initialize all the parameters you are going to need for this simple example:

itemNames contains the names of properties representing a row in your tabular data.

private static String[] itemNames = { "page", "before", "after" };

itemDescriptions will contain the table headers for display in a JMX tool.

private static String[] itemDescriptions = { "Page class", "Before detach", "After detach" };

itemTypes defines the type of each row.

private static OpenType[] itemTypes = { SimpleType.STRING, SimpleType.LONG, SimpleType.LONG };

indexNames defines the itemName to be used to guarentee uniqueness of a row.

private static String[] indexNames = { "page" };

We are going to define the actual tabular data in the following static block:

private static TabularType pageTabularType = null;
private static CompositeType pageType = null;
 static {
    try {
       pageType = new CompositeType("page", "Page size info", itemNames,
          itemDescriptions, itemTypes);
       pageTabularType = new TabularType("pages", "List of Page Size
          results", pageType, indexNames);
    } catch (OpenDataException e) {
       throw new RuntimeException(e);
    }
 }

In this case pageType uses the definitions from above to describe a row in the table and pageTabularType defines the table.
In the constructor we are going to create a container for the content of the table, called pageData and the OpenMBeanInfoSupport object to hold the information required for the MBean server.

 private TabularDataSupport pageData;
 private OpenMBeanInfoSupport PSOMBInfo;
 public PageSizeResultOpenMBean() throws OpenDataException {
    OpenMBeanAttributeInfoSupport[] attributes =
          new OpenMBeanAttributeInfoSupport[] {
             new OpenMBeanAttributeInfoSupport( "PageInfos",
                "Page Infos sorted by class name", pageTabularType,
                true, false, false) };
    PSOMBInfo = new OpenMBeanInfoSupport(this.getClass().getName(),
          "Page Size OMB", attributes,
          new OpenMBeanConstructorInfoSupport[0],
          new OpenMBeanOperationInfoSupport[0],
          new MBeanNotificationInfo[0]);
    pageData = new TabularDataSupport(pageTabularType);
 }

In this case we are only defining one attribute, holding the tabular data, and no operations or constructors. We don’t need constructors as we are going to register an instance of the OpenMBean manually.Just a few methods left to implement.This method is going to be called from getAttribute and provides a cloned table to the client for display purposes:

public TabularData getPageInfos() {
   return (TabularData) pageData.clone();
}

getAttribute is called with an attribute name. In the constructor we defined an attribute named ‘PageInfos’. We simply check if that’s the attribute the client was asking for and return it.

public Object getAttribute(String attribute_name) throws
      AttributeNotFoundException, MBeanException, ReflectionException {

    if (attribute_name == null) {
    throw new RuntimeOperationsException(
       new IllegalArgumentException("Attribute name cannot be null"),
          "Cannot call getAttributeInfo with null attribute name");
    }
    if (attribute_name.equals("PageInfos")) {
       return getPageInfos();
    }
    throw new AttributeNotFoundException("Cannot find " +
       attribute_name + " attribute ");
 }

We are not allowing to set the attribute. Let it crash’n’burn.

public void setAttribute(Attribute attribute) throws
   AttributeNotFoundException,
   InvalidAttributeValueException,
   MBeanException,
   ReflectionException {
    throw new AttributeNotFoundException("No attribute can be set in
       this MBean");
 }

A shortcut method used by clients to get several attributes at once. In our case we are only
returning one attribute and this might never get called, but it doesn’t hurt to do a clean implementation 😉


public AttributeList getAttributes(String[] attributeNames) {
    if (attributeNames == null) {
       throw new RuntimeOperationsException(
          new IllegalArgumentException("attributeNames[] cannot be null"),
          "Cannot call getAttributes with null attribute names");
    }
    AttributeList resultList = new AttributeList();
    if (attributeNames.length == 0)
       return resultList;
    for (int i = 0; i < attributeNames.length; i++) {
       try {
          Object value = getAttribute(attributeNames[i]);
          resultList.add(new Attribute(attributeNames[i], value));
       } catch (Exception e) {
          e.printStackTrace();
       }
    }
    return (resultList);
 }
 public AttributeList setAttributes(AttributeList attributes) {
    return new AttributeList();
 }

We are not providing any operation so this methos is going to throw an exception if somebody tries to invoke one.

public Object invoke(String operationName, Object[] params, String[] signature)
   throws MBeanException, ReflectionException {
      throw new RuntimeOperationsException(
         new IllegalArgumentException("No operations defined for this
            OpenMBean"),
         "No operations defined for this OpenMBean");
}

Used by JMX-clients to get to now what we got here.

public MBeanInfo getMBeanInfo() {
    return PSOMBInfo;
}

A little internal method my aspect uses to actually ad some data to the table. This is just an example on how to modify the data stored by the MBean.

   public void addPageSizeResult(PageSizeResult pageSizeResult) {
      Object[] itemValues = {
         pageSizeResult.pageClass.getName(),
         pageSizeResult.sizeBeforeDetach,
         pageSizeResult.sizeAfterDetach
      };
      try {
         pageData.put(new CompositeDataSupport(pageType, itemNames,
            itemValues));
      } catch (OpenDataException e) {
         e.printStackTrace();
      }
   }

That’s it.

Source code available on github.