Chapter 3. Standard MBeans

In this chapter, we will discuss all the aspects of instrumentation using Standard MBeans.

First, you will look at the formal definition of an MBean, as defined in the JMX specification. You will then go through the naming conventions and properties of Standard MBeans and see how to implement the management interface using the Standard MBean mechanism. At the end of this chapter, you will look at the notification mechanism defined in the JMX specification and see how it can be used for communication between the managed component and the management application.

In this chapter, you will write example code to demonstrate the features of Standard MBeans. You will later use the same base example to implement Dynamic MBeans and compare the two different kinds of instrumentation of managed components. Also, familiarizing yourself with the JMX notification mechanism is important at this point, because it will be featured in the subsequent chapters of the book.

MBean Definition

MBeans must be concrete Java classes. That is the first requirement for all MBeans, regardless of their type. The MBean must be declared as a public, non-abstract class. Classes with package-only visibility or abstract classes are not compliant MBeans. The public, non-abstract class requirement is to ensure that the agent is able to instantiate the MBean on request.

For this same reason, an MBean must have at least one public constructor. An MBean is allowed to have any number of constructors that can be defined using the usual Java visibility rules with keywords public, private, or protected. Nevertheless, at least one of the constructors must be public. Also, the constructor is allowed to have any number of parameters of any type. However, remember that it is the developer's or the administrator's responsibility to make sure all the classes used in the constructor parameter list are available to the agent when it needs to instantiate the MBean.

In the Hello MBean example we built in Chapter 1, “Getting Started,” you didn't declare any constructors at all. However, both of the agent implementations you used accepted the component as a valid MBean. In the example, a default constructor was used. A default constructor is a public constructor that takes no arguments. Therefore, the Hello class was a valid MBean. However, keep in mind that the default constructor is only guaranteed when you do not explicitly declare any other constructors, regardless of their visibility.

The MBean class must implement its own corresponding MBean interface or a DynamicMBean interface. If an MBean implements its own statically-typed Java interface, it is called a Standard MBean. Dynamic MBeans implement the DynamicMBean interface. We will cover the Dynamic MBeans in detail in the next chapter.

These are the three rules you need to remember when developing your MBeans.

  • An MBean must be a public, non-abstract class.

  • An MBean must have at least one public constructor.

  • An MBean must implement its own corresponding MBean interface or implement the DynamicMBean interface.

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.

Example 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.

Example 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.

Example 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
UserClient.java User.java UserMBean.java

C: Examples> java -classpath .;jmx-1_0_1-ri_bin/jmx/lib/jmxri.jar book.jmx.examples
UserClient.java.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
UserClient.java/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
UserClient.java/lib/jmxtools.jar book.jmx.examples.Agent

Example 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).

HTTP Adaptor view of the User MBean.

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.

Inheritance Patterns

In the case of Standard MBeans, the management interface can be inherited from the superclass. It is also possible for the subclass to override the management interface of its ancestor by declaring its own management interface. You will now see how the agent determines the actual management interface of a Standard MBean and then investigate the inheritance patterns.

The most common case, which you have seen with the two Standard MBean examples so far, is to have the MBean class directly implement its own management interface. However, it is also possible for an MBean to inherit its management interface from a superclass. For example, you could create a subclass of the User class—implemented in the previous chapter—and call it Guest. In this case, the Guest class inherits the methods and fields of its superclass as per the usual Java language inheritance rules, but it also inherits the management interface of its superclass. When the Guest component is registered to the agent, the agent will first look for a management interface matching the naming conventions for the Guest class. If the agent does not find the GuestMBean interface, it will traverse the inheritance tree of the Guest class to find the ancestor User. For the User class, the agent will find the matching management interface declaration. Therefore, the Guest class is a compliant MBean and exposes the same attributes and operations as its ancestor User. The management applications will be able to manipulate the Guest instances by using the same interface as for the User instances.

Figure 3.2 shows the inheritance pattern for the Guest class. When the Guest component instance is registered to the agent, it will expose the UserMBean management interface.

Class structure of the Guest MBean.

Figure 3.2. Class structure of the Guest MBean.

