The Source for Java Technology Collaboration
User: Password:



Eamonn McManus

Eamonn McManus's Blog

A real example of a Dynamic MBean

Posted by emcmanus on November 08, 2006 at 07:38 AM | Comments (8)

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 JMX forum on the Sun Developer Network, Athar 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 StandardMBean class, and it's also true for MXBeans.

It isn't necessarily true for Dynamic MBeans. A Dynamic MBean is a Java object of a class that implements the DynamicMBean interface. This interface includes a method getMBeanInfo(). A class that implements DynamicMBean can construct the 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 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 getAttribute on your MBean. The only way it can know what attributes are available is to call 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 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, 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

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 getAttributes and (especially) 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 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");
        }
    }
}

Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Thanks Eamonn.. This was exactly the one i was looking for. Infact to load the properties file dynamically, so that it can be reloaded when the properties file is edited when exposed by the Mbean. Though i'd nt specified what exactly i wanted, you could get what i intended to tell.. thanks a lot !
    Athar

    Posted by: athahar on November 09, 2006 at 02:00 AM

  • Hi Eamonn,
    I wonder if you could help me with this:
    I created a dynamic MBean similar to the one you are presenting here, and I registered in a local MBean server.
    Then, from a remote machine, I opened an RMI MBeanServerConnection to that MBean server, and created a proxy to the dynamic MBean (using the MBeanServerInvocationHandler.newProxyInstance() method).
    In that remote machine, I also have a remote MBean server, and I'd like to register the proxy as an MBean in that server. The problem is how to obtain the interface for the dynamic MBean at runtime. I tried to get it using mbsc.getMBeanInfo(MBeanName) , but this does not return an interface that I can use in server.registerMBean() method.
    Could you provide any hint ?
    Thanks,
    Jaime.

    Posted by: jimbojava on January 22, 2007 at 08:37 AM

  • jimbojava, the short answer is that you can't register the object you get from MBeanServerInvocationHandler.newProxyInstance (or JMX.newMBeanProxy in Java 6) in the MBean Server. Instead you need to use a class something like this:


    public class DynamicMBeanProxy implements DynamicMBean {
    private final MBeanServerConnection mbsc;
    private final ObjectName objectName;

    /** Creates a new instance of DynamicMBeanProxy */
    public DynamicMBeanProxy(MBeanServerConnection mbsc, ObjectName objectName) {
    this.mbsc = mbsc;
    this.objectName = objectName;
    }

    public Object getAttribute(String name)
    throws AttributeNotFoundException, MBeanException, ReflectionException {
    try {
    return mbsc.getAttribute(objectName, name);
    } catch (IOException e) {
    throw new MBeanException(e);
    } catch (InstanceNotFoundException e) {
    throw new MBeanException(e);
    }
    }

    ...other methods from DynamicMBean similarly...
    }


    This should work in the simple case you are talking about, but there are interesting issues with performance and notifications. I am planning to write a blog entry with the full story.

    Posted by: emcmanus on January 22, 2007 at 10:01 AM

  • Thank you, that makes a lot of sense.
    And thank you also for mentioning the performance issues, it is my main concern here.
    I'm trying to use a "master" MBean server to centralize management of MBeans from "subordinate" MBean servers. The idea is to manage a client application (always the same, an application all employees use), running on desktop/laptop computers. That application would contain the embedded subordinated MBean server. So the subordinate MBean servers can be of the order of thousands, each having just one or two MBeans (the same MBeans, for example the PropertyManager), and the master MBean server would ask for values of certain properties to produce statistics, or would set the value of the properties, or it could launch a window in the application to notify all employees about some specific issue, etc.

    Thanks again,
    Jaime.

    Posted by: jimbojava on January 22, 2007 at 02:41 PM

  • Hi Eamonn, I wonder if you could help me with this: - This example deals with property file. What if I want to achieve the same thing with XML file. Problem is that in my configuration.xml there are sub nodes used. I have api for reading that xml file but there is no generic method like getPropertyByName() so that I can place it in dynamic mbean. So my question is: how to handle such situation where you have different methods to read the items from main nodes and subnodes??
    Will it be necessary to have a method of kind getPropertyByName() ??
    But what if two sub nodes are having similar attributes?? e.g.

    LogConfiguration
    ----Logger
    -------LoggerName v="DEFAULT"
    -------LogLevel v="INFO"
    ---- /Logger
    -----Logger
    -------LoggerName v="CONFIG"
    -------LogLevel v="WARN"
    -----/Logger
    -----Logger
    -------LoggerName v="AUTH"
    -------LogLevel v="DEBUG"
    -----/Logger
    /LogConfiguration.

    Posted by: pks_chennai on August 03, 2007 at 05:03 AM

  • Thanks,
    very useful and interestingexample

    Posted by: lukebike on November 21, 2007 at 03:53 AM

  • Hi Eamonn,

    Thanks a lot for this example, it really has helped me out. However, I am getting strange behavior when I try to invoke methods on my MBean.

    Basically, I found I had to add method handling in invoke() for all methods exposed in DynamicMBean interface, and even when I did that the setAttribute(Sttribute) method for some reason is not going through invoke(). Also, this non-invoke()-based invocation is getting passed an Attribute object with name set to "Attribute" and value set to "javax.management.Attribute@", rather than what I am setting on the client, so it is not updating the property correctly.

    I have no idea why this strange behavior is happening... I posted more details here: http://forum.java.sun.com/thread.jspa?threadID=5300274

    Thanks in advance for any advice/direction you might be able to give!

    cheers,
    Doug

    Posted by: doug_harley on May 28, 2008 at 03:45 PM

  • never mind, i figured out my problem: incorrect use of APIs. DOH!

    i am not sure why my mangled code was partially working, it must have been a miracle. actually, it was more like a curse cause if it had just failed miserably i would have realized quicker i was on wrong track. i should have reviewed the JSR at start instead of hacking away...

    thanks again for this dynamic mbean properties file manager example, awesome stuff.

    Posted by: doug_harley on May 28, 2008 at 11:12 PM



Only logged in users may post comments. Login Here.


Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds