Implementing DynamicMBean interface

Introduction
Using JMX (Java Management eXtensions) to manage part of your application is a great java feature. The standard Mbeans mechanisms which consist of creating a classwhich implements an MBean interface is q uite simple and already allows to manage most of your application. In this article, I will describe how can we implements the DynamicMBean interface to do more 'complex' things. Especially, we will look on how we can we expose and modify a set of properties, and how can we expose methods by specifiing their names (The java code will be more clear than my poor english). The following examples are taken from the work I have done on the PEtALS CDK (Component Development Kit) in order to expose a set of JBI extensions and bootstrap methods for management.

How to expose a set of properties?
The standards MBeans automatically expose the setters and getters methods as MBean attribu tes. Imagine that you have a set of properties (a HashMap/Properties...). Ho can you manage this object, how can you w change a value in the set? Simply by creating an implementation of the DynamicMBean interface. The following code snip pet shows how we can do such a thing.

/* * (non-Javadoc) * * @see javax.management.DynamicMBean#getAttribute(java.lang.String) */ public synchronized Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException { String value = managedBootstrap.getComponentPropertiesManager() .getProperties().getProperty(attribute); if (value != null && !value .startsWith(ComponentPropertiesManager.MANAGED_METHOD_PREFIX)) { return value; } else { throw new AttributeNotFoundException("Attribute '" + attribute + "' does not exists");

} } /* * (non-Javadoc) * * @see javax.management.DynamicMBean#getAttributes(java.lang.String[]) */ public synchronized AttributeList getAttributes(String[] attributes) { AttributeList list = new AttributeList(); for (String name : attributes) { String value = managedBootstrap.getComponentPropertiesManager() .getProperties().getProperty(name); if (value != null && !value .startsWith(ComponentPropertiesManager.MANAGED_METHOD_PREFIX)) list.add(new Attribute(name, value)); } return list; } /* * (non-Javadoc) * * @see javax.management.DynamicMBean#setAttribute(javax.management.Attribute) */ public synchronized void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { String name = attribute.getName(); if (managedBootstrap.getComponentPropertiesManager().getProperties() .getProperty(name) == null) {

throw new AttributeNotFoundException(name); } Object value = attribute.getValue(); if (!(value instanceof String)) { throw new InvalidAttributeValueException( "Attribute value not a string: " + value); } // set the property and save the properties file managedBootstrap.getComponentPropertiesManager().getProperties() .setProperty(name, (String) value); try { save(); } catch (IOException e) { throw new MBeanException(e); } } /* * (non-Javadoc) * * @see javax.management.DynamicMBean#setAttributes(javax.management.AttributeList) */ public synchronized AttributeList setAttributes(AttributeList list) { AttributeList result = new AttributeList(); for (Object object : list) { Attribute attr = (Attribute) object; String name = attr.getName(); Object value = attr.getValue(); if (managedBootstrap.getComponentPropertiesManager()

.getProperties().getProperty(name) != null && value instanceof String) { managedBootstrap.getComponentPropertiesManager() .getProperties().setProperty(name, (String) value); result.add(new Attribute(name, value)); } } try { save(); } catch (IOException e) { return new AttributeList(); } return result; }