It's also possible for the Guest class to override the management interface of its ancestor class. If the Guest class implements a management interface GuestMBean, the agent only exposes the attributes and operations specified in that specific interface. The management interface of all the ancestor classes will be overridden, and nothing of the ancestor classes is exposed to the management. You could use this feature to turn the Guest component instances to strictly read-only in terms of management by defining the interface in Listing 3.5.

Example 3.5. GuestMBean.java

public interface GuestMBean {

    // read-only attributes
    public long getID();
    public String getName();
    public String getAddress();
    public String[] getPhoneNumbers();

    // operations
    public String printInfo();
}

When implementing the Guest resource, you would declare the class as follows:

public class Guest extends User implements GuestMBean {
   // implementation
}

This declaration would hide the UserMBean management interface completely and only allow the management application to read the Guest object's state.

In case the Guest should inherit the management interface of its ancestor User and expose additional attributes and operations, you must have the GuestMBean extend the management interface of the superclass, in this case the UserMBean interface.

As Figure 3.3 shows, this leads to two parallel inheritance trees—one of the implementation and another one of the management interface. Of course, it is also possible for the Guest in this case to implement the entire management interface, instead of inheriting the implementation from the User superclass.

Management interface inheritance.

Figure 3.3. Management interface inheritance.

MBean Notification Mechanism

Building a component-based system often requires the ability for the components to communicate between parts of the system, sending important events and state changes. The JMX architecture defines a notification mechanism for MBeans that allows management events or notifications to be sent to other MBeans or management applications.

The JMX notification mechanism is based on the Java event mechanism. It involves listener objects that are registered to the MBeans emitting notifications, called broadcaster MBeans. The broadcaster MBeans send out notification objects to all interested listeners whose notification filters accept the notification's type.

The JMX notification mechanism builds upon the following four classes:

  • Notification

  • NotificationBroadcaster

  • NotificationFilter

  • NotificationListener

The Notification object is a generic event object sent to the listeners when an event occurs. It extends the java.util.EventObject, which is the standard Java event class. The listener object implements the NotificationListener interface that contains one method, handleNotification(). This is a generic event handler used for all JMX notification listeners, regardless of the notification type.

The NotificationBroadcaster interface is implemented by the MBeans emitting management events. The NotificationBroadcaster interface declares methods for registering (addNotificationListener) and unregistering (removeNotificationListener) listeners and an additional method for retrieving the notification metadata (getNotificationInfo). It is important to notice that the notifications sent by the broadcaster MBeans are part of the management interface. The management applications can query the agent for information about what types of notification the MBean emits.

The NotificationFilter interface is passed to the broadcaster MBean as an argument when the listener is registered. The interface contains one method, isNotificationEnabled(), which must always be invoked by the broadcaster MBean before the management event is sent. This allows the object registering as a listener to select which notifications to receive of all the possible notifications the MBean is emitting.

Note

Notifications are part of the MBeans' management interface.

The registering of the listener to the broadcaster MBean is done through the agent layer. As with all the other MBean invocations, the MBean server sits between the consumer and the producer of the notifications. The MBeanServer interface exposes two methods for adding a listener to an MBean, and two for removing the listener. The declarations of the four methods are shown next. Exceptions have been left out for the sake of brevity.

public void addNotificationListener(
                ObjectName name,
                NotificationListener listener,
                NotificationFilter filter,
                Object handback)

public void addNotificationListener(
                ObjectName name,
                ObjectName listener,
                NotificationFilter filter,
                Object handback)

public void removeNotificationListener(
                ObjectName name,
                NotificationListener listener)

public void removeNotificationListener(
                ObjectName name,
                ObjectName listener)

Both the addNotificationListener() method and the removeNotificationListener() method are overloaded to take a listener argument either as a direct implementation of the NotificationListener interface, or an ObjectName reference to a registered listener MBean. If the ObjectName reference is used, the listener MBean must implement the NotificationListener interface.

Next, we will look at each of the notification interfaces in a little more detail.

Notification

The Notification class represents a generic event sent by an MBean. It is a subclass of the standard Java EventObject class that should be familiar to those who have worked with Java's event mechanism before. The Notification class can be used for any type of management event the MBean wants to broadcast, or it can be subclassed to provide more specific event notifications.

