The Artima Developer Community
Sponsored Link

Software and Return on Investment
Netbeans Module for JMX enabling your application
by Gregg Wonderly
September 6, 2006
Summary
I am finishing up a rather large application that needs some JMX management capabilities. I've got a lot of classes that need to be MBean enabled, and I really don't want to go through and change them to inherit from StandardMBean.

Advertisement

Forays into Netbeans and JMI

I've been playing around with the Netbeans IDE for some time now. In 5.0, they've finally cleaned up enough issues that I feel like I can be productive with the way that I work. One of the standing issues for me and IDEs is whether or not it supports the Perforce SCM. That was an initial issue with 5.0, but the Netbeans Module system and built in authoring support made it possible for me to define a half-dozen or so actions, fairly quickly, that let me interact with perforce. More about that in another blog.

Netbeans includes the ability to analyze, alter, and generate the structure of Java source code in modules. That's what I'm getting the most mileage out of.

A Netbeans JMX Module

What I wanted to talk about was the module I created to help me make changes to existing classes so that they can become MBeans with attributes and operations without having to change the object hierarchy. The StandardMBean class will use introspection and/or a passed interface, to build a DynamicMBean. So, if you take an existing class, and add a couple of methods such as:
    StandardMBean bean;
    protected void registerMBean() {
        bean = new StandardMBean( this, MyInterface.class );
        MBeanServer appsrvr = MBeanServerFactory.createMBeanServer();
        appsrvr.registerMBean( bean, getObjectName() );
    }

    protected ObjectName getObjectName() {
        return new ObjectName( getClass().getPackage().getName()+":"+
            "type=name,name="+getClass().getName() );
    }
You can add a call to registerMBean in the classes constructor, and have a working MBean for access to your class base on the methods defined in the MyInterface interface definition. StandardMBean uses the JavaBean principles to find attributes by looking for setter and getter patterns. The other methods in the interface become operations.

I created a Netbeans module that embodies this pattern.

The Modules Operation

The Netbeans plugin generates two different support classes, plus manages an interface (the MyInterface business above). It then augments the class you are manipulating to make it possible for the user of the module to just put the call to registerMBean() in one place, one time, and be done.

The user of the module only needs to right click on a field name, or method name, and select the modules action from the menu. Depending on the selected object, appropriate steps are taken to process the users request.

The two support classes and the interface are always created if not present already. The two support classes and the interface are placed into the same package as the class being manipulated. This might create some redundant code. I am contemplating how to deal with this issue more easily. However, there are some customization possiblities of those two classes which I think makes it interesting to keep them in the package with the class(es) that are using them.

Generating an Operation from a Method

If the user selects a method, the interface will be augmented with a definition of the same method signature. An additional exception, java.io.IOException, will be added to the throws clause as needed so that the interface can be used directly in remoting application environments, such as Jini.

Generating an Attribute from a Field

Field processing is more complex because there are some additional features provided. When the user selects a field to be processed, a wizard opens. The first pane lets the user select whether they want to allow read, write, read-write or no access to the field. The user can also specify a different name for the Attribute than the field name so that odd field naming practices don't create Attribute names which are difficult to use. Based on the users selection, the appropriate methods will be added to the interface to allow setting, getting, or neither access to the field. No access to the field is related to the options on the second pane.

The second pane allows the user to select to receive notification on field value changes. It also allows the user to attach either a CounterMonitor and/or a GaugeMonitor to the fields values. Because of the monitors, a getter will be needed. If notifications on change are requested, a setter will be added to the class to centralize updates to the field, but it will not be present in the interface.

Summary

Below is an example class which has been manipulated by this module. It shows an overview of what's possible. The registerMBean() method needs some lines of code added for each field that is managed. So, I've placed marker tokens in comments that allow the code generation to find the information it needs to manage the content of this method.

This is still a work in progress, but so far, it look like it will be useful for our needs.


/*
 * BreakTest.java
 *
 * Created on September 5, 2006, 4:46 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.wonderly.test2;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.Notification;
import javax.management.ObjectName;
import javax.management.StandardMBean;
import javax.management.monitor.CounterMonitor;
import javax.management.monitor.GaugeMonitor;

/**
 *
 * @author gregg
 */
public class BreakTest implements IJMXBreakTest {
    int fld;
    String val;
    int other1;
    int other2;

    /** Creates a new instance of BreakTest */
    public BreakTest() {
    }

