Skip to main content

A real example of a Dynamic MBean

Posted by emcmanus on November 8, 2006 at 7:38 AM PST

The JMX API includes the possibility to create "Dynamic
MBeans", whose management interface is determined at run time.
When might that be useful? Here's an example.

In the href="http://forum.java.sun.com/forum.jspa?forumID=537">JMX
forum on the Sun Developer Network, Athar href="http://forum.java.sun.com/thread.jspa?forumID=537&threadID=783791">asks
how to load a properties file in a dynamic MBean. I think
that's an excellent question, because it's exactly the example I
usually use for "Runtime" Dynamic MBeans.

What I call a "Runtime" Dynamic MBean is one whose management
interface you cannot determine by looking at the source code.
Obviously Standard MBeans aren't like this, because you can just
look at WhateverMBean.java to see what the
management interface is going to be. This is still true for
MBeans constructed using the href="http://download.java.net/jdk6/doc/api/javax/management/StandardMBean.html">StandardMBean
class, and it's also true for href="http://java.sun.com/developer/technicalArticles/J2SE/mxbeans/">MXBeans.

It isn't necessarily true for Dynamic MBeans. A Dynamic MBean
is a Java object of a class that implements the href="http://download.java.net/jdk6/doc/api/javax/management/DynamicMBean.html">DynamicMBean
interface. This interface includes a method href="http://download.java.net/jdk6/doc/api/javax/management/DynamicMBean.html#getMBeanInfo()">getMBeanInfo().
A class that implements DynamicMBean can construct the href="http://download.java.net/jdk6/doc/api/javax/management/MBeanInfo.html">MBeanInfo
object that it returns from getMBeanInfo() however it likes. It
can even return a different MBeanInfo every time it is
called!

This flexibility is almost never necessary. Nearly always,
when you create a Dynamic MBean, it is because you want to add
extra information to the MBeanInfo, or because you want to
implement the logic to get an attribute or call an operation in
some particular way. Just like href="http://weblogs.java.net/blog/emcmanus/archive/2006/10/build_your_own.html">dynamic
code generation, my advice if you are considering making a
Runtime Dynamic MBean is to think really hard about whether you
couldn't redesign things so that the interface is known at
compile time. The problem with an MBean interface only known at
run time is that it's hard for a client to interact with it.
Suppose your client wants to call href="http://download.java.net/jdk6/doc/api/javax/management/MBeanServerConnection.html#getAttribute(javax.management.ObjectName,%20java.lang.String)">getAttribute
on your MBean. The only way it can know what attributes are
available is to call href="http://download.java.net/jdk6/doc/api/javax/management/MBeanServerConnection.html#getMBeanInfo(javax.management.ObjectName)">getMBeanInfo
beforehand. If the MBean's interface can change as it is
running, even this isn't guaranteed to work!

However, there are some cases where it makes a certain amount
of sense to have a Runtime Dynamic MBean, and Athar's question
suggests one of them. Suppose you have a properties file
containing configuration for your application, and you'd like to
expose its contents for management, so that you can see the
values of configuration items, and perhaps change them as the
application is running. The obvious way to do this is to have a
ConfigurationManagementMBean that is linked to the properties
file.

Every time you change your app to add a
new configuration item, you'll need to add it to the initial
configuration file, and you'll need to add code to interpret it.
But it would be a pain to have to add a new attribute explicitly
to the ConfigurationManagementMBean as well. So this argues for
one of two approaches:

  1. The ConfigurationManagementMBean has one big attribute that
    is a href="http://download.java.net/jdk6/doc/api/java/util/Properties.html">
    Properties, say, or a
    Map<String,String> if you're using
    MXBeans. This is workable, but in practice it is very clumsy.
    In order to change a configuration item, you'll have to get
    the complete set of items, change the item within it, and
    write the result back to the MBean. Furthermore, href="http://java.sun.com/developer/technicalArticles/J2SE/jconsole.html">
    JConsole doesn't currently support changing an item in
    the middle of a Properties or Map
    attribute.

  2. The ConfigurationManagementMBean has one attribute per
    configuration item, or in other words per property. It
    determines these attributes at run time by reading the
    properties file. So it's a Runtime Dynamic MBean!

If you adopt the second approach, then JConsole looking at your
ConfigurationManagementMBean might look like this:

JConsole looking at a Runtime Dynamic MBean src="http://weblogs.java.net/blog/emcmanus/PropertyManager.png"
width="851" height="555" />

I'll present the code to implement this below. A few things
are worth noting. First of all, the DynamicMBean interface is a
little bit clunky, in particular the href="http://download.java.net/jdk6/doc/api/javax/management/DynamicMBean.html#getAttributes(java.lang.String[])">
getAttributes and (especially) href="http://download.java.net/jdk6/doc/api/javax/management/DynamicMBean.html#setAttributes(javax.management.AttributeList)">
setAttributes methods. The problem that generates this
clunkiness is what to do if one of the attributes to be set
produces an error. Should you throw an exception? If so, have
any of the other attributes been set? The cleanest solution
would be to say that setAttributes is an all-or-nothing
operation: either it sets all of the given attributes, or it
sets none of them and throws an exception. However, the
designers of the JMX API felt that this was a harsh constraint
to put on MBean writers. What's more it is not at all obvious
how it should apply to Standard MBeans. So instead,
setAttributes returns an AttributeList containing the attributes
that were actually set. The caller needs to check that this
contains all the values that were supposed to be set, and react
appropriately if not.

The code doesn't let you set a value for a property that was
not already present. The MBean Server does not check that the
attribute name in setAttribute is present in the MBeanInfo. It
is up to the MBean to do that. An MBean could choose to accept
such a name, which in this case would allow you to define new
properties. But I think it would be better to achieve that in
some other way, for example an explicit addProperty
operation.

In addition to one attribute per property, I've defined an
operation reload which reloads the properties from
the file. If there are properties in the file that were not
present before, then they will appear as new attributes. Notice
that adding an operation requires you both to mention it in
getMBeanInfo and to recognize it in
invoke. If there are many operations, you might
want to consider getting the href="http://download.java.net/jdk6/doc/api/javax/management/StandardMBean.html">StandardMBean
class to do some of the work for you.

Finally, every time you change a property the code updates the
configuration file. The way it does this is intended to be a
safe way to update a file. It writes a new properties file in
the same directory, then renames it over the original. On most
operating systems, renaming is atomic, so even if your app is
interrupted in the middle of this operation, you will end up
with either the old file or the new file, but not with a missing
or partially-written file.

package propertymanager;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Properties;
import java.util.SortedSet;
import java.util.TreeSet;
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;

public class PropertyManager implements DynamicMBean {
    private final String propertyFileName;
    private final Properties properties;
   
    public PropertyManager(String propertyFileName) throws IOException {
        this.propertyFileName = propertyFileName;
        properties = new Properties();
        load();
    }

    public synchronized String getAttribute(String name)
    throws AttributeNotFoundException {
        String value = properties.getProperty(name);
        if (value != null)
            return value;
        else
            throw new AttributeNotFoundException("No such property: " + name);
    }

    public synchronized void setAttribute(Attribute attribute)
    throws InvalidAttributeValueException, MBeanException, AttributeNotFoundException {
        String name = attribute.getName();
        if (properties.getProperty(name) == null)
            throw new AttributeNotFoundException(name);
        Object value = attribute.getValue();
        if (!(value instanceof String)) {
            throw new InvalidAttributeValueException(
                    "Attribute value not a string: " + value);
        }
        properties.setProperty(name, (String) value);
        try {
            save();
        } catch (IOException e) {
            throw new MBeanException(e);
        }
    }

    public synchronized AttributeList getAttributes(String[] names) {
        AttributeList list = new AttributeList();
        for (String name : names) {
            String value = properties.getProperty(name);
            if (value != null)
                list.add(new Attribute(name, value));
        }
        return list;
    }

    public synchronized AttributeList setAttributes(AttributeList list) {
        Attribute[] attrs = (Attribute[]) list.toArray(new Attribute[0]);
        AttributeList retlist = new AttributeList();
        for (Attribute attr : attrs) {
            String name = attr.getName();
            Object value = attr.getValue();
            if (properties.getProperty(name) != null && value instanceof String) {
                properties.setProperty(name, (String) value);
                retlist.add(new Attribute(name, value));
            }
        }
        try {
            save();
        } catch (IOException e) {
            return new AttributeList();
        }
        return retlist;
    }

    public Object invoke(String name, Object[] args, String[] sig)
    throws MBeanException, ReflectionException {
        if (name.equals("reload") &&
                (args == null || args.length == 0) &&
                (sig == null || sig.length == 0)) {
            try {
                load();
                return null;
            } catch (IOException e) {
                throw new MBeanException(e);
            }
        }
        throw new ReflectionException(new NoSuchMethodException(name));
    }
   
