Home > Articles > Programming > Java

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

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.

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

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

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

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

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

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

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

Notifications to Remote Systems

The JMX notification mechanism discussed so far is strictly a local one, notifying only the MBeans or management applications that are located in the same JVM. For most practical purposes, you will need the notifications to be able to propagate from the JVM to others that are possibly located on a different machine across the network.

The distributed services level of the JMX architecture is currently, at its 1.0 version, left mostly unspecified. The distribution in the management architecture is the responsibility of the JMX connectors and protocol adaptors. Therefore, these same components are the most likely candidates to take care of the propagation of management events to remote systems as well.

Because the distribution of the notifications is left unspecified, there are several possibilities how to implement them. The notifications can be sent to the management applications synchronously or asynchronously, they can be persisted, and so on. The actual functionality will often be dictated by the underlying connector implementation that is used to distribute the notifications. We will see some implementation possibilities in the second part of the book, which is more concentrated on the distributed services of the JMX architecture.

  • + Share This
  • 🔖 Save To Your Account