    /**
     * This method is used to retrieve the current value of
     * the fld field.  The associated MBean attribute will
     * be capitalized as Fld due to themechanism implemented by the {@link javax.management.StandardMBean}.
     * @see #setFld(int)
     */
    public synchronized int getFld() {
        return fld;
    }

    /**
     * This method is used to set the current value of
     * the fld field.  The associated MBean attribute will
     * be capitalized as Fld due to themechanism implemented by the {@link javax.management.StandardMBean}.
     * @see #getFld()
     */
    public synchronized void setFld(int val) throws IOException, MalformedObjectNameException {
        int oval = fld;
        fld = val;
        bean.notifyAttributeChange( "Fld", oval, val );
    }

    /**
     * A logging instance for use by the generated code.
     */
    private java.util.logging.Logger log = Logger.getLogger( getClass().getName() );;

    /**
     * This field refers to this instances MBean
     */
    private EventingMBean bean;

    /**
     * This method registers the requested monitors for the
     * Fld attribute.
     */
    protected void registerMonitorsFld(EventingMBean bean, javax.management.ObjectName on) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException {
        log.fine("registering mbean monitors for \"Fld\"");
        GaugeMonitor gageMonitor = new GaugeMonitor() {
            public void sendNotification( Notification notif ) {
                notif.setUserData( getFld() );
                super.sendNotification( notif );
            }
        };
        gageMonitor.setObservedAttribute( "Fld");
        gageMonitor.setGranularityPeriod( 100000 );
        gageMonitor.setNotifyHigh( true );
        gageMonitor.setNotifyLow( true );
        gageMonitor.stop();
        gageMonitor.addObservedObject( bean.getObjectName() );
        CounterMonitor cntMonitor = new CounterMonitor() {
            public void sendNotification( Notification notif ) {
                notif.setUserData( getFld() );
                super.sendNotification( notif );
            }
        };
        cntMonitor.setObservedAttribute( "Fld");
        cntMonitor.setGranularityPeriod( 100000 );
        cntMonitor.stop();
        cntMonitor.addObservedObject( bean.getObjectName() );
        if( usePlatformServer() ) {
            log.finer("registering with platform mbean server");
            platformMBeanServer.registerMBean( gageMonitor, bean.getObjectName("GaugeFld"));
            platformMBeanServer.registerMBean( cntMonitor, bean.getObjectName("CounterFld"));
        }
        log.finer("registering with application mbean server");
        appMBeanServer.registerMBean( gageMonitor, bean.getObjectName("GaugeFld"));
        appMBeanServer.registerMBean( cntMonitor, bean.getObjectName("CounterFld"));
    }

    /**
     * This is the reference to the platform MBeanServer
     * which was used to register MBeans.
     * @see #appMBeanServer
     */
    protected javax.management.MBeanServer platformMBeanServer;

    /**
     * This is the reference to the application MBeanServer
     * which was used to register MBeans.
     * @see #platformMBeanServer
     */
    protected javax.management.MBeanServer appMBeanServer;

    /**
     * This method initializes all aspects of the JavaBeans which
     * are needed to support the options selected.  Do not edit out the {...}
     * markers from comments below as they are used by the code generation
     * steps to find the various pieces already in place so that only the needed
     * new code is added.
     */
    private void registerMBean() throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException {
        EventingMBean bean = new EventingMBean(this,org.wonderly.test2.IJMXBreakTest.class);
        ObjectName on = bean.getObjectName();
        if( usePlatformServer() ) {
            platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
            platformMBeanServer.registerMBean( bean, on );
        }
        appMBeanServer = MBeanServerFactory.createMBeanServer();
        appMBeanServer.registerMBean( bean, on );
        // Do not edit the lines below with {XXXX} tags on them.
        // Registration of monitors will be added here {REGMON}
        // {REGMON-MaxValue}
        registerMonitorsMaxValue( bean, on);
        // {REGMON-MinValue}
        registerMonitorsMinValue( bean, on);
        // {REGMON-Fld}
        registerMonitorsFld( bean, on);
        // Notifications will be added here {NOTIFADD}
        // {NOTIFADD-MaxValue}
        log.fine("add notification info for MaxValue");
        bean.addNotificationInfo( new MBeanNotificationInfo(
            new String[] { "MaxValue" },
            "MaxValue",
            "MaxValue value change notification"
            ));
        // {NOTIFADD-MinValue}
        log.fine("add notification info for MinValue");
        bean.addNotificationInfo( new MBeanNotificationInfo(
            new String[] { "MinValue" },
            "MinValue",
            "MinValue value change notification"
            ));
        // {NOTIFADD-Val}
        log.fine("add notification info for Val");
        bean.addNotificationInfo( new MBeanNotificationInfo(
            new String[] { "Val" },
            "Val",
            "Val value change notification"
            ));
        // {NOTIFADD-Fld}
        log.fine("add notification info for Fld");
        bean.addNotificationInfo( new MBeanNotificationInfo(
            new String[] { "Fld" },
            "Fld",
            "Fld value change notification"
            ));
    }