    public synchronized MBeanInfo getMBeanInfo() {
        SortedSet<String> names = new TreeSet<String>();
        for (Object name : properties.keySet())
            names.add((String) name);
        MBeanAttributeInfo[] attrs = new MBeanAttributeInfo[names.size()];
        Iterator<String> it = names.iterator();
        for (int i = 0; i < attrs.length; i++) {
            String name = it.next();
            attrs[i] = new MBeanAttributeInfo(
                    name,
                    "java.lang.String",
                    "Property " + name,
                    true,   // isReadable
                    true,   // isWritable
                    false); // isIs
        }
        MBeanOperationInfo[] opers = {
            new MBeanOperationInfo(
                    "reload",
                    "Reload properties from file",
                    null,   // no parameters
                    "void",
                    MBeanOperationInfo.ACTION)
        };
        return new MBeanInfo(
                this.getClass().getName(),
                "Property Manager MBean",
                attrs,
                null,  // constructors
                opers,
                null); // notifications
    }

    private void load() throws IOException {
        InputStream input = new FileInputStream(propertyFileName);
        properties.load(input);
        input.close();
    }

    private void save() throws IOException {
        String newPropertyFileName = propertyFileName + "$$new";
        File file = new File(newPropertyFileName);
        OutputStream output = new FileOutputStream(file);
        String comment = "Written by " + this.getClass().getName();
        properties.store(output, comment);
        output.close();
        if (!file.renameTo(new File(propertyFileName))) {
            throw new IOException("Rename " + newPropertyFileName + " to " +
                    propertyFileName + " failed");
        }
    }
}
Related Topics >>

Comments

Adding new properties

Hi, "If there are properties in the file that were not present before, then they will appear as new attributes." Could you please explain how is this actually supposed to work? New attributes appear where? On the JMX console? Frankly, I can't see that happening...I guess I'm missing something.. That reload operation is supposed to make the MBeanServer to call the getMBeanInfo() method of the actual DynamicMBean, ain't it? Otherwise it will never know a new atteribute has appeared.. I realize this is a fairly old post, and thing might have been changed, but still I'm looking for exactly the behavior the quoted sentence suggests me (ie. the new attribute 'pops up' on JMX console automatically, in other words, the MBeanInfo gets refreshed as result of the reload() operation.) If you could explain this in bit more detail, I'd be grateful. Cheers

Adding new properties and removing existing ones.

I am new to JMX. I am also facing the same issue when I update the property file. The behaviour that we expect is that when we invoke reload() it should update the jconsole as well. Assume that I had 4 key-value pairs in the property file initially. If I remove one of it and then invoke the reload(), the jconsole attribute shows that the deleted property is unavailable instead of removing it from the attribute list. Similarly if I add a new property into the file, it is not getting added in the jconsole. But once I disconnect and reconnect jconsole, the new properties would be displayed properly. Is this a limitation with jconsole? I think the getMBeanInfo() is not getting invoked after we invoke reload() as told in the previous comment. I even tried to invoke it explicitly in the load(). But even then the results were the same. I saw this on net: http://robey.lag.net/2009/03/29/anyone-use-jmx.html I saw some other posts like : http://weblogs.java.net/blog/2006/06/08/adding-descriptors-mbeans-tiger, http://weblogs.java.net/blog/emcmanus/archive/2005/10/adding_descript.html Can somebody suggest a solution with respect to this example.

Adding new properties and removing existing ones.

Indeed, this is a shortcoming in the existing JConsole and JVisualVM tools. For performance reasons, they cache the MBeanInfo of every MBean they display, which means that they do not notice if it subsequently changes. Ideally, they would notice when an MBean has immutableInfo=false in its Descriptor, and not cache in that case. They could ultimately also recognize the jmx.mbean.info.changed notification which will be documented in JDK7 (see MBeanInfo in the draft JDK7 javadoc).

For now, the simplest way to get JConsole or JVisualVM to react to change in the MBeanInfo is to unregister the MBean and immediately register it again.

Dynamic MBean for loading properties

Hello, very good article about using JMX as a means for loading the settings for an application. I see the most benefit from your solution when you implement the MBean to generate a notification for the application to signalize if a property changed. So whenever a change is made (e.g. through an administrator) the MBean fires and the application gets the latest entry for that property. Have you made any experiences in that way ? Regards, AgeBee

I am trying to develop this

I am trying to develop this dynamic MBean on my local workstation running Tomcat and test it there running jconsole. But for Production, it will be deployed to WebSphere.
A couple questions:
How do I deploy this thing? Do I package it in its own jar file? Do I include it with my server source?
Is it just the example code above?
No deployment descriptors?
What configuration files need to be modified for Tomcat?
Do I need to deploy an mbeans-descriptor.xml? And if so, what goes in there?
Thanks,
Kevin