« Stateful JAX-WS with Coherence*Web | Main | Threads Constraints in Work Managers »

Developing custom MBeans to manage J2EE Applications (Part II)

This is the second part in a series of blogs, that demonstrate how to add management capability to your own application using JMX MBeans.

In Part I we did the bulk of the work. We saw:

  • How to implement a custom MBean to manage configuration associated with an application.
  • How to package the resulting code and configuration as part of the application's ear file.
  • How to register MBeans upon application startup, and unregistered them upon application stop (or undeployment).
  • How to use generic JMX clients such as JConsole to browse and edit our application's MBean.

In this second part, we will add descriptions to our:

  • MBean
  • MBean attributes
  • MBean operations
  • MBean operation parameters

We saw using JConsole, that default descriptions were generated for all of the above. However those default descriptions didn't provide any meaningful information to our MBean users. We will replace those default descriptions with custom descriptions that can be used by us human to understand the functionality provided by our MBean.

We will also add localization support to our MBean. Our goal is to ensure that our MBean's meta-data is localized based on the WebLogic Server it is deployed to. So if that server uses a French Locale, then we'd expect our MBeans descriptions to be French and not English.

The complete code sample and associated build files for part II are available as a zip file. The code has been tested against WebLogic Server 10.3.1 and JDK6. To build and deploy our sample application, please follow the instruction provided in Part I, as they also apply to part II's code and associated zip file.

Providing custom descriptions

In order to provide custom description we need to modify our MBean implementation to extend the StandardMBean class. Despite its name that class is used to implement both Standard and MXBeans.

We override the StandardMBean class many getDescription methods to provide our own custom descriptions. We use resources bundles to store our descriptions to ensure that those can be properly localized. The updated code for our MBean implementation is included below:

package blog.wls.jmx.appmbean;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.File;

import java.net.URL;

import java.util.Map;
import java.util.HashMap;
import java.util.Properties;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.MBeanRegistration;
import javax.management.StandardMBean;
import javax.management.MBeanInfo;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;

public class PropertyConfig extends StandardMBean implements
    PropertyConfigMXBean, MBeanRegistration {

    private String relativePath_ = null; 

    private Properties props_ = null;

    private File resource_ = null;

    private ResourceBundle resourceBundle_ = null;

    private static Map operationsParamNames_ = null;

    static {
        operationsParamNames_ = new HashMap();
        operationsParamNames_.put("setProperty", new String[] {"key", "value"});
        operationsParamNames_.put("getProperty", new String[] {"key"});
    }

    public PropertyConfig(String relativePath) throws Exception {
        super(PropertyConfigMXBean.class , true);
        props_ = new Properties();
        relativePath_ = relativePath;
    }

    public String setProperty(String key,
                              String value) throws IOException {

        String oldValue = null;

        if (value == null) {
            oldValue = String.class.cast(props_.remove(key));
        } else {
            oldValue = String.class.cast(props_.setProperty(key, value));      
        }

        save();
        return oldValue;
    }

    public String getProperty(String key) {
        return props_.getProperty(key);
    }

    public Map getProperties() {
        return (Map) props_;
    }

    private void load() throws IOException {
        
        InputStream is = new FileInputStream(resource_);
        try {
            props_.load(is);
        }
        finally {
            is.close();
        }
    } 

    private void save() throws IOException {
  
        OutputStream os = new FileOutputStream(resource_);

        try {
            props_.store(os, null);
        }
        finally {
            os.close();
        }
    }

    public ObjectName preRegister(MBeanServer server, ObjectName name)
        throws Exception {
       
        // MBean must be registered from an application thread
        // to have access to the application ClassLoader
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        URL resourceUrl = cl.getResource(relativePath_);
        resource_ = new File(resourceUrl.toURI());

        load();     

        return name;
    }

    public void postRegister(Boolean registrationDone) { }

    public void preDeregister() throws Exception {}

    public void postDeregister() {}

    private synchronized ResourceBundle getResourceBundle() {
   
        if ( resourceBundle_ == null ) {
            resourceBundle_ = 
                PropertyResourceBundle.getBundle(
                    "blog.wls.jmx.appmbean.MBeanDescriptions");
        }
       
        return resourceBundle_;
    } 

    protected String getDescription(MBeanAttributeInfo info) { 
        return getResourceBundle().getString("PropertyConfigMXBean.attribute." + 
            info.getName() ); 
    } 

    protected String getDescription(MBeanOperationInfo info) { 
        return getResourceBundle().getString("PropertyConfigMXBean.operation." + 
            info.getName() ); 
    }   

    protected String getDescription(MBeanInfo info) {
        return getResourceBundle().getString("PropertyConfigMXBean.mbean");
    }

    protected String getDescription(MBeanOperationInfo op,
                                    MBeanParameterInfo param,
                                    int sequence) {
        return getResourceBundle().getString(
            "PropertyConfigMXBean.operation." + 
            op.getName() + "." + 
            operationsParamNames_.get(op.getName())[sequence] ); 
    }

    protected String getParameterName(MBeanOperationInfo op,
                                      MBeanParameterInfo param,
                                      int sequence) {
        return operationsParamNames_.get(op.getName())[sequence];
    } 

}