The previous methods are the ones which are called by the JMX implementation for management. In our case it is quite simple since a property entry is directly mapped into an attribute... Note 1 : The managedBootstrap attribute is a class which contains a Propertie object. s Note 2 : We persist the properties in a file each ti e a property is modified. m In order to expose these attributes, we will see later that we need to create a set of MBeanAttributeInfo that will be used by the getMBeanInfo method to create the information object used by JMX to expose all of this. /** * Dynamically get attributes for the MBean. The attributes are the ones * from the properties file. * * @return */ protected MBeanAttributeInfo[] getAttributesInfos() { // get the attributes names as sorted strings Set<String> attributeKeys = managedBootstrap .getComponentPropertiesManager().getExposedAttributeKeys();

// build the MBeanAttributeInfo MBeanAttributeInfo[] attrs = new MBeanAttributeInfo[attributeKeys .size()]; Iterator<String> it = attributeKeys.iterator(); for (int i = 0; i < attrs.length; i++) { String name = it.next(); attrs[i] = new MBeanAttributeInfo(name, "java.lang.String", "Property " + name, true, true, false); } return attrs; }

How to expose a set of methods? Building the MBeanInfo
In the previous chapter, I have introduced how we can dynamically expose properties. Now we will focus on how we can choose the methods to expose through JMX. There are several methods to do this. Here I have choosen to expose only the public methods that are programatically defined in a list. I have choosen this simple way to allow the component developer to simply define the methods he wants to expose by returning a list of methods names... Simple and efficient! /** * Dynamically get the operations for the MBean. The operations are defined * in the properties manager with a special prefix. * * @return */ protected MBeanOperationInfo[] getOperationsInfos() { List<String> methods = managedBootstrap.getComponentPropertiesManager() .getExposedMethodNames(); MBeanOperationInfo[] result = null;

List<MBeanOperationInfo> operations = new ArrayList<MBeanOperationInfo>(); Method methodList[] = managedBootstrap.getClass().getMethods(); for (int i = 0; i < methodList.length; i++) { Method method = methodList[i]; String name = method.getName(); // add method if it has been defined by the user if (methods.contains(name)) { MBeanOperationInfo oper = new MBeanOperationInfo( "Operation description", method); operations.add(oper); } } result = new MBeanOperationInfo[operations.size()]; operations.toArray(result); return result; }

This time again, we are are creating a set of MBeanOp erationInfo from a list methods names. We retrieve the methods from the class we want to manage from the managedBootstrap class by reflection. This method will be then used to create the MBeanInfo that is returned by the method getMBeanInfo: /* * (non-Javadoc) * * @see javax.management.DynamicMBean#getMBeanInfo() */ public synchronized MBeanInfo getMBeanInfo() { // get the attributes MBeanAttributeInfo[] attrs = getAttributesInfos(); // get the operations

MBeanOperationInfo[] opps = getOperationsInfos(); // create MBeanInfo with no constructors and no notifications return new MBeanInfo(this.getClass().getName(), "Property Manager MBean", attrs, null, opps, null); }

Invoking a method
Doing java reflection is out of this article scope. Have a look on the implementation to see how it is done.

Complete implementation
/** * PETALS - PETALS Services Platform. * Copyright (c) 2007 EBM Websourcing, http://www.ebmwebsourcing.com/ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * * ------------------------------------------------------------------------02111-1307 USA See the GNU

* $Id$ * ------------------------------------------------------------------------*/ package org.objectweb.petals.component.common; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.AttributeNotFoundException; import javax.management.DynamicMBean; import javax.management.InvalidAttributeValueException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; import javax.management.ReflectionException; import javax.management.RuntimeOperationsException; /** * The MBean used to expose component attributes and operations via JMX. * * @author Christophe HAMERLING - eBMWebSourcing * */

public class ExtensionMBean implements DynamicMBean { private ManagedBootstrap managedBootstrap; private final static Hashtable<String, Class<?>> primitiveClasses = new Hashtable<String, Class<?>>( 8); { primitiveClasses.put(Boolean.TYPE.toString(), Boolean.TYPE); primitiveClasses.put(Character.TYPE.toString(), Character.TYPE); primitiveClasses.put(Byte.TYPE.toString(), Byte.TYPE); primitiveClasses.put(Short.TYPE.toString(), Short.TYPE); primitiveClasses.put(Integer.TYPE.toString(), Integer.TYPE); primitiveClasses.put(Long.TYPE.toString(), Long.TYPE); primitiveClasses.put(Float.TYPE.toString(), Float.TYPE); primitiveClasses.put(Double.TYPE.toString(), Double.TYPE); } /** * Creates a new instance of {@link ExtensionMBean} * * @param bootstrap */ public ExtensionMBean(ManagedBootstrap bootstrap) { this.managedBootstrap = bootstrap; } /* * (non-Javadoc) * * @see javax.management.DynamicMBean#getAttribute(java.lang.String) */ public synchronized Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {

String value = managedBootstrap.getComponentPropertiesManager() .getProperties().getProperty(attribute); if (value != null && !value .startsWith(ComponentPropertiesManager.MANAGED_METHOD_PREFIX)) { return value; } else { throw new AttributeNotFoundException("Attribute '" + attribute + "' does not exists"); } } /* * (non-Javadoc) * * @see javax.management.DynamicMBean#getAttributes(java.lang.String[]) */ public synchronized AttributeList getAttributes(String[] attributes) { AttributeList list = new AttributeList(); for (String name : attributes) { String value = managedBootstrap.getComponentPropertiesManager() .getProperties().getProperty(name); if (value != null && !value .startsWith(ComponentPropertiesManager.MANAGED_METHOD_PREFIX)) list.add(new Attribute(name, value)); } return list; } /*

* (non-Javadoc) * * @see javax.management.DynamicMBean#setAttribute(javax.management.Attribute) */ public synchronized void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { String name = attribute.getName(); if (managedBootstrap.getComponentPropertiesManager().getProperties() .getProperty(name) == null) { throw new AttributeNotFoundException(name); } Object value = attribute.getValue(); if (!(value instanceof String)) { throw new InvalidAttributeValueException( "Attribute value not a string: " + value); } // set the property and save the properties file managedBootstrap.getComponentPropertiesManager().getProperties() .setProperty(name, (String) value); try { save(); } catch (IOException e) { throw new MBeanException(e); } } /* * (non-Javadoc) * * @see javax.management.DynamicMBean#setAttributes(javax.management.AttributeList)

*/ public synchronized AttributeList setAttributes(AttributeList list) { AttributeList result = new AttributeList(); for (Object object : list) { Attribute attr = (Attribute) object; String name = attr.getName(); Object value = attr.getValue(); if (managedBootstrap.getComponentPropertiesManager() .getProperties().getProperty(name) != null && value instanceof String) { managedBootstrap.getComponentPropertiesManager() .getProperties().setProperty(name, (String) value); result.add(new Attribute(name, value)); } } try { save(); } catch (IOException e) { return new AttributeList(); } return result; } /* * (non-Javadoc) * * @see javax.management.DynamicMBean#getMBeanInfo() */ public synchronized MBeanInfo getMBeanInfo() { // get the attributes

MBeanAttributeInfo[] attrs = getAttributesInfos(); // get the operations MBeanOperationInfo[] opps = getOperationsInfos(); // create MBeanInfo with no constructors and no notifications return new MBeanInfo(this.getClass().getName(), "Property Manager MBean", attrs, null, opps, null); } /** * Dynamically get the operations for the MBean. The operations are defined * in the properties manager with a special prefix. * * @return */ protected MBeanOperationInfo[] getOperationsInfos() { List<String> methods = managedBootstrap.getComponentPropertiesManager() .getExposedMethodNames(); MBeanOperationInfo[] result = null; List<MBeanOperationInfo> operations = new ArrayList<MBeanOperationInfo>(); Method methodList[] = managedBootstrap.getClass().getMethods(); for (int i = 0; i < methodList.length; i++) { Method method = methodList[i]; String name = method.getName(); // add method if it has been defined by the user if (methods.contains(name)) { MBeanOperationInfo oper = new MBeanOperationInfo( "Operation description", method); operations.add(oper); } }

result = new MBeanOperationInfo[operations.size()]; operations.toArray(result); return result; } /** * Dynamically get attributes for the MBean. The attributes are the ones * from the properties file. * * @return */ protected MBeanAttributeInfo[] getAttributesInfos() { // get the attributes names as sorted strings Set<String> attributeKeys = managedBootstrap .getComponentPropertiesManager().getExposedAttributeKeys(); // build the MBeanAttributeInfo MBeanAttributeInfo[] attrs = new MBeanAttributeInfo[attributeKeys .size()]; Iterator<String> it = attributeKeys.iterator(); for (int i = 0; i < attrs.length; i++) { String name = it.next(); attrs[i] = new MBeanAttributeInfo(name, "java.lang.String", "Property " + name, true, true, false); } return attrs; } /* * (non-Javadoc) *

* @see javax.management.DynamicMBean#invoke(java.lang.String, * */ public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException { Object result = null; final Class objClass = managedBootstrap.getClass(); final ClassLoader loader = objClass.getClassLoader(); final Class[] parameters = ((signature == null) ? null : findSignatureClasses(signature, loader)); // Query the metadata service to get the right method // Method method = null; try { method = objClass.getMethod(actionName, parameters); } catch (Exception e) { } if (method == null) { throw new ReflectionException( new NoSuchMethodException(actionName), "The operation with name " + actionName + " could not be found"); } try { // invoke method result = method.invoke(managedBootstrap, params); } catch (IllegalAccessException e) { throw new ReflectionException(e, "IllegalAccessException" + " occured trying to invoke operation " + actionName); } catch (RuntimeException e) { java.lang.Object[], java.lang.String[])

throw new RuntimeOperationsException(e, "RuntimeException" + " occured trying to invoke operation " + actionName); } catch (InvocationTargetException e) { throw new MBeanException(e, "Exception thrown in operation " + actionName); } return result; } /** * Find the classes of the signature * * @param signature * @param loader * @return * @throws ReflectionException */ private Class[] findSignatureClasses(String signature[], ClassLoader loader) throws ReflectionException { if (signature == null) return null; final int length = signature.length; final Class tab[] = new Class[length]; if (length == 0) return tab; try { for (int i = 0; i < length; i++) { // try to get the primitive class final Class primCla = (Class) primitiveClasses .get(signature[i]);

if (primCla != null) { tab[i] = primCla; } else { // try to get the class if not primitive one tab[i] = Class.forName(signature[i], false, loader); } } } catch (ClassNotFoundException e) { throw new ReflectionException(e, "The parameter class could not be found"); } catch (RuntimeException e) { throw e; } return tab; } /** * Save the properties to a file * * @throws IOException */ private void save() throws IOException { managedBootstrap.getComponentPropertiesManager().save(); }

About
I am a software engineer and community manager on the open source PEtALS ESB proj ct at eBM e WebSourcing. You can get this article as PDF file on my scribd space or on my work blog.