Home > Articles > Programming > Java

  • Print
  • + Share This
This chapter is from the book

Implementing Standard MBeans

The main constraint for writing a Standard MBean is the requirement to declare a statically typed Java interface that explicitly declares the management attributes and operations of an MBean. In some cases, having to declare such a rigid programmatic structure to expose the management interface may not be desirable. Usually, however, the Standard MBean mechanism is the simplest and most convenient method for bringing new Java classes into the JMX realm.

The naming conventions used in the MBean interface follow closely the rules set by the JavaBeans component model. To expose the management attributes, you declare getter and setter methods, similar to JavaBean component properties. There are some differences however, especially in the way the JMX agent takes into consideration the inheritance structure of the MBeans, which makes the naming conventions used by the Standard MBeans specific to the JMX specification. We will go through the naming conventions and see examples of the inheritance patterns in the next few pages.

The Hello MBean example used in Chapter 1 was a Standard MBean. We declared a statically typed Java interface called HelloMBean that was implemented by the resource class Hello. It is important to notice that the suffix of the interface name, MBean, is significant. The agent uses introspection on the MBean class to determine which interfaces the class implements. The agent will recognize the class as a Standard MBean type if it finds it implementing an interface with a corresponding MBean suffix in the name. For example, a User class implementing a UserMBean interface is recognized by the agent as a Standard MBean.

Let's take a closer look at the attribute naming conventions next.

Standard MBean Attributes

Management attributes are named characteristics of an MBean. With Standard MBeans, attributes are defined in the MBean interface via the use of naming conventions in the interface methods. There are three kinds of attributes, read-only, write-only, and read-write attributes. The agent determines what kind of attribute has been declared by introspecting the method naming in the MBean interface.

Read-only attributes are defined by declaring only a getter method in the interface. The naming for the getter follows the same rules as with the JavaBeans component model. In other words, a getAttribute() method defines a read-only attribute named Attribute. Similarly, you define a write-only attribute by declaring only a setter method in the MBean interface, for example, setAttribute(). If you declare both the getter and setter method, the agent will determine the attribute Attribute is a read-write type.

The naming convention for MBean attributes is as follows:

public AttributeType getAttributeName();
public void setAttributeName(AttributeType value);

Attributes can be any valid Java type. You can use the standard Java classes such as String, Integer, and Boolean, or you can use your own classes as attribute types. Java primitive types, such as int or byte, are acceptable as well. You can also use an array of any valid Java type. Remember that if you use your own classes, the classes need to be available to the agent at runtime.

There are some restrictions to defining the MBean attributes. You cannot overload attribute accessor methods. For example, you cannot declare an MBean attribute of Java type int that has overloaded setter method, such as follows:

public void setAttributeName(int value);
public void setAttributeName(String value); // NOT COMPLIANT!

Overloading an attribute setter method will lead to a non-compliant MBean.

Another restriction worth noting is the fact that attributes based on arrays can only have getter and setter methods that deal with the entire array all at once. In other words, you cannot declare a setter method for an attribute of Java type int[] that will set, for example, the value of the first item in the array. You will have to declare a setter method that operates on the entire array of integers instead. A workaround for this is to declare a management operation that will manipulate single entries in the array. You will see an example of how to do this in the "Standard MBean Example" section later in the chapter.

For boolean types, the Standard MBean naming conventions allow two alternative ways to declare the getter method. For example, for a management attribute named Active, you can declare either a getter method, getActive() or isActive(), in the management interface. Both methods will be recognized as accessors to the Active attribute. The isActive() form of declaration also implies the boolean type for the given management attribute.

The naming convention for boolean types can be expressed as follows:

public boolean isAttributeName();

Note, however, that you cannot use both the getAttributeName() method and isAttributeName() method for the same attribute in the MBean interface. You will have to pick one of the two methods.

MBean attribute names are case sensitive. Declaring two accessor methods for attributes that differ in capitalization will lead to two separate MBean attributes exposed by the agent.

Management Operations

Management operations for Standard MBeans include all the methods declared in the MBean interface that are not recognized by the agent as being either a read or write method to an attribute. The operations don't have to follow any specific naming rules as long as they do not intervene with the management attribute naming conventions.

The management operations can have return values. The returned values can be of any valid Java type, either primitive type or reference type. In case of the generic management tools, such as the two HTTP protocol adaptors we used earlier, it is up to the adaptor to decide how to deal with the return value. If the management application is aware of the semantics of the return value of the operation it has invoked, it may react to the return value in a specific way. Depending on the return value, the management application may attempt to display the returned object to the user, send a notification based on the value of the returned object, or execute other management code or business logic.

Exceptions in the Management Interface

When creating the Standard MBean interface, you can declare the methods to throw any type of exception as per the rules of Java programming language. The exception types can be those included in the Java standard libraries, such as the java.io.IOException, or they can be custom application exceptions declared by the application developer.

When the agent invokes a method of the management interface, whether a management operation method or management attribute's accessor method, it will catch all the instances of java.lang.Throwable and its subclasses if thrown. The agent will wrap the checked exceptions in a javax.management.MBeanException class and then proceed to propagate this exception to the originating caller. Unchecked exceptions—subclasses of RuntimeException—will be wrapped in a javax.management.RuntimeMBeanException class. Similarly, all errors thrown by the MBean implementation will be wrapped in javax.management.RuntimeErrorException.

Both the RuntimeMBeanException and MBeanException implement a getTargetException() method that allows you to access the original exception that was thrown in the MBean. The RuntimeErrorException implements a getRuntimeError() method for retrieving the error thrown by the MBean.

The methods in the MBeanServer interface used for accessing the MBeans, such as setAttribute(), getAttribute() and invoke(), declare the checked exception class MBeanException to be thrown, and, therefore, require a try–catch block in the management client. In the exception handling code, the client can extract the actual target exception and react accordingly.

Standard MBean Example

So far, you have learned several details about Standard MBeans, and now it is time to put that knowledge into practice with a code example. In the next few pages, you will define and implement a User resource and create a management interface for it. The User resource is not specific to any existing system. It is an abstract construct that stores the user ID, name, address, and phone numbers. It should be easy to see a User resource as part of many information systems. Managing users is a common administrative task in many environments.

You will define five attributes for the management interface of the user object. First, the user has a read-only attribute ID. The ID represents a unique identifier for this particular user, such as a primary key in the database. You will also define read-write attributes Name and Address. These two string attributes can be used to store the user's name and address.

public long getID();
public String getName();
public void setName(String name);
public String getAddress();
public void setAddress(String address);

To demonstrate the use of arrays, define an array-based attribute PhoneNumbers. It's a string array containing a maximum of three telephone numbers.

public String[] getPhoneNumbers();
public void setPhoneNumbers(String[] numbers);

Last, there is a write-only attribute Password. Because you only declare a setter method for this attribute, which makes it write-only, you can set a new password for the user via the management interface. But you are unable to read the stored password after you've set it. This prevents the HTTP adaptor from displaying the contents of the Password attribute on the Web page.

public void setPassword(String passwd);

Naturally, any management operation dealing with sensitive information, such as passwords, must be properly secured. The JMX specification does not currently define security features for MBeans. In practice, this responsibility is left mostly to the distributed services level and agent level of the JMX architecture—the connectors, protocol adaptors and the JMX agent.

In addition to the five attributes, you declare three operations for this MBean component. Two of the operations, addPhoneNumber() and removePhoneNumber(), are used for modifying individual elements of the PhoneNumbers array. The third operation, printInfo(), is used for printing the contents of the User object—name, address, and phone numbers. This time, you won't print the information to the console, as you did with the Hello example in Chapter 1. Instead, you declare the printInfo() operation to return a string value.

Listing 3.1 is the complete declaration of the management interface.

Listing 3.1 UserMBean.java

package book.jmx.examples;

public interface UserMBean {

  // read-only attribute 'ID'
  public long getID();
  
  // read-write attribute 'Name'
  public String getName();
  public void setName(String name);
  
  // read-write attribute 'Address'
  public String getAddress();
  public void setAddress(String address);
  
  // read-write array attribute 'PhoneNumbers'
  public String[] getPhoneNumbers();
  public void setPhoneNumbers(String[] numbers);
  
  // write-only attribute 'Password'
  public void setPassword(String passwd);
  
  // management operations
  public String printInfo();
  public void addPhoneNumber(String number);
  public void removePhoneNumber(int index);
}

In the MBean implementation, you will store the attribute values to object fields id, name, address, password, and numbers. This is a very straightforward assignment from parameters to fields. However, there are a couple of things you should notice.

In the example, the setID() method of the User class is implemented. Notice that you did not declare this method in the MBean interface. This is a usual practice in cases where the methods in the resource class are not meant to be exposed for management applications. In the User class, the reading of the ID attribute is allowed for management applications, but the ability to write the ID value is reserved for the application's internal methods only. The user of the management application will not be able to change the value of this attribute. For the ID value, the creation time of the object instance is used because a database or directory is not set up for this example.

The methods addPhoneNumber() and removePhoneNumber() have been implemented and exposed for management. They allow you to modify individual entries in the array attribute PhoneNumbers. The addPhoneNumber() method tries to add the string given as parameter to the first entry in the array containing a null reference. The removePhoneNumber() method will set the array entry to null for the given index.

Listing 3.2 is the full source code of the managed resource.

Listing 3.2 User.java

package book.jmx.examples;

public class User implements UserMBean {

  private long id     = System.currentTimeMillis();
  private String name   = "";
  private String address  = "";
  private String password = null;
  private String[] numbers = new String[3];
  

  // read-only attribute 'ID'
  public long getID() { 
    return id;
  }


  // application method, not exposed to management
  public void setID(long id) {
    this.id = id;
  }
  

  // read-write attribute 'Name'
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }

  
  // read-write attribute 'Address'
  public String getAddress() {
    return address;
  }
  public void setAddress(String address) {
    this.address = address;
  }

  
  // read-write array attribute 'PhoneNumbers'
  public String[] getPhoneNumbers() {
    return numbers;
  }
  public void setPhoneNumbers(String[] numbers) {
    this.numbers = numbers;
  }

  
  // write-only attribute 'Password'
  public void setPassword(String passwd) {
    this.password = passwd;
  }

  
  // management operations
  public String printInfo() {
    return 
     "User: " + getName() +"\n"+
     "Address: " + getAddress() +"\n"+
     "Phone #: " + getPhoneNumbers()[0] +"\n"+
     "Phone #: " + getPhoneNumbers()[1] +"\n"+
     "Phone #: " + getPhoneNumbers()[2] +"\n";
  }
  
  public void addPhoneNumber(String number) {
    for (int i = 0; i < numbers.length; ++i)
      if (numbers[i] == null) {
        numbers[i] = number;
        break;
      }
  }

  public void removePhoneNumber(int index) {
    if (index < 0 || index >= numbers.length)
      return;
      
    numbers[index] = null;
  }
}

User Client

Next, you will build a simple management client for the MBean to demonstrate the use of the User MBean. The client will operate in the same Java Virtual Machine as the JMX agent but will be useful to show the programmatic invocation of the MBeans. Later, after you have read about the JMX connectors in Part II, "JMX in the J2EE Platform," you can also invoke the same MBeans using a wide variety of different protocol and messaging mechanisms.

As was shown in Chapter 2, "Architecture," where management architecture was covered, the MBeans themselves are never directly exposed to the management clients. What the management applications have access to is the agent layer of the JMX architecture, which offers the programmatic interface to manipulate the managed resources. This layer of indirection between the management application and the managed resource creates the decoupled nature of the JMX-based management systems. The only static information known to the management application about the resource is its name, which was used to register the component to the agent, and is represented by the ObjectName instance. Any change to the management interface of the resource is kept local between the resource and the agent. In effect, this decouples the management applications from the resources, and changes to the resources, as they evolve and go through maintenance, can be isolated.

Listing 3.3 should clearly explain this nature of the Java Management Extensions. As you will see, the name of the managed resource is involved in all invocations to the agent, which will then proceed to propagate the invocation to the correct managed resource known to it. Notice that you never at any point have a direct Java reference to the resource on which you invoke the operations. You should also notice that all the invocations to management operations are generic, and all the type information—in other words, the operation's signatures—are passed to the agent as string types instead of statically-typed method calls. This type of invocation is crucial to systems built for very long uptimes where new components are introduced to the system dynamically and the old ones are upgraded by hot-deploying the software components.

The whole agent API will be covered in Chapter 6, "Mbean Server," but for now look at two methods of the MBeanServer interface needed to use in the client—the setAttributes() and invoke() methods.

The setAttributes(), as the name implies, is used to set the management attributes of an MBean. The setAttributes() method takes an ObjectName instance and an AttributeList instance as its parameters, where the ObjectName is the name of the MBean registered to the agent, and the AttributeList is a collection of Attribute objects representing the management attributes.

The invoke() method of the MBeanServer is declared as follows (exceptions have been left out for the sake of brevity).

public Object invoke(ObjectName name, String operationName,
           Object[] params, String[] signature)

As you can see, there is no static type information involved in the invocation. The return value and parameters are passed as generic objects, the MBean instance to receive the invocation is identified by its object name, and the operation name and its signature are passed as strings. On the one hand, this kind of detyping of the invocation leads to a system capable of dynamically changing while maintaining high-availability capabilities. On the other hand, it puts you, the developer, in a position where extra rigor is needed in implementation. You should be extra careful when writing the invocations to avoid simple typos or disarranged signatures. Smart use of refactoring techniques to avoid replicating the invocations and use of constants and other measures are recommended.

Now that you are aware of the double-edged sword of the invocation and can handle it with care, take a look at the UserClient code. The management code to handle the programmatic invocation of management operations is shown in Listing 3.3.

Listing 3.3 UserClient.java

package book.jmx.examples;

import javax.management.*;
import java.util.List;

public class UserClient {

  public void run() {

   // Find an agent from this JVM. Null argument will return
   // a list of all MBeanServer instances.
   List srvrList = MBeanServerFactory.findMBeanServer(null);
   MBeanServer server =
      (MBeanServer)srvrList.iterator().next();
   
   try {    
     // register the MBean
     ObjectName name = new ObjectName("example:name=user");
     server.registerMBean(new User(), name);

     // Invoke the printInfo operation on an
     // uninitialized MBean instance.
     Object result = server.invoke(
              name,     // MBean name
              "printInfo",  // operation name
              null,     // no parameters
              null      // void signature
             );
      
     System.out.println("Non-initialized User object:");
     System.out.println(result);
      
     // Create the list of management attribute value pairs
     AttributeList list = new AttributeList();
     list.add(new Attribute("Name", "John"));
     list.add(new Attribute("Address", "Strawberry Street"));
     list.add(new Attribute("PhoneNumbers", new String[]
                 {
                  "555-1232",
                  null,
                  null
                 }
                ));

     // Init the MBean instance by calling setAttributes()
     server.setAttributes(name, list);

     // Invoke the printInfo to retrieve the updated state      
     result = server.invoke(
              name,     // MBean name
              "printInfo",  // operation name
              null,     // no parameters
              null      // void signature
             );
  
     System.out.println("Initialized User object:");
     System.out.println(result);
   }
   catch (MalformedObjectNameException e) {
     e.printStackTrace();
   }
   catch (InstanceNotFoundException e) {
     e.printStackTrace();
   }
   catch (MBeanException e) {
     e.getTargetException().printStackTrace();
   }
   catch (ReflectionException e) {
     e.printStackTrace();
   }
   catch (InstanceAlreadyExistsException e) {
     e.printStackTrace();
   }
   catch (NotCompliantMBeanException e) {
     e.printStackTrace();
   }

  }

  /**
  * Main method for the client. This will instantiate an agent
  * and register the User MBean to it.
  */
  public static void main(String[] args) {
  
   MBeanServer server =
        MBeanServerFactory.createMBeanServer();
    
   new UserClient().run();
  }
}