    /**
     * This method provides the plugable place to override how the use
     * of the platform MBeanServer is selected.  The platform server is actively used
     * by default in this implementation.
     */
    private boolean usePlatformServer() {
        return System.getProperty( getClass().getPackage().getName()+".noplatform") == null;
    }

    /**
     * This method is used to retrieve the current value of
     * the val field.  The associated MBean attribute will
     * be capitalized as Val due to themechanism implemented by the {@link javax.management.StandardMBean}.
     * @see #setVal(java.lang.String)
     */
    public synchronized java.lang.String getVal() {
        return val;
    }

    /**
     * This method is used to set the current value of
     * the val field.  The associated MBean attribute will
     * be capitalized as Val due to themechanism implemented by the {@link javax.management.StandardMBean}.
     * @see #getVal()
     */
    public synchronized void setVal(java.lang.String val) throws IOException, MalformedObjectNameException {
        java.lang.String oval = val;
        val = val;
        bean.notifyAttributeChange( "Val", oval, val );
    }

    /**
     * This method is used to retrieve the current value of
     * the minValue field.  The associated MBean attribute will
     * be capitalized as MinValue due to themechanism implemented by the {@link javax.management.StandardMBean}.
     * @see #setMinValue(int)
     */
    public synchronized int getMinValue() {
        return other1;
    }

    /**
     * This method is used to set the current value of
     * the minValue field.  The associated MBean attribute will
     * be capitalized as MinValue due to themechanism implemented by the {@link javax.management.StandardMBean}.
     * @see #getMinValue()
     */
    public synchronized void setMinValue(int val) throws IOException, MalformedObjectNameException {
        int oval = other1;
        other1 = val;
        bean.notifyAttributeChange( "MinValue", oval, val );
    }

    /**
     * This method registers the requested monitors for the
     * MinValue attribute.
     */
    protected void registerMonitorsMinValue(EventingMBean bean, javax.management.ObjectName on) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException {
        log.fine("registering mbean monitors for \"MinValue\"");
        GaugeMonitor gageMonitor = new GaugeMonitor() {
            public void sendNotification( Notification notif ) {
                notif.setUserData( getMinValue() );
                super.sendNotification( notif );
            }
        };
        gageMonitor.setObservedAttribute( "MinValue");
        gageMonitor.setGranularityPeriod( 100000 );
        gageMonitor.setNotifyHigh( true );
        gageMonitor.setNotifyLow( true );
        gageMonitor.stop();
        gageMonitor.addObservedObject( bean.getObjectName() );
        if( usePlatformServer() ) {
            log.finer("registering with platform mbean server");
            platformMBeanServer.registerMBean( gageMonitor, bean.getObjectName("GaugeMinValue"));
        }
        log.finer("registering with application mbean server");
        appMBeanServer.registerMBean( gageMonitor, bean.getObjectName("GaugeMinValue"));
    }

    /**
     * This method is used to retrieve the current value of
     * the maxValue field.  The associated MBean attribute will
     * be capitalized as MaxValue due to themechanism implemented by the {@link javax.management.StandardMBean}.
     * @see #setMaxValue(int)
     */
    public synchronized int getMaxValue() {
        return other2;
    }

    /**
     * This method is used to set the current value of
     * the maxValue field.  The associated MBean attribute will
     * be capitalized as MaxValue due to themechanism implemented by the {@link javax.management.StandardMBean}.
     * @see #getMaxValue()
     */
    public synchronized void setMaxValue(int val) throws IOException, MalformedObjectNameException {
        int oval = other2;
        other2 = val;
        bean.notifyAttributeChange( "MaxValue", oval, val );
    }