The JMX Notification class extends the EventObject class by adding fields for the event's type, a sequence number, time stamp, message, and an optional user data. The type string is used to convey the semantics of the notification and is formed by adding string components separated by dots. This is not to be confused with the Java class type and a fully-qualified classname that is a dot-separated string of package structure. A type string can be freely formed and contain as many parts as necessary. Notice, however, that all type strings prefixed with jmx. are reserved for the JMX implementation. The following are some examples of notification type strings.

MyApp.MyEvent
Acme.Application.User.CreateEvent
Acme.Application.User.RemoveEvent
jmx.modelmbean.general// RESERVED !!

As you can see, the type string can be used to build a hierarchical structure for the different types of management events.

The sequence number can be retrieved from the Notification object by calling the getSequenceNumber() method. This field allows individual notifications to be identified by the receiver, acting as a serial number. The sequence number is valid in the context of a broadcaster MBean instance. Constructing the Notification object requires you to supply the sequence number as an argument.

The getTimeStamp() method allows access to the date and time the notification was sent. The time stamp can also be set in the constructor, but it is also possible to leave the time stamp creation up to the Notification implementation.

In addition to the message field that can be used for an explanation of the notification, the Notification object allows the broadcaster to attach a user data object to the management event. The user data object can contain any additional information the broadcaster wants to relay to its consumers. There are no specific restrictions to what kind of object can be attached to the notification. However, it is probably a good idea to have the user data objects implement the Serializable interface, making it considerably easier for the connectors to send notifications with user data attached to remote management applications.

NotificationBroadcaster and NotificationFilter

The NotificationBroadcaster interface must be implemented by those MBeans broadcasting management events. The interface declares three methods that the MBean implementation must provide. The declaration of the NotificationBroadcaster interface is shown in Listing 3.6.

Example 3.6. NotificationBroadcaster Interface

package javax.management;

public interface NotificationBroadcaster {
  public void addNotificationListener(
            NotificationListener listener,
            NotificationFilter filter,
            Object handback)
         throws IllegalArgumentException;

  public void removeNotificationListener(
            NotificationListener listener)
         throws ListenerNotFoundException;

  public MBeanNotificationInfo[] getNotificationInfo();
}

The addNotificationListener() registers an event consumer with the MBean. It takes a NotificationListener reference as its first argument and two other arguments—the NotificationFilter object and a hand-back object reference.

The removeNotificationListener() method of the NotificationBroadcaster interface removes the given listener instance from the notification broadcaster. If the given listener instance was not found, a ListenerNotFoundException should be thrown.

The last method of the NotificationBroadcaster interface is a method to return the metadata of the broadcaster MBean to the agent. We will take an extensive look at the metadata structures of the JMX architecture in the Chapter 4, “Dynamic MBeans,” when we investigate the Dynamic MBeans. The getNotificationInfo() method is part of the metadata that is being passed to the agent. It returns an array of MBeanNotificationInfo objects, each of which describe a notification class emitted from the broadcaster MBean. In turn, each MBeanNotificationInfo object describes all the notification types for the given notification class. Remember that the Notification class can be used to deliver several different types of notifications. It is very easy to get your classes and types mixed up here, so be careful.

The NotificationFilter interface allows the listener to communicate to the broadcaster MBean which notifications it is interested in receiving. The broadcaster MBean may be sending several different types of notifications. The listener can register to receive all or just some of them. When passing the filter to the broadcaster MBean, you can select which events you want to receive and avoid registering for each individual event type separately, or avoid having to handle dozens of notifications from the MBean when you're only interested in one particular type. It is also possible to implement more sophisticated logic in the filter that selects the notifications based on the time stamp, sequence number, or possibly the user data object of the notification.

The NotificationFilter is a very simple interface to implement. It only contains one method, isNotificationEnabled(), which returns a Boolean value true or false, depending on whether the broadcaster MBean should send the given Notification instance to the listener. The declaration of the NotificationFilter interface is shown in Listing 3.7.

Example 3.7. NotificationFilter Interface

package javax.management;

public interface NotificationFilter
            extends java.io.Serializable {

  boolean isNotificationEnabled(Notification notif);

}