Our default resource bundle class, that contains our English descriptions contains the following code:

package blog.wls.jmx.appmbean;

import java.util.ListResourceBundle;

public class MBeanDescriptions extends ListResourceBundle {
     protected Object[][] getContents() {
         return new Object[][] {
             {"PropertyConfigMXBean.mbean", 
              "MBean used to manage persistent application properties"},  
             {"PropertyConfigMXBean.attribute.Properties", 
              "Properties associated with the running application"},
             {"PropertyConfigMXBean.operation.setProperty", 
              "Create a new property, or change the value of an existing property"},
             {"PropertyConfigMXBean.operation.setProperty.key", 
              "Name that identify the property to set."},
             {"PropertyConfigMXBean.operation.setProperty.value", 
              "Value for the property being set"},
             {"PropertyConfigMXBean.operation.getProperty", 
              "Get the value for an existing property"}, 
             {"PropertyConfigMXBean.operation.getProperty.key", 
              "Name that identify the property to be retrieved"} 
        };
     }
 }

Our MBean is quite simple, and only exposes one attribute and two operations. This helps keep our resource bundle class quite small. For real world example that file will be much bigger. Note: We didn't override the getDescription method associated with our MBean constructor, to keep our sample small as this doesn't add much value to our discussion.

To add support for other languages, we only need to implement the corresponding resource bundle class. MBeanDescriptions_fr for French, and translate the description appropriately.

One interesting thing to note, is the name we used for our resource bundle keys. We didn't use arbitrary values, but we made sure we followed the following convention:

  • MBean description: <MBeanInterfaceClass>.mbean
  • MBean attribute description: <MBeanInterfaceClass>.attribute.<AttributeName>
  • MBean operation description: <MBeanInterfaceClass>.operation.<OperationName>
  • MBean operation parameter description: <MBeanInterfaceClass>.operation.<OperationName>.<ParameterName>
  • MBean constructor description: <MBeanInterfaceClass>.constructor.<ConstructorName>
  • MBean constructor parameter description: <MBeanInterfaceClass>.constructor.<ConstructorName>.<ParameterName>
We also purposely named our resource bundle class MBeanDescriptions and included it as part of the same package as our MBean. The above convention is used by the JDK 7 to localize MBean descriptions without requiring us to extend the StandardMBean class and override its many getDescription methods. Unfortunately JDK 6 doesn't support built-in JMX localization, so we have to write the above code. However we can anticipate the JDK 7 functionality (and possible early support by WebLogic) by using the above convention when specifying our MBean resource bundle and associated resource keys.

You might have noticed the following code in our updated MBean implementatrion:

    protected String getParameterName(MBeanOperationInfo op,
                                      MBeanParameterInfo param,
                                      int sequence) {
        return operationsParamNames_.get(op.getName())[sequence];
    } 
This is used to provide customized name to our operation parameters in place of the default generated 'po', 'p1', ... , 'pn' values.

The result of our hard work can be seen in the following JConsole screen shot:

app_mbean_part2_jconsole1.JPG

Consult Part I for information on how to use JConsole to browse/edit our MBean.

What's next?

What if our application is deployed to a WebLogic server running with an English Locale, and our management client wants to use a French Locale. Currently as things stand the Localization is performed based on the server Locale, and not based on the client's Locale. In the last part of this blog series, we will see how to associate a Locale with our JMX client connection, and we will update our MBean code to support client based localization.

TrackBack

TrackBack URL for this entry:
http://blogs.oracle.com/mt/mt-tb.cgi/15198

Comments (10)

Dmitry:

Hi,

