Developing custom MBeans to manage J2EE Applications (Part I)

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

In this first blog entry, we will learn:

  • 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 our MBean 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.

The complete code sample and associated build files are available as a zip file. The code has been tested against WebLogic Server 10.3.1 and JDK6.

Implementing the MBean

We chose to implement an MBean that can be used to manage properties associated with an application. To keep things interesting those properties will be persisted, and originally packaged with the deployed application's ear file.

Among the different types of MBeans, we choose to implement an MXBean." MXBeans have the main advantage to expose model specific types as Open Types. This means that clients interacting with MXBeans do not need to include any jar files containing application custom classes. For our simple example, this doesn't really come into play, but this also doesn't complicate our task either. So MXBean it is.

If you want to know more about MXBean, a tutorial is available here

MBean interface

The MBean interface declares the JMX management interface exposed by the MBean. Java methods translate to JMX attributes ( Java bean getter/setter pattern ) or to JMX operations ( the remaining methods ). More info on the mapping from Java methods to JMX attributes and operations is available in the JMX specification or in this tutorial. The same mapping rules apply to both Standard MBeans and MXBeans.


package blog.wls.jmx.appmbean;

import java.util.Map;
import java.io.IOException;

public interface PropertyConfigMXBean {

    public String setProperty(String key, String value) throws IOException;\
    public String getProperty(String key);
    public Map getProperties();
}

The above interface declares one attribute: Properties, and two operations: setProperty and getProperty. Those are the attribute and operations exposed by our MBean to JMX clients.

MBean implemetation

Our MBean implementation relies on the JDK's Properties class to manage and persist the underlying properties. The complete code for the MBean implementation is included below, and specific sections are discussed thereafter.


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.Properties;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.MBeanRegistration;

public class PropertyConfig implements PropertyConfigMXBean, MBeanRegistration {

    private String relativePath_ = null; 
    private Properties props_ = null;
    private File resource_ = null;

    public PropertyConfig(String relativePath) throws Exception {
        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() {}     
}

Our MBean implements the PropertyConfigMXBean interface, as well as the MBeanRegistration interface.

We use the MBeanRegistration.preRegister method to load the persisted properties upon MBean registration. The original properties are included as part of the deployed ear, and accessed using the application's Context ClassLoader:

        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        URL resourceUrl = cl.getResource(relativePath_);
        resource_ = new File(resourceUrl.toURI());
        load();     

The interesting part in the above code is the use of the application's Context ClassLoader to extract the path under which the deployed ear's property file is located. For this to work we must ensure that the preRegister method is triggered by an application Thread, so that we can access the proper ClassLoader. More on this later.

The load and save methods are quite straight forward. The load method use the Properties class to read the properties from the application's property file into the MBean state. The save method use the Properties class to persist the properties from the MBean state back into the application's property file.

Any new property added using the setProperty method is immediately persisted by calling the save method within the setProperty method. On a more complex project, one could expose save as a JMX operation, and build an associated Configuration Session MBean. This is beyond this blog's topic thought.

MBean life-cycle implementation

To register our MBean upon application start, and unregister it upon application stop (or undeploy), we use WebLogic's ApplicationLifecycleListener class. Note: We could have used the J2EE ServletContextListener class in place, but we chose the ApplicationLifecycleListener as it also works for applications that do not include web modules. The complete code is included below:

package blog.wls.jmx.appmbean;

import weblogic.application.ApplicationLifecycleListener; 
import weblogic.application.ApplicationLifecycleEvent; 
import weblogic.application.ApplicationException; 

import javax.management.ObjectName; 
import javax.management.MBeanServer; 

import javax.naming.InitialContext; 

public class ApplicationMBeanLifeCycleListener extends ApplicationLifecycleListener {

    public void postStart(ApplicationLifecycleEvent evt) throws ApplicationException { 
        try { 
            InitialContext ctx = new InitialContext(); 
            MBeanServer mbs  = 
                MBeanServer.class.cast( ctx.lookup("java:comp/jmx/runtime") ); 
            PropertyConfig mbean = new PropertyConfig("config/properties.data");
            ObjectName oname = new ObjectName(
                "blog.wls.jmx.appmbean:type=PropertyConfig,name=myAppProperties"); 
            mbs.registerMBean(mbean, oname); 
        } 
        catch (Exception e) {
            // Deal with exception
            e.printStackTrace();
        } 
    } 