The broadcaster is required to check the notification against the filter before sending it to the notification consumer. If the filter is a null reference, all notifications from the MBean will be delivered to the registered consumer.

The hand-back object is provided as an argument with the NotificationListener reference when the consumer is registered. It can be used to provide the listener instance an object reference that it can use to retrieve required context information with every broadcast notification. The hand-back object must be stored by the NotificationBroadcaster implementation and passed back to the listener with each notification. In addition, the hand-back object must not be changed by the broadcaster.

The same listener object can be registered more than once to the broadcaster with different hand-back objects. This will cause the listener to receive the notifications as many times as it has registered itself, each time with a different hand-back object.

Broadcaster MBean Example

The implementation of the NotificationBroadcaster interface can become quite complex compared to the usual JavaBeans event mechanism. You need to store a triplet of objects with each added listener— NotificationListener reference, the NotificationFilter reference, and the hand-back object. In addition, you will have to generate the sequence number for each Notification object and remember to invoke the isNotificationEnabled() method of the filter before sending the event to the registered listener.

Luckily, there is a NotificationBroadcasterSupport class available that implements the NotificationBroadcaster interface. You can either have your MBean extend the support class or delegate to it to gain the registration management and notification invocation support. In Listing 3.8, you will use the broadcaster support class to add notifications to the User MBean implementation. The example shows the implementation of the NotificationBroadcaster interface and adds notification to a new management operation remove(). Let's call this new MBean a BroadcastingUser and have it implement a corresponding management interface that extends from the UserMBean interface.

The management interface for the BroadcastingUser is shown in Listing 3.8.

Example 3.8. BroadcastingUserMBean.java

package book.jmx.examples;


public interface BroadcastingUserMBean extends UserMBean {

    public void remove();

}

Remember that the Standard MBean inheritance patterns will expose all properties and operations declared in the ancestor interfaces to the management.

Next, you will write the MBean implementation. Most of the implementation will be inherited from the User class that already implements the attributes and all operations of the MBean, except for the remove() operation. You will use the NotificationBroadcasterSupport class to help implement the NotificationBroadcaster interface. Because you already used up the inheritance tree by extending the User class, you will delegate the NotificationBroadcaster method calls to a support class instance.

The notificationSequence field in the BroadcastingUser class is used for keeping track of the sequence numbers of sent notifications. The implementation of the remove() management operation uses the sendNotification() method of the support class to emit the Notification object. Each Notification object is created with example.user.remove as its type, notificationSequence as its sequence number, and the current BroadcastingUser instance as its source.

Notice that the source reference in the Notification instance will be converted by the agent to represent the ObjectName reference of the broadcaster MBean. This allows the notification consumer to reference the broadcasting MBean through the MBean server and also ensures the decoupling of the Notification objects from the broadcaster MBean. Remember that no direct references to the MBean instances should exist outside the agent. All communications must go through it.

The NotificationBroadcaster is implemented by delegating the functionality of the addNotificationListener() and removeNotificationListener() methods to the notification support object. In addition, you provide the metadata about the notification by returning an MBeanNotificationInfo object from the getNotificationInfo() method.

The complete source for the BroadcastingUser class is provided in Listing 3.9.

Example 3.9. BroadcastingUser.java

package book.jmx.examples;

import javax.management.*;

public class BroadcastingUser extends User implements
      BroadcastingUserMBean, NotificationBroadcaster  {


   // broadcaster support class
   private NotificationBroadcasterSupport broadcaster =
               new NotificationBroadcasterSupport();

   // sequence number for notifications
   private long notificationSequence = 0;


   // management operations
   public void remove() {

      broadcaster.sendNotification(
         new Notification(
            "example.user.remove",          // type
            this,                           // source
            ++notificationSequence,         // seq. number
            "User " +getName()+ " removed." // message
         )
      );
   }


   // notification broadcaster implementation
   public void addNotificationListener(
            NotificationListener listener,
            NotificationFilter filter,
            Object handback) {

      broadcaster.addNotificationListener(
               listener, filter, handback);
   }

   public void removeNotificationListener(
            NotificationListener listener)
            throws ListenerNotFoundException {

      broadcaster.removeNotificationListener(listener);
   }

   public MBeanNotificationInfo[] getNotificationInfo() {
     return new MBeanNotificationInfo[] {
       new MBeanNotificationInfo(
         new String[]
           {  "example.user.remove" } ,  // notif. types
         Notification.class.getName(), // notif. class
         "User Notifications."         // description
       )
     } ;
   }

}