    /**
     * This method registers the requested monitors for the
     * MaxValue attribute.
     */
    protected void registerMonitorsMaxValue(EventingMBean bean, javax.management.ObjectName on) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException {
        log.fine("registering mbean monitors for \"MaxValue\"");
        GaugeMonitor gageMonitor = new GaugeMonitor() {
            public void sendNotification( Notification notif ) {
                notif.setUserData( getMaxValue() );
                super.sendNotification( notif );
            }
        };
        gageMonitor.setObservedAttribute( "MaxValue");
        gageMonitor.setGranularityPeriod( 100000 );
        gageMonitor.setNotifyHigh( true );
        gageMonitor.setNotifyLow( true );
        gageMonitor.stop();
        gageMonitor.addObservedObject( bean.getObjectName() );
        if( usePlatformServer() ) {
            log.finer("registering with platform mbean server");
            platformMBeanServer.registerMBean( gageMonitor, bean.getObjectName("GaugeMaxValue"));
        }
        log.finer("registering with application mbean server");
        appMBeanServer.registerMBean( gageMonitor, bean.getObjectName("GaugeMaxValue"));
    }
}
The generated interface is shown below. Notice how the setters for minValue and maxValue are not present since I just configured those to notify on change and have gauges.
/*
 * IJMXBreakTest.java
 *
 * Created on September 6, 2006, 12:50 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.wonderly.test2;

import java.io.IOException;
import java.rmi.Remote;
import javax.management.MalformedObjectNameException;

/**
 * This interface defines the MBean attributes and Operations
 * which the implementing class should provide.  This interface is
 * intended to be RMI compatible.  All methods throw
 * {@link java.io.IOException} to provide RMI compatibility without
 * the {@link java.rmi.RemoteException} being the limit of exceptions
 * thrown due to I/O or communications issues.
 * 
 * This interface is machine generated and methods may be added
 * by subsequent development activities.  It can be freely edited
 * but that might affect its correctness.
 */
public interface IJMXBreakTest extends Remote {
    /**
     * This method is used to retrieve the current value of
     * the fld field.  The associated MBean attribute will
     * be capitalized as Fld due to the
     * mechanisms implemented by the {@link javax.management.StandardMBean}.
     * @see #setFld(int)
     */
    public int getFld() throws IOException;

    /**
     * This method is used to set the current value of
     * the fld field.  The associated MBean attribute will
     * be capitalized as Fld due to themechanism implemented by the {@link javax.management.StandardMBean}.
     * @see #getFld()
     */
    public void setFld(int val) throws IOException, MalformedObjectNameException;

    /**
     * This method is used to retrieve the current value of
     * the val field.  The associated MBean attribute will
     * be capitalized as Val due to themechanism implemented by the {@link javax.management.StandardMBean}.
     * @see #setVal(java.lang.String)
     */
    public java.lang.String getVal() throws IOException;

    /**
     * This method is used to set the current value of
     * the val field.  The associated MBean attribute will
     * be capitalized as Val due to themechanism implemented by the {@link javax.management.StandardMBean}.
     * @see #getVal()
     */
    public void setVal(java.lang.String val) throws IOException, MalformedObjectNameException;

    /**
     * This method is used to retrieve the current value of
     * the minValue field.  The associated MBean attribute will
     * be capitalized as MinValue due to themechanism implemented by the {@link javax.management.StandardMBean}.
     */
    public int getMinValue() throws IOException;

    /**
     * This method is used to retrieve the current value of
     * the maxValue field.  The associated MBean attribute will
     * be capitalized as MaxValue due to themechanism implemented by the {@link javax.management.StandardMBean}.
     */
    public int getMaxValue() throws IOException;
    
}

Talk Back!

Have an opinion? Be the first to post a comment about this weblog entry.

RSS Feed

If you'd like to be notified whenever Gregg Wonderly adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Gregg Wonderly graduated from Oklahoma State University in 1988 with an MS in COMSCI. His areas of concentration include Operating Systems and Languages. His first job was at the AT&T Bell Labs facilities in Naperville IL working on software retrofit for the 5ESS switch. He designed a procedure control language in the era of the development of Java with similar motivations that the Oak and then Java language development was driven by. Language design is still at the top of his list, but his focus tends to be on application languges layered on top of programming languages such as Java. Some just consider this API design, but there really is more to it! Gregg now works for Cyte Technologies Inc., where he does software engineering and design related to distributed systems in highly available environments.

This weblog entry is Copyright © 2006 Gregg Wonderly. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use