At the main() method, the JMX agent instance is created with the createMBeanServer() method of the MBeanServerFactory class. Then the run() method is invoked, which implements the client logic. The client first tries to find a reference to the MBeanServer. This is achieved by using the MBeanServerFactory class and its findMBeanServer() method. The findMBeanServer() method will return an agent reference based on either an agent identifier, which can be passed as a parameter, or a list of all known MBean server instances found in the JVM. The UserClient code uses the latter option, and picks the first reference from the returned list, knowing that, in the case of this specific example, the only MBean server instance in the JVM is the one created in the main() method.

Next, you create the ObjectName instance that identifies the MBean in the agent. The object name represents the reference to the MBean, which the agent will know how to map to a Java reference to the managed resource. Remember that this is the mechanism keeping the management client decoupled from the resource implementation.

After you have created the ObjectName instance and registered the MBean to the server, you can use the MBean server to invoke the printInfo() operation. It returns a formatted string containing the managed resource's state.

To compile and run the code using Sun reference implementation, execute the following two commands:

C:\Examples> javac -d . -classpath .;jmx-1_0_1-ri_bin/jmx/lib/jmxri.jar
_ UserClient.java User.java UserMBean.java

C:\Examples> java -classpath .;jmx-1_0_1-ri_bin/jmx/lib/jmxri.jar
_ book.jmx.examples.UserClient

The client should output the following:

Non-initialized User object:
User:
Address:
Phone #: null
Phone #: null
Phone #: null

As you can see, none of the fields in the resource have been initialized yet. Next you build an AttributeList object containing the attributes you want to set on the MBean. The AttributeList contains instances of Attribute objects that represent the management attributes in our managed bean—Name, Address, and PhoneNumbers.

When the AttributeList has been created and initialized, you set all of the attributes with the setAttributes() call and invoke the printInfo operation again. This time, the output contains the attribute values you passed to the agent.

Initialized User object:
User: John
Address: Strawberry Street
Phone #: 555-1232
Phone #: null
Phone #: null

You can also try the User MBean with the JMX HTTP adapter that was tested in Chapter 1. You will need to alter the Agent.java from the Hello example (Listing 1.4 in Chapter 1) to register the User MBean component to the agent. The changes are shown in Listing 3.4.

To compile and run execute the following commands (using Sun Reference Implementation), use the following:

C:\Examples> javac -d . -classpath .;jmx-1_0_1-ri_bin/jmx/lib/jmxri.jar;
_jmx-1_0_1-ri_bin/jmx/lib/jmxtools.jar  _User.java UserMBean.java Agent.java

C:\Examples> java -classpath .;jmx-1_0_1-ri_bin/jmx/lib/jmxri.jar;
_jmx-1_0_1-ri_bin/jmx/lib/jmxtools.jar _book.jmx.examples.Agent

Listing 3.4 Agent.java

...

  try {
    // create the agent reference
    MBeanServer server = 
      MBeanServerFactory.createMBeanServer();

    // create object name reference
    ObjectName name = 
      new ObjectName("example:name=user");
    
    // register Hello MBean
    server.registerMBean(new User(), name);

    // create the adaptor instance
    com.sun.jdmk.comm.HtmlAdaptorServer adaptor =
      new com.sun.jdmk.comm.HtmlAdaptorServer();

    ... 
  }

Enter the query for the User component name, example:name=user and click the link to open the management view of the MBean (see Figure 3.1).

Figure 3.1 HTTP Adaptor view of the User MBean.

Now fill in some attribute values for Name, Address, and Password. Try viewing the values of PhoneNumbers and use the management operations addPhoneNumber() and removePhoneNumber() to add and remove single entries to the array.

The User MBean example covers the most common features of the Standard MBeans. You have written a simple management client and programmatically invoked operations on the MBean. You have seen, at the code level, the implications of decoupling the management applications from the managed resources and the agent's role in providing the said indirection. You have also seen examples of all three kinds of management attributes—read-only, write-only and read-write—as well as an example of an array-based attribute.

Next, you will look at how the inheritance of the Standard MBean management interfaces are interpreted by the agent, and how inheritance can be used to enable management of existing Java resources.

  • + Share This
  • 🔖 Save To Your Account