The remove() operation implementation was left empty apart from the notification send implementation. In a real-world case, you would add any necessary code to clean up the database records if a user was removed from the system.

NotificationListener

The NotificationListener is implemented by all classes wanting to act as management event consumers. The NotificationListener interface is a generic listener interface that can be used to receive several different types of notifications from different sources.

The interface defines only one method, handleNotification, which is declared as follows:

public void handleNotification(Notification notification,
                               Object handBack)

The same listener can be registered to receive notifications from different notification broadcasters. In addition, the same listener can receive several different types of notifications from the same notification broadcaster, depending on the notification filter. There are several ways for the implementation of the NotificationListener interface to determine the source and meaning of the received notifications.

  • Use the getSource() method of the Notification class to determine the source of the notification. The object returned by the getSource() method is an ObjectName reference to the MBean that broadcast the notification.

  • Use the getType() method of the Notification class to retrieve the notification type information. This is a dot-separated string of the notification's semantic information.

  • Use the handBack object to map the notifications to a registered listener. The handBack object is guaranteed to be returned to the listener with every notification and can be used to pass any type of context information.

  • Optional use of user data objects. The broadcaster can attach additional data to the notifications. The user data can be retrieved via the getUserData() method call of the Notification class.

Notification Listener Example

Listing 3.10 will show how to register and receive the notifications from the BroadcastingUser MBean. You will register three instances of the BroadcastingUser class to the agent and add a listener object to each one of them, implemented as an inner class UserListener in the code. The notification listener registration is accompanied by a filter object and implemented as an inner class UserFilter, which only accepts notifications of type example.user.remove to be sent to the listener. In addition, you will use the ObjectName instance as a hand-back object with each registration. You will use the hand-back to reference the notification producer MBean in the listener code to unregister it when a notification to remove the user is received. Normally, you would use the getSource() method of the Notification object to retrieve the ObjectName reference to the broadcaster. Unfortunately, the JMX RI version 1.0 has a bug in how it handles the listener instance that is registered to more than one notification producer. This causes the getSource() to return the same ObjectName reference with each notification, regardless of which MBean instance sends it. We use the hand-back to work around this quirk in the reference implementation.

Listing 3.10 is the complete code for the ListenerClient class. After registering the MBeans and adding listeners to them, it invokes the remove() method of the BroadcastingUser objects that will cause a notification to be received in the listeners. The listeners will then unregister the MBeans.

Example 3.10. ListenerClient.java

package book.jmx.examples;

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

public class ListenerClient {

  private NotificationListener listener = null;
  private NotificationFilter filter     = null;
  private MBeanServer server            = null;

  public void run() {

    // Find an agent from this JVM. Null argument will
    // return a list of all MBeanServer instances.
    List list = MBeanServerFactory.findMBeanServer(null);
    server    = (MBeanServer)list.iterator().next();

    // create the listener and filter instances
    listener  = new UserListener(server);
    filter    = new UserFilter();

    // Register three different instances of Broadcasting
    // User MBean to the agent. The single UserListener
    // instance is registered to each MBean. MBean
    // ObjectName is used as a hand-back object.
    try {
      ObjectName john = new ObjectName("user:name=John");
      ObjectName mike = new ObjectName("user:name=Mike");
      ObjectName xena = new ObjectName("user:name=Xena");
      Attribute jName = new Attribute("Name", "John");
      Attribute mName = new Attribute("Name", "Mike");
      Attribute xName = new Attribute("Name", "Xena");

      server.registerMBean(new BroadcastingUser(), john);
      server.registerMBean(new BroadcastingUser(), mike);
      server.registerMBean(new BroadcastingUser(), xena);

      server.addNotificationListener(
                john, listener, filter, john);
      server.addNotificationListener(
                mike, listener, filter, mike);
      server.addNotificationListener(
                xena, listener, filter, xena);

      server.setAttribute(john, jName);
      server.setAttribute(mike, mName);
      server.setAttribute(xena, xName);

      // Invoke remove on each MBean instance. This
      // will broadcast a notification from the MBean.
      server.invoke(john, "remove", null, null);
      server.invoke(mike, "remove", null, null);
      server.invoke(xena, "remove", null, null);
    }
    catch (JMException e) {
      e.printStackTrace();
    }
  }