    public void preStop(ApplicationLifecycleEvent evt) throws ApplicationException { 
        try { 
            InitialContext ctx = new InitialContext(); 
            MBeanServer mbs  = 
                MBeanServer.class.cast( ctx.lookup("java:comp/jmx/runtime") ); 
            ObjectName oname = new ObjectName(
                "blog.wls.jmx.appmbean:type=PropertyConfig,name=myAppProperties");
            if ( mbs.isRegistered(oname) ) { 
                 mbs.unregisterMBean(oname); 
            }
        } 
        catch (Exception e) {
            // Deal with exception
            e.printStackTrace();
        } 
    }        
}

In the above code we register our MBean in WebLogic's Runtime MBeanServer: ctx.lookup("java:comp/jmx/runtime"). This MBeanServer is present on all WebLogic's processes, and can be used to register application's custom MBeans.

We construct an instance of our MBean: new PropertyConfig("config/properties.data") passing the relative path to the application's property file. The path is relative to the root of the ear file, as we will see in the next section. Remember from the previous section we used the application Context ClassLoader to load the application property file using the relative path provided above: cl.getResource(relativePath_);. This is made possible by the postStart method that is executed by an application Thread.

We register our MBean under the "blog.wls.jmx.appmbean:type=PropertyConfig,name=myAppProperties" ObjectName. We followed the JMX best practices when picking that name. We recommend you do the same as this greatly helps:

  • Avoid naming collisions.
  • Organize MBeans in MBean browsers. For instance JConsole's MBean tree.
  • Provide a consistent MBean naming structure when MBeans from many different sources are registered in a single MBeanServer.

Another point worth mentioning is the importance of unregistering the MBean when the application is stopped. If the MBean is left registered, the application ClassLoader(s) will be pinned and resource will be leaked.

Packaging the ear file

Now we need to package our code and property file so that both can be loaded by the application Context ClassLoader.

The MBean and life cycle code is packaged in a jar as follow:

$ jar tvf sample-mbean-app-mbeans.jar
     0 Tue Oct 07 16:35:28 PDT 2009 META-INF/
   121 Tue Oct 07 16:35:26 PDT 2009 META-INF/MANIFEST.MF
     0 Tue Oct 07 16:35:26 PDT 2009 blog/
     0 Tue Oct 07 16:35:26 PDT 2009 blog/wls/
     0 Tue Oct 07 16:35:26 PDT 2009 blog/wls/jmx/
     0 Tue Oct 07 16:35:26 PDT 2009 blog/wls/jmx/appmbean/
  1337 Tue Oct 07 16:35:26 PDT 2009 
            blog/wls/jmx/appmbean/ApplicationMBeanLifeCycleListener.class
  2417 Tue Oct 07 16:35:26 PDT 2009 blog/wls/jmx/appmbean/PropertyConfig.class
   408 Tue Oct 07 16:35:26 PDT 2009 
           blog/wls/jmx/appmbean/PropertyConfigMXBean.class
$ 

The jar's manifest includes the following entry: Class-Path: ../... This will ensure that the application's property file can be accessed from the application's ClassLoader. More on this later.

The ear file is packaged as follow:

$ jar tvf sample-mbean-app.ear
     0 Tue Oct 07 16:35:28 PDT 2009 META-INF/
   102 Tue Oct 07 16:35:26 PDT 2009 META-INF/MANIFEST.MF
   602 Tue Oct 0714:50:34 PDT 2009 META-INF/application.xml
   719 Tue Oct 07 16:35:26 PDT 2009 app.war
     0 Tue Oct 07 16:35:28 PDT 2009 APP-INF/
     0 Tue Oct 07 16:35:28 PDT 2009 APP-INF/lib/
  3328 Tue Oct 07 16:35:26 PDT 2009 APP-INF/lib/sample-mbean-app-mbeans.jar
     0 Tue Oct 07 16:35:28 PDT 2009 config/
   105 Tue Oct 07 30 12:46:28 PDT 2009 config/properties.data
   422 Tue Oct 07 13:10:52 PDT 2009 META-INF/weblogic-application.xml
$

The MBean jar file sample-mbean-app-mbeans.jar is added under the APP-INF/lib directory. Any jars located under that directory will be added to the application's ClassLoader.

The application's property file is added under config/properties.data, and can be accessed by the application's Context ClassLoader using that path. This works because we added the Class-Path: ../.. entry in sample-mbean-app-mbeans.jar's manifest, and we placed that jar under the APP-INF/lib directory.

META-INF/application.xml declares a single war module app.war, that is empty, and whose only purpose is to make our application valid by including at least one J2EE module.

META-INF/weblogic-application.xml declares our ApplicationLifecycleListener:

<?xml version="1.0" encoding="UTF-8"?>
<weblogic-application xmlns="http://www.bea.com/ns/weblogic/90"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://www.bea.com/ns/weblogic/90 
  http://www.bea.com/ns/weblogic/90/weblogic-application.xsd">
  <listener>
    <listener-class> 
        blog.wls.jmx.appmbean.ApplicationMBeanLifeCycleListener
    </listener-class>
  </listener>
</weblogic-application>

Download the code and build file

The above code and associated build files are available as a zip file. Once you have downloaded the zip file, extract its content, and cd to the app_mbean_part1 directory.

In order to build the code and create the application's ear file, you first need to define the WL_HOME environment property to point to your WebLogic binary install. Make sure $WL_HOME/lib/weblogic.jar is valid. Then just execute:

ant

If everything goes well the sample-mbean-app.ear file should now be located under the build directory.

Deploying the ear

The sample-mbean-app.ear file can be deployed as follow:

java -classpath $WL_HOME/lib/weblogic.jar weblogic.Deployer -noexit -name sample-mbean-app -source build/sample-mbean-app.ear -targets jrfServer_admin -adminurl t3://140.87.10.42:7086 -user weblogic -password gumby1234 -deploy

Make sure to substitute:

  • Your WebLogic server host and port in place of 140.87.10.42:7086. The port value for your WebLogic server is available from the <WLS_INSTANCE_HOME>/config/config.xml file:
     <server>
        <name>jrfServer_admin</name>
        <listen-port>7086</listen-port>
        <listen-address>140.87.10.42</listen-address>
      </server>
    
    Make sure you look under the correct server if several servers are defined as part of your config.xml. For instance in the above case we are connecting to the server identified as "jrfServer_admin ".

  • Your WebLogic server name in place of jrfServer_admin
  • Your WebLogic server administrator's login and password in place of weblogic and gumby1234

The following command can be used to undeploy the sample-mbean-app application:

java -classpath $WL_HOME/lib/weblogic.jar weblogic.Deployer -noexit -name sample-mbean-app -targets jrfServer_admin -adminurl t3://140.87.10.42:7086 -user weblogic -password gumby1234 -undeploy

Using JConsole to browse and edit our application MBean

Using JConsole to connect to WebLogic's MBeanServers was the subject of an earlier blog entry. I recommand reading through that blog if you want to know more. Here I will just fast forward to the command line used:

jconsole -J-Djava.class.path=$JAVA_HOME/lib/jconsole.jar:$JAVA_HOME/lib/tools.jar:$WL_HOME/lib/wljmxclient.jar -J-Djmx.remote.protocol.provider.pkgs=weblogic.management.remote -debug

Once JConsole is started select the Remote process connection, and fill out the target MBeanServer URI, Username and Password fields:

service:jmx:iiop://140.87.10.42:7086/jndi/weblogic.management.mbeanservers.runtime

Make sure you replace the host and port values with the ones you used in your deployment command line. The username and password values are also the ones you used as part of your deployment command line.

You should now be connected and able to see our application MBean under the blog.wls.jmx.appmbean tree entry:

app_mbean_part1_jconsole1.JPG

You can experience with creating new properties using the setProperty operation, and browsing them with either the getProperty operation or Properties attribute. Notice that the Properties attribute is exposed to JMX clients as a TabularData:

app_mbean_part1_jconsole2.JPG

The conversion between the Map instance returned by our public Map getProperties() method and the TabularData exposed to JMX clients is performed by the MXBean implementation that is part of the JDK. More info on MXBeans and how they convert native types to Open Types is available here.

You might wonder where is the property file persisted. The answer is it depends on the deployment staging mode, and whether the application is deployed as an ear file or an exploded directory. In most cases the file will be located in a temporary directory under the target server. For instance:

servers/jrfServer_admin/tmp/_WL_user/sample-mbean-app/dgiyyk/config/properties.data

In this case, re-deploying the ear (provided the ear was modified) will overwrite the existing property file with the one contained in the ear file. Any new property added since the application was last deployed will be lost. It is also not possible to share the application's properties among several WebLogic servers, as each server gets its own local copy of the property file.

We can work around this by either:

  • Removing the property file from the ear file, and access it from a well known external location using regular file I/O.
  • Deploying the application as an exploded ear accessible from all targeted WebLogic servers.

    java -classpath $WL_HOME/lib/weblogic.jar weblogic.Deployer -noexit -name sample-mbean-app -source build/exploded-ear -targets jrfServer_admin -adminurl t3://140.87.10.42:7086 -user weblogic -password gumby1234 -deploy

    Where the exploded-ear directory contains: jar xvf sample-mbean-app.ear.