What is the general approach to take if you need to create a custom singleton MBean in a (Weblogic) cluster?

Thanks

philippe Le Mouel Author Profile Page:

You can use a startup class to register your MBean in the WLS "Domain Runtime MBeanServer". This is the MBeanServer that runs as part of the "AdminServer" process associated with your domain. More info on startup classes below:

http://download.oracle.com/docs/cd/E12840_01/wls/docs103/ConsoleHelp/taskhelp/startup_shutdown/UseStartupAndShutdownClasses.html

The startup class will be responsible for registering your MBean when the AdminServer starts up. Since the "Domain Runtime" MBeanServer provides access to the MBeans running on the different managed WebLogic Servers, you can access other server MBeans if necessary from your MBeans as long as those are up and running.

Dmitry:

Thanks a lot Philippe,

I've got to try this.

Dmitry:

Philippe,

Are startup/shutdown classes invoked only once, or once on each server in a cluster?

philippe Le Mouel Author Profile Page:

You can specify the server(s) on which the class should be executed as part of the config.xml configuration.

Bill:

Is there a different type of mbean for values that update dynamically (like heap usage)? I'd like to add some more values for monitoring (perhaps network or I/O bytes, for example).

philippe Le Mouel Author Profile Page:

If the values are always available then any type of MBean will do. You can just fetch the most recent value each time the corresponding getter is called.

If the value is not always defined, then you can use a Dynamic MBean whose JMX interface varies based on the availability of the metrics it reports. So for instance if the network connection is off, you might not expose the JMX attributes corresponding to network metrics. I would however caution against Dynamic MBeans whose JMX interface change during the time it is registered, as it can confuse JMX client and human users who expect the JMX interface to be immutable.

Bill:

Thanks, that helps. I appreciate the response.

It seems I need a value type of long or Long instead of String. If my datatype is long, then tools like JRMC will allow me to graph the values over time.

I tried replacing the maps, getters, setters with , etc, but something still isn't quite right. Is this the right approach? I notice that many of the mbeans I see for weblogic are 'CompositeData'

I also noticed that the Properties class requires Strings for key and value, so I'm not quite sure how to get around that. Doing a .toSting() on my doubles in my class allowed it to compile, but the run time results are note as expected.

-Bill

philippe Le Mouel Author Profile Page:

The reason WebLogic uses CompositeData types, is to allow generic JMX clients to manage WebLogic without requiring them to add WebLogic's model specific types to their ClassPath. Another advantage is that generic JMX client such as JConsole are built to handle OpenTypes. For instance JConsole can display the content of a TabularData.

If you want to add values of type long/Long, you can just use a different data structure than the Properties class used in the sample to store those. You can use a generic collection with instance of type Long, or your own class if you'd rather use long in place of Long. You can then just use Object serialization to persist and restore those form the file system. If the values do not need to be persisted, then the load, save and preRegister methods are not necessary. To expose your long values, you can just add new getters ( and setters if those are mutable values). For instance:

public long getMetrci1();

public long getMetrics2();

......

You will need to add those to both the MBean interface and implementation. You can add a getter per value as illustrated above, or use a single method keyed on the metrics name:

public long getMetrics(String metricsName);

If the metrics are known in advance, and you have relatively few of them I would use a getter per metrics, as they will be exposed as JMX attributes, and can be graphed using JConsole or other basic JMX tools.

If several values are correlated in a single metrics, then I would return them through a single getter:

public ComplexMetrics getComplextMetrics();

Where ComplexMetrics is your own type that can be converted to an OpenType ( CompositeData most likely ) by the MXBean engine. More info at:

http://java.sun.com/javase/6/docs/api/javax/management/MXBean.html

under the "Type Mapping Rules" section.

For instance:

public class ComplexMetrics{

public long getValue1() {return value1;}
public void setValue1(long value1) {this.value1 = value1;}

public long getValue2() {return value2;}
public void setValue2(long value2) {this.value2 = value2;}

public ComplexMetrics() {}

private long value1;
private long value2;
}

Hope this helps.
Philippe

Bill:

I got it now! Thanks for the quick replies and the great info...

-Bill

Post a comment

About This Entry

This page contains a single entry from the blog posted on November 10, 2009 6:10 PM.

The previous post in this blog was Stateful JAX-WS with Coherence*Web .

The next post in this blog is Threads Constraints in Work Managers.

Many more can be found on the main index page or by looking through the archives.

Powered by
Movable Type and Oracle