  //
  // Notification listener implementation.
  //
  class UserListener implements NotificationListener {

    MBeanServer server = null;

    UserListener(MBeanServer server) {
      this.server = server;
    }

    public void handleNotification(Notification notif,
                                   Object handback) {

      String type = notif.getType();

      if (type.equals("example.user.remove")) {
        try {
          System.out.println(notif.getMessage());

          server.unregisterMBean((ObjectName)handback);
          System.out.println(handback + " unregistered.");
        }
        catch (JMException e) {
          e.printStackTrace();
        }
      }
    }
  }


  //
  // Notification filter implementation.
  //
  class UserFilter implements NotificationFilter {

    public boolean isNotificationEnabled(Notification n) {
      return (n.getType().equals("example.user.remove"))
              ? true
              : false;
    }
  }


  //
  // Main method for the client. This will instantiate
  // an agent in the JVM.
  //
  public static void main(String[] args) {

    MBeanServer server =
        MBeanServerFactory.createMBeanServer();

    new ListenerClient().run();
  }

}

When you compile and run the BroadcastingUser MBean and the ListenerClient, you will see the following output on the console.

User John removed.
user:name=John unregistered.
User Mike removed.
user:name=Mike unregistered.
User Xena removed.
user:name=Xena unregistered.

The user removed messages are the contents of the Notification object's message field and retrieved with getMessage() method. The unregistered messages are generated by the listener after it has invoked the unregisterMBean() method on the MBeanServer.

Attribute Change Notifications

The JMX specification defines a specific AttributeChangeNotification class for MBeans that send notifications on change in their management attribute.

The AttributeChangeNotification class extends the Notification class by adding four new fields to the notification—name, type, oldvalue, and newvalue. The name field should contain the name of the management attribute whose value has been changed, and the type field should contain the runtime type of the management attribute. The oldvalue and newvalue fields should contain the old and new value of the attributes, respectively. All the new fields are accessible via respective getter methods for retrieving the values—getAttributeName(), getAttributeType(), getNewValue(), and getOldValue().

In addition, the AttributeChangeNotification class defines the notification type string that must be exposed as notification metadata in getNotificationInfo() method of the NotificationBroadcaster interface. The notification type is exposed as a static string ATTRIBUTE_CHANGE in the AttributeChangeNotification class.

The AttributeChangeNotificationFilter class is an implementation of the NotificationFilter interface. It contains methods for configuring the filter to accept or deny a pattern of attribute change notifications.

The methods of the AttributeChangeNotificationFilter are described in Table 3.1.

Table 3.1. Methods of the AttributeChangeNotificationFilter Class

Method Description
enableAttribute(String name) The attribute change notifications for the given attribute name will be enabled in the filter and sent to the listener.
disableAttribute(String name) All of the attribute change notifications for the given attribute name will be denied by the filter.
disableAllAttributes() All attribute change notifications for all management attributes in the filter will be disabled.
getEnabledAttributes() Returns a vector of the enabled attributes in the notification filter.

The AttributeChangeNotification allows you to easily configure which attribute notifications you want the listener instance to receive.

Attribute Change Notification Example

Listing 3.11 will extend the previous BroadcastingUser example. You will add one attribute change notification to its management interface. It doesn't require many code changes, so only the changed parts are highlighted.

In the BroadcastingUser class, you will override the setUser() method of the superclass User to send an attribute change notification after its setUser() method has been called. In addition, you need to add the new notification type to your metadata in the getNotificationInfo() method. These changes are shown in Listing 3.11.

Example 3.11. BroadcastingUser.java

...