    In this case the property file will be modified under its original location: build/exploded-ear/config/properties.data.

Note: If the property file is shared among several MBeans, then some form of synchronization will need to be provided across those MBeans when reading/writing the property file. We won't get into this in this blog.

Aside from JConsole we can also use a Java client to browse and edit our MBean. This is demonstrated in an earlier blog entry. Another possibility is to use Oracle's Enterprise's Manager.

What's next?

Even thought our MBean is functional, it cannot be considered production ready. It is lacking descriptions for the MBean itself, its attributes, operations and operation parameters. The operation parameters are also lacking meaningful names as demonstrated below:

app_mbean_part1_jconsole3.JPG

Our goal next time around will be to fix the above issues.

Comments:

I have an EAR application with some WebApplications (war) inside. I want to isolate the properties inside each one of the WebApplications, that is, i want one propeties config per classloader/webapp. Is there a way to get this to work?

Posted by Thiago Kafouri on December 07, 2009 at 10:08 PM PST #

I have not tried. but I believe this can be made to work by: 1) Removing the Class-Path: ../.. entry from the mbean(s) jar ( sample-mbean-app-mbeans.jar in the blog sample ). 2) Packaging the properties configuration file in the war in a place that can be accessed by the war's ClassLoader. One possibility is WEB-INF/classes , or in some other location and then adding the location using a manifest Class-Path entry to one of the jar in WEB-INF/lib so that the configuration is visible from the war ClassLoader. 3) Then each war would need to register the Config MBean responsible for managing its associated properties file, through it's specific ServletContextListener. Each of those MBean will need to have a different ObjectName.

Posted by philippe Le Mouel on December 07, 2009 at 11:19 PM PST #

Thanks. Actualy i've already thinked of that but i was with a problem of executing ctx.lookup("java:comp/jmx/runtime"), but i discovered that this statement only works on ApplicationLifecycleListener, in the ServletContextListener you have to use ctx.lookup("java:comp/env/jmx/runtime"). I've made a sample application that works fine and i used the context-name of the WebAplication to define de ObjectName of each application, and my webapp is running on a cluster so i used the cluster name too on the ObjectName.

Posted by Thiago Kafouri on December 09, 2009 at 12:38 AM PST #

My bro told me about this site just today. Great blog you've got!

Posted by Button Boy on October 05, 2010 at 09:18 AM PDT #

I get an error when deploying with WLS 10.3.3 javax.management.NotCompliantMBeanException: MBean class com.directv.ccs.mbean.CacheMBeanImpl does not implement DynamicMBean, nei ther follows the Standard MBean conventions (javax.management.NotCompliantMBeanException: Class com.directv.ccs.mbean.CacheMBeanIm pl is not a JMX compliant Standard MBean) nor the MXBean conventions (javax.management.NotCompliantMBeanException: com.directv.ccs .mbean.CacheMBeanImpl: Class com.directv.ccs.mbean.CacheMBeanImpl is not a JMX compliant MXBean)

Posted by Jim on November 22, 2010 at 05:40 AM PST #

How to manage configuration associated with an application using XML instead of Properties file.

Posted by Afroz on January 30, 2011 at 06:49 PM PST #

The layout is what really caught my eye, then the i looked at the writing and i think you did a very nice job. Good work:)

Posted by Merrill Finnegan on February 03, 2011 at 05:44 PM PST #

Keep focusing on your blog. I love how we can all express our feelings. This is an extremely great blog here :)

Posted by SEO Services on February 27, 2011 at 09:19 PM PST #

Great work, philippe!

Posted by Steven F. on March 01, 2011 at 06:31 AM PST #

let me just tell you that i really like your blog because it is so original ;:~`;"

Posted by ATX Power Supply %0B on March 02, 2011 at 07:25 AM PST #

Would not it be nice to combine those two things and Replica Louis Vuitton Handbags store something called negotiation or designer, designer price tags less?

Posted by Louis Vuitton Handbags on March 29, 2011 at 09:27 PM PDT #

I am also getting the error similar to Sasa Milicevic. java.io.IOException: Failed to retrieve RMIServer stub: javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial at javax.management.remote.rmi.RMIConnector.connect(RMIConnector.java:323) at javax.management.remote.JMXConnectorFactory.connect(JMXConnectorFactory.java:248) at sun.tools.jconsole.ProxyClient.tryConnect(ProxyClient.java:362) at sun.tools.jconsole.ProxyClient.connect(ProxyClient.java:298) at sun.tools.jconsole.VMPanel$2.run(VMPanel.java:280) Caused by: javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file: java.naming.factory.initial at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:645) at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:288) at javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:325) at javax.naming.InitialContext.lookup(InitialContext.java:392) at javax.management.remote.rmi.RMIConnector.findRMIServerJNDI(RMIConnector.java:1871) at javax.management.remote.rmi.RMIConnector.findRMIServer(RMIConnector.java:1841) at javax.management.remote.rmi.RMIConnector.connect(RMIConnector.java:257) ... 4 more Please correct or suggest... -Srikanth

Posted by Srikanth on April 13, 2011 at 08:35 PM PDT #

Hi Phillippe,
Great job. 5 out of 5 for this entry.
You covered almost all the details.
Good for both - architect and developer - alike.

Thanks-
Ashish

Posted by guest on August 26, 2011 at 11:32 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

The official blog for Oracle WebLogic Server fans and followers!

Stay Connected

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
5
6
7
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today