public class BroadcastingUser extends User implements
      BroadcastingUserMBean, NotificationBroadcaster  {


   // broadcaster support class
   private NotificationBroadcasterSupport broadcaster =
               new NotificationBroadcasterSupport();

   // sequence number for notifications
   private long notificationSequence = 0;

   // override the 'Name' management attribute
   public void setName(String name) {
       String oldValue = super.getName();
       String newValue = name;
       String attrType = String.class.getName();
       String attrName = "Name";

       super.setName(name);

       broadcaster.sendNotification(
         new AttributeChangeNotification(
           this,                            // source
           ++notificationSequence,          // seq. number
           System.currentTimeMillis(),      // time stamp
           "User's name has been changed.", // message
           attrName, attrType,
           oldValue, newValue
         )
       );
   }

...

   public MBeanNotificationInfo[] getNotificationInfo() {
      return new MBeanNotificationInfo[] {

        new MBeanNotificationInfo(
          new String[]
            {  "example.user.remove" } ,  // notif. types
          Notification.class.getName(), // notif. class
          "User Notifications."         // description
        ),

        // attribute change notification type
        new MBeanNotificationInfo(
          new String[] {
            AttributeChangeNotification.ATTRIBUTE_CHANGE
          } ,
          AttributeChangeNotification.class.getName(),
          "User attribute change notification."
        )
      } ;
   }

...

}

Also, you need to change the ListenerClient class to accept the new notifications in the notification filter object and change the NotificationListener implementation to process the attribute change notifications. The code changes to ListenerClient are shown in Listing 3.12.

Example 3.12. ListenerClient.java

...

  //
  // Notification listener implementation.
  //
  class UserListener implements NotificationListener {

    ...

    public void handleNotification(Notification notif,
                                   Object handback) {

      String type = notif.getType();

      if (type.equals("example.user.remove")) {
        try {
          System.out.println(notif.getMessage());

          server.unregisterMBean((ObjectName)handback);
          System.out.println(handback + " unregistered.");
        }
        catch (JMException e) {
          e.printStackTrace();
        }
      }

      // process attribute change notifications
      else if (type.equals(
          AttributeChangeNotification.ATTRIBUTE_CHANGE)) {

        AttributeChangeNotification notification =
          (AttributeChangeNotification)notif;

        System.out.println(notification.getMessage());
        System.out.println(
          "  New value=" + notification.getNewValue());
      }
    }
  }

  //
  // Notification filter implementation.
  //
  class UserFilter implements NotificationFilter {

    public boolean isNotificationEnabled(Notification n) {
      return
        (n.getType().equals
            ("example.user.remove") ||
         n.getType().equals
            (AttributeChangeNotification.ATTRIBUTE_CHANGE)
        )
           ? true
           : false;
    }
  }

...

}

Now, if you compile and run the changed classes, you should see the following output on the console.

User's name has been changed.
  New value=John
User's name has been changed.
  New value=Mike
User's name has been changed.
  New value=Xena
User John removed.
user:name=John unregistered.
User Mike removed.
user:name=Mike unregistered.
User Xena removed.
user:name=Xena unregistered.

The User's name has been changed messages are printed when the listener receives a notification from the MBean that has had its setName() method called to initialize it with a new value.

Summary

We have now covered one instrumentation approach in detail—the Standard MBean. MBeans were defined, the naming conventions involved with declaring a management interface with the Standard MBean were discussed, how management attributes and operations are declared, and how the agent finds them introspecting the MBean interface.

The naming conventions allow you to declare read-only, write-only, and read-write access to the management attributes. Also important to remember are the inheritance rules that the agent uses to determine the exposed management interface.

In the last part of this chapter, you had an in-depth look into the notification system defined by the JMX specification and all the related classes. Notifications are an important mechanism used to implement some of the standard agent services that will be covered in Chapter 7.

Standard MBeans are fairly easy to create and useful when creating new resources for management. They are quick to implement and offer statically typed interfaces for robustness. However, they might become inflexible when the management interface is expected to change often, or when you need to be able to determine the management interface at runtime instead of compile time. In those cases, Dynamic MBeans might suit your needs better. We are going to look at the Dynamic MBeans in Chapter 4.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.42.128