Chapter 7. Standard Agent Services

The agent layer of the JMX architecture is formed by the MBean server and a set of agent services that are registered to the MBean server. The agent services are MBeans themselves and are handled by the MBean server in a manner similar to any managed resource. This allows the configuration of the agent based on the requirements of each management system, and also makes parts of the agent itself manageable through the JMX instrumentation layer.

The JMX specification does not set any specific requirements for MBeans that are registered as agent services. However, the specification does define four separate services that can be considered standard and can be expected to be available on every compliant JMX implementation. The standard agent services are as follows:

  • M-Let Service

  • Timer Service

  • Monitoring Service

  • Relation Service

The services are shown in Figure 7.1. In this chapter, you will go through each standard agent service and look at examples using them.

The standard services of the agent layer.

Figure 7.1. The standard services of the agent layer.

Note

The standard agent services are mandatory services implemented by all compliant JMX implementations.

Recall from Chapter 2, “Architecture,” that the MBean server itself does not differentiate between agent service MBeans and regular managed resource MBeans. That is to say, the distinction is purely on the architectural level and not on the code implementation level. When agent service MBeans are registered to the MBean server, they are handled the same way as any other MBean.

M-Let Service

The M-Let service is a mechanism defined by the JMX specification to dynamically load new Java classes to the MBean server. The new classes can be loaded from a local machine or they can be downloaded from a remote host accessible from the network. In addition, the M-Let service allows the configuration of the application to be moved onto a remote server, thus allowing a centralized location of the configuration of an application or server.

In the next pages, you will see how the M-Let service is used to write a network-enabled application that will load all of its MBean components from a remote host.

The M-Let service itself is, as are all other standard agent services, an MBean. The MLetMBean exposes a management interface that allows you to add new Java classes to the agent or load an M-Let text file. You will take a closer look at the management interface of the M-Let service in the next section.

MLetMBean

The MLetMBean interface exposes the relevant operations of the M-Let service for management. You can add new classes to the agent by providing an URL of a Java archive or an M-Let text file, which allows you to automate both the loading of new classes and the registration of the MBeans to the agent.

First, let's go through the most relevant operations of the MLet MBean. The addURL() operation enables the addition of new classes to the agent. The getMBeansFromURL() operation allows the loading of the M-Let text file to instantiate and register the MBean components to the server. In addition, the MLetMBean interface exposes operations for finding resources in addition to class files via the getResource(), getResources(), and getResourceAsStream() methods and listing the URLs that have been added to the MLet class via the getURLs() operation.

MLET Tag

The getMBeansFromURL() operation allows you to read an M-Let text file from a given URL. The M-Let text file is a collection of MLET tags that describe MBean components. With the MLET tags, it is possible to describe a set of MBeans that should be loaded from the network and registered to the MBean server.

The MLET tag is very similar in format to a regular XML document tag. However, be aware that although the M-Let text file looks very close to an XML file, it in fact is not. The agent implementation is not required to be able to parse XML files, so the only format of the M-Let text file that is guaranteed to work is the tag described here. Any additional features, such as the XML style <!- and -> comment tags, are not within the scope of the JMX specification and may not be supported.

The definition of the MLET tag is as follows.

<MLET  CODE = class  |  OBJECT = serfile
        ARCHIVE = "archivelist"
        [CODEBASE = codebaseURL]
        [NAME = MBeanName]
        [VERSION = version]
>

        [arglist]

</MLET>

The MLET tag allows the declaration of the MBean class, the Java archive (JAR) it can be located in, and optionally define from where the Java archive can be loaded. It is also possible to set the object name that should be used to register the MBean to the MBean server.

Each of the attributes of the MLET tag is described in detail in the list that follows.

  • CODEThe CODE attribute specifies the fully-qualified classname of the MBean to instantiate and register to the agent. The class specified by this attribute must be available in one of the Java archive files listed with the ARCHIVE attribute.

    Either CODE or the OBJECT attribute must be present in the MLET tag.

  • OBJECTThe OBJECT attribute can be used in place of the CODE attribute to point to a serialized instance of an MBean. The file containing the serialized representation of the MBean must be contained in one of the archives specified by the ARCHIVE attribute. If the Java archive contains a directory hierarchy, the correct path to the serialized file must be specified in full.

    Either CODE or the OBJECT attribute must be present in the MLET tag.

  • ARCHIVEThe ARCHIVE attribute lists one or more Java archive files that are added to the MLet's class loader. One of the archives listed with this attribute must contain the class or .ser file specified with the CODE or OBJECT attribute, respectively.

    If more than one Java archive is listed with the ARCHIVE attribute, they must be separated by a comma (,) character. The entire list must be enclosed in double quotation marks. The additional Java archives can contain support classes required by the MBean implementation.

    For example,

    <MLET CODE=com.mycompany.Foo
          ARCHIVE="MyComponents.jar,AcmeComponents.jar">
    </MLET>
    

    The Java archives specified with the ARCHIVE attribute must be found in the URL specified with the CODEBASE attribute. If no CODEBASE is specified within the MLET tag, the default code base is used. The default code base is set to the URL where the M-Let text file was loaded.

    The ARCHIVE attribute is mandatory in the MLET tag.

  • CODEBASEThe CODEBASE attribute defines an URL from which the Java archives specified with the ARCHIVE attribute can be loaded. Notice that the code base does not have to point to the same location as the URL for the M-Let text file.

    If the CODEBASE attribute is not defined in the MLET tag, the M-Let service uses the URL of the M-Let text file as its base. In this case, all the Java archives in the ARCHIVE attribute must be found in the same location as the M-Let text file.

    The CODEBASE attribute is optional in the MLET tag.

  • NAMEThe NAME attribute specifies the object name with which the loaded MBean is registered when the M-Let service adds the component to the agent. The object name is given in a string form. The string form must be a valid object name as was described in Chapter 6, “MBean Server.”

    The NAME attribute is optional in the MLET tag.

  • VERSIONThe VERSION attribute can be used to specify a version of an MBean and its associated Java archives. The VERSION attribute can be used by the M-Let service to determine whether the archives already loaded to the server should be updated.

    Notice that the current 1.0 version of the Sun Reference Implementation does not make use of the VERSION attribute.

    The VERSION attribute is optional in the MLET tag.

  • arglistThe arglist defines a list of arguments passed to the MBean's constructor. The M-Let service attempts to find a constructor that has a matching signature to the contents of the arglist. If such constructor is found, it is invoked when the MBean is instantiated in the agent.

    The arglist consists of one or more ARG tags. The ARG tag has two attributes, TYPE and VALUE, and can be defined as follows:

    <ARG TYPE=argumentType VALUE=argumentValue>
    

    The TYPE attribute consists of the fully-qualified classname of the argument type, and the VALUE attribute consists of a string representation of the argument value.

    Notice that because the argument values are restricted to types that can have a string representation, not every kind of constructor can be invoked through the M-Let file. Generally, arguments with String type or the primitive types, and their corresponding classes, work with the M-Let service implementation. You may need to check with your JMX implementation for which argument types are supported with the M-Let service.

    The arglist is an optional element inside the MLET tag. If no arglist is specified, a default no-args constructor is invoked.

Hot Deployment Revisited

In Chapter 4, “Dynamic MBeans,” you saw how the different implementations of the User resource could be switched behind the scenes, away from the management applications. You saw that, with the decoupled JMX architecture and its invocation mechanism, it was possible to change the resource implementation on-the-fly, without having to shut down the server or detach the client communication.

One restriction with the example in Chapter 4 was that the classes you recycled needed to be available in the JVM at startup time. In a real life scenario, this is rarely the case, because you will want to update parts of your application with new components or introduce updated versions of the existing ones. The only way to bring new classes to the application, in this case to the JMX agent, is to dynamically load them to the JVM.

The JMX agent provides this capability via the M-Let service. With the M-Let service, you can add new classes to the agent dynamically. You can also use the M-Let file to locate and load MBeans from a remote host and register them to the MBean server.

You will now revisit the Recycler example from Chapter 4 to re-implement the Recycler MBean to load the User resource implementations dynamically using the M-Let service. You will also use the M-Let file to load the Recycler MBean, leaving only the creation of the MBean server and the registration of the M-Let service to the core application. The core of the application that is responsible for the loading of the other application components is often called the bootstrap application.

The general design of the application is shown in Figure 7.2.

M-Let based application design.

Figure 7.2. M-Let based application design.

With the design shown in Figure 7.2, you can build a central configuration and component management where the administrator can configure and update components in one location. The JMX-enabled application using M-Lets can then update and reconfigure themselves from dedicated servers without requiring administrator interaction on each host machine (see Figure 7.3). For each node, only the bootstrap is required, which creates the MBean server and registers the M-Let service. The bootstrap is capable of loading the list of MBean components for the application and each individual component from their respective host machines. This greatly reduces the amount of work on the administrator's part and provides a simple yet powerful mechanism of distributing software updates to the clients.

Centralized management of JMX-enabled software.

Figure 7.3. Centralized management of JMX-enabled software.

Now, take a look at the new and improved Recycler implementation. The signature of the recycle() management operation has changed slightly. It will now require two parameters—a classname string and a Java archive URL string. In addition, the recycle() method have declares MalformedURLException to be thrown if there is a problem converting the URL string to an actual URL object.

The updated recycle() management operation allows you to supply the classname you want to bind to the object name "example:name=User". The URL parameter is used for providing the Java archive location in the network where the class is located. You can now load any implementation from the network to be added as the User resource.

The management interface of the new Recycler component is shown in Listing 7.1.

Example 7.1. RecyclerMBean.java

package book.jmx.examples;

import java.net.*;

public interface RecyclerMBean {

  public String recycle(String clazz, String url)
      throws MalformedURLException;

}

The Recycler implementation has changed a bit more from what you saw in Chapter 4. The given URL will be added to the MLet instance via the addURL() method. If a User MBean already exists in the server, it is unregistered and replaced by the loaded component.

There are a couple of things you should notice here. First, the MLet instance is used to load the new component at runtime. Therefore, the new classes need not be available when the application is started.

Second, a new MLet instance is created for each loaded component. There is a very specific reason for this. In Java, there is no explicit way to tell a class loader to unload one of its classes. The unloading of a class only occurs when no live references to the class loader exist any longer and it is garbage collected. Therefore, if you want to load an updated version of one of the existing classes you need to create a new class loader for it. This can be done quite easily via M-Lets, as you can see in Listing 7.2. A new MLet instance is created for each loaded component and it is explicitly referenced in the instantiate() call to indicate to the MBean server which class loader should be used to load the class. Because a Java class identity is formed by its fully-qualified name and its defining class loader, an updated version of the class can be loaded to the same JVM in this manner. The newly registered MBean class is associated with its own class loader.

The mechanism just described allows you to reload new, updated versions of the classes to the server. In the example, the old resource is replaced with the new class instances. However, it would also be possible to register the new resource instance with a different object name and have two different versions of the same class servicing the clients.

The revised implementation of the Recycler MBean is shown in Listing 7.2.

Example 7.2. Recycler.java

package book.jmx.examples;

import javax.management.*;
import javax.management.loading.*;
import java.net.*;

public class Recycler
  implements RecyclerMBean, MBeanRegistration {

  protected String component   = "Standard User";
  private MBeanServer server = null;

  // retrieve the server reference
  public ObjectName preRegister(MBeanServer server,
                                ObjectName name) {

    this.server = server;
    return name;
  }

  // recycle implementation

  public String recycle(String clazz, String url)
      throws MalformedURLException {

    ObjectName user = null;
    URL jarURL      = new URL(url);

    try {

      user = new ObjectName("example:name=User");

      // create new MLet class loader
      ObjectName loader = new ObjectName(
          "Loader:class=" + clazz +
          ",timestamp="   + System.currentTimeMillis()
      );

      MLet mlet = new MLet();
      mlet.addURL(jarURL);

      // unregister the old implementation
      if (server.isRegistered(user))
          server.unregisterMBean(user);

      // register the loaded mbean
      server.registerMBean(mlet, loader);
      Object mbean = server.instantiate(clazz, loader);
      server.registerMBean(mbean, user);

      component = clazz;
    }
    catch (JMException e) {
      e.printStackTrace();
    }

    return "Implementation changed to " + component;
  }


  public void postRegister(Boolean b) { }
  public void preDeregister() throws Exception { }
  public void postDeregister() { }

}

As was mentioned earlier, the example application will also load the MBean components from the network. To achieve this, you will need a minimal application bootstrap called NetworkApp. It creates an MBean server, registers the M-Let service, and loads the M-Let text file from the network. This sequence is shown in Figure 7.4.

Sequence of operations of the bootstrap application.

Figure 7.4. Sequence of operations of the bootstrap application.

The NetworkApp is not specific to the Recycler example. You can use it to load any application. The NetworkApp class could be used as a bootstrap for any application, from something as simple as the Recycler example to something as complicated as an entire J2EE application server.

The NetworkApp code is shown in Listing 7.3.

Example 7.3. NetworkApp.java

package book.jmx.examples;

import javax.management.*;
import javax.management.loading.*;
import java.io.*;
import java.net.*;

public class NetworkApp {

  public static void main(String[] args) {

    // create the MBean server
    MBeanServer server =
        MBeanServerFactory.createMBeanServer();

    ObjectName name = null;

    try {

      // instantiate and register the M-Let service
      name = new ObjectName("service:name=MLet");
      server.registerMBean(new MLet(), name);

      // load M-Let text file
      server.invoke(name, "getMBeansFromURL",
        new Object[] {
            new URL("http://myconfigserver/conf.mlet")
        } ,
        new String[] {
            URL.class.getName()
        }
      );

      // create and register HTTP adaptor
      com.sun.jdmk.comm.HtmlAdaptorServer adaptor =
          new com.sun.jdmk.comm.HtmlAdaptorServer();

      server.registerMBean(adaptor,
          new ObjectName("adaptor:protocol=HTTP"));

      adaptor.start();
    }
    catch (JMException e) {
      e.printStackTrace();
    }
  }

}

Creating the MBean server, registering the M-Let service, and loading the M-Let file takes less than ten lines of code. This is a very small footprint bootstrap code capable of loading a complex application. All that is required from the software is to use a component-based design, in this case, MBeans.

The last thing you need to write is the M-Let file and compile and package the components. The M-Let file is loaded from the URL shown in Listing 7.3, http://myconfigserver/conf.mlet. You should replace the URL to match the setup of your system. If you want to read the M-Let file from a local file system, you can write the URL using the file: identifier, for example, file://C:/Examples/conf.mlet.

The configuration shown in Listing 7.4 is quite simple, as it only loads the Recycler MBean.

Example 7.4. conf.mlet

<MLET
    CODE=book.jmx.examples.Recycler
    ARCHIVE=recycler.jar
    CODEBASE=http://mycomponentserver
    NAME=example:name=recycler>
</MLET>

The final step is to compile and package the components. The packages are regular Java archives and do not require any special meta information in manifest files. If you have compiled the previous examples to your working directory, the classes should be under book/jmx/examples directory. From the work directory, you can then execute the following commands:

C: Examples> javac -d . -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar;
conf.mlet jmx-1_0_1-ri_bin jmx lib jmxtools.jar NetworkApp.java Recycler.java RecyclerMBean.java

C: Examples> jar -cvf user.jar book/jmx/examples/User.class book/jmx/examples/UserMBean.class
C: Examples> jar -cvf user2.jar book/jmx/examples/BroadcastingUser.class book/jmx
conf.mlet/examples/BroadcastingUserMBean.class
C: Examples> jar -cvf user3.jar book/jmx/examples/DynamicUser.class

C: Examples> jar -cvf recycler.jar book/jmx/examples/Recycler.class book/jmx/examples
conf.mlet/RecyclerMBean.class
C: Examples>java -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar; jmx-1_0_1-ri_bin
conf.mlet jmx lib jmxtools.jar book.jmx.examples.NetworkApp

This should give you four packaged components—user.jar, user2.jar, user3.jar, and recycler.jar. The user archives contain the Standard User MBean, the broadcasting User MBean sand a Dynamic User MBean, respectively.

Drop the Java archives to a Web server, if you have one available, or set one up on your local machine. Edit the conf.mlet file to point to the correct URL in the CODEBASE attributes and edit the NetworkApp to load the conf.mlet file from the correct location.

You can also load the components from the local file system by using the file: protocol identifier in the URL. See the Java API documentation for detailed information on how to convert the instances of File class to URL form using the toURL() method.

After you have packaged the components and started the NetworkApp application, you can browse the agent using the HTTP adaptor, as shown in Figure 7.5. In the Recycle MBean, try to load another implementation of the user resource to replace the one packaged in user.jar.

View of the recycle operation (Sun HTTP Adaptor).

Figure 7.5. View of the recycle operation (Sun HTTP Adaptor).

You can now switch between different implementations of the User MBean. You can also update the existing classes with new versions and dynamically load them to the system.

Timer Service

As part of the standard agent services, the JMX specification defines a Timer service. The Timer service is based on the JMX notification mechanism and can be used to schedule notifications to occur at a given date and time or at defined intervals. The JMX Timer service is a similar service to the cron service on Unix and Linux systems, and the Task Scheduler Service on Windows NT systems. The Timer service offers similar functionality to these operating system services in the Java management domain.

The Timer service classes can be found in the javax.management.timer package. The service consists of one interface and two classes:

TimerMBean
Timer
TimerNotification

The TimerMBean is the management interface of the Timer service. The management interface contains methods for starting and stopping the service and adding and removing scheduled notifications to the service. Having the Timer service implemented as an MBean allows you to set up and configure a scheduler service using the same tools and methods you would use to manage any other MBean.

The TimerNotification class is a specialization of the JMX Notification class and is used for all notifications from the Timer service. The notification type is specified by the application when a new timed event is scheduled.

Timer MBean

The Timer MBean is a broadcaster MBean, so it implements the NotificationBroadcaster interface. To receive notifications from the timer, the notification consumer must implement the NotificationListener interface and register itself to the timer via addNotificationListener() method. This is the normal registration procedure for all management event consumers that were discussed in Chapter 3, “Standard MBeans.”

As part of its management interface, the Timer MBean exposes methods to schedule notifications to the service. The scheduled notifications can be sent only once or can be repeated within a given interval. The repeated notifications can define a maximum number of occurrences they are sent or can be sent indefinitely. To schedule a notification, you must use the addNotification() method. The TimerMBean management interface exposes three overloaded versions of the addNotification() method that are shown here.

public Integer addNotification(String type, String message,
        Object userData, Date date);

public Integer addNotification(String type, String message,
        Object userData, Date date, long period);

public Integer addNotification(String type, String message,
        Object userData, Date date, long period, long occurrences);

The type argument is the notification type available for the notification filter and listener via the getType() method of the Notification class. Similarly, the message and userData arguments are used to initialize the Notification objects sent from the Timer service.

The date argument is the scheduled time point when the notification is sent. If the notifications are repeated, the date represents the time of the first notification being sent in the sequence. For repeated notifications, the period argument must be used that represents the interval between notifications in milliseconds. If the repeated notifications should not continue indefinitely, use the occurrences argument. If the number of occurrences is greater than zero, only the defined number of notifications will be sent. For indefinite numbers of notifications, the occurrences should be zero or null, and period should be any non-zero value.

The returned integer from the addNotification() call is an identifier for the scheduled notification or notifications. The identifier is attached to all TimerNotification objects sent by the Timer service. The identifier is available via the getNotificationID() method of the TimerNotification class. The identifier is unique in the Timer MBean instance and will remain the same no matter how many occurrences the scheduled event contains.

The next example shows a Timer service MBean registered to the agent, and another MBean, TimerReceiver, registered for receiving a scheduled notification from the service. The notification is set to go off five seconds after it has been added to the Timer service. The code is shown in Listings 7.5, 7.6, and 7.7.

Example 7.5. TimerAgent.java

package book.jmx.examples;

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

public class TimerAgent {

  private MBeanServer server  = null;
  private ObjectName timer    = null;
  private ObjectName receiver = 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();

    try {

      // register the timer and receiver mbeans
      timer    = new ObjectName("service:name=timer");
      receiver = new ObjectName("example:name=listener," +
                                "source=timer");

      server.registerMBean(new Timer(), timer);
      server.registerMBean(new TimerReceiver(), receiver);

      // start the timer service
      server.invoke(timer, "start", null, null);

      // add scheduled notification to five seconds
      // past the registration
      Date date = new Date(System.currentTimeMillis() +
          Timer.ONE_SECOND * 5);

      Integer id = (Integer)server.invoke(
        timer,                          // MBean
        "addNotification",              // operation

        new Object[] {                  // arguments:
          "timer.notification",         // type
          "Scheduled notification.",    // message
          null,                         // user data
          date
        } ,

        new String[] {                   // signature
          String.class.getName(),
          String.class.getName(),
          Object.class.getName(),
          Date.class.getName()
        }
      );

      // add listener to the timer
      NotificationFilter filter = new TimerFilter(id);
      server.addNotificationListener(
                  timer, receiver, filter, null);
    }
    catch (JMException e) {
        e.printStackTrace();
    }
  }


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

    private Integer id = null;

    TimerFilter(Integer id) {
      this.id = id;
    }

    public boolean isNotificationEnabled(Notification n) {

      if (n.getType().equals("timer.notification")) {
        TimerNotification notif = (TimerNotification)n;

        if (notif.getNotificationID().equals(id))
          return true;
      }

      return 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 TimerAgent().run();
  }
}

Example 7.6. TimerReceiver.java

package book.jmx.examples;

import javax.management.*;
import javax.management.timer.*;

public class TimerReceiver
    implements TimerReceiverMBean, NotificationListener {

    public void handleNotification(Notification n, Object hb) {
        System.out.println(n.getMessage());
    }
}

Example 7.7. TimerReceiverMBean.java

package book.jmx.examples;

import javax.management.*;
import javax.management.timer.*;

public interface TimerReceiverMBean {

}

Compile the three classes:

C: Examples> javac -d . -classpath jmx-1_0_1-ri_bin jmx lib jmxri.jar TimerAgent.java
TimerReceiverMBean.java TimerReceiver.java TimerReceiverMBean.java
C: Examples> java -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar book.jmx.examples
TimerReceiverMBean.java.TimerAgent

When you run the example, you should see a message printed on the console about five seconds after the startup.

Scheduled notification.

If you try to schedule notifications to a date that is earlier than the current date of the Timer service, the service will try to update the scheduled notifications until the current time is reached. If the scheduled notifications have a period set to them, the Timer service will add the time period to the submitted date until it has reached the current date in the service. When the next scheduled notification is set at the current date or past it, the rest of the scheduled notifications will be processed as usual.

If the scheduled notifications had the occurrences argument set with the period, the Timer service adds only the number of periods allowed by the occurrences argument to the submitted date. If the submitted date will reach the current date or past that, the notification occurrences that are left for the scheduled event will be processed as usual. If, however, the addition of the maximum number of occurrences will not reach the current date in the Timer service, an IllegalArgumentException is thrown. The exception indicates that all the occurrences of the scheduled notifications are scheduled for a date earlier than the current date and cannot be sent.

Table 7.1. Operations of the Timer Service

Operation Description
start()

Starts the Timer service.

If the SendPastNotifications attribute has been set to true, any notifications that occurred during the time the service was not running will be sent as soon as the start() is called.

stop()

Stops the Timer service and disables all scheduled notifications until the service is started again.

If the SendPastNotifications attribute has been set to true, the notifications that occur during the time the service is stopped will be sent as soon as the service is started again.

getSendPast Notifications() Allows the manipulation of the SendPastNotifications attribute.
setSendPast Notifications() This attribute indicates whether the notifications scheduled to be sent while the service has been stopped should be sent when it is started again.
getAllNotification IDs()

Returns all the notification identifiers that have been registered to the timer service.

The returned object is a vector of Integer objects. If there are no registered notifications in the timer service, an empty vector is returned.

getNotificationIDs (String type)

Returns all the notification identifiers that have been registered with the given notification type.

The returned object is a vector of Integer objects. If no matching identifiers are found, an empty vector is returned.

getNbOccurences() Returns the number of notifications scheduled in the timer service.
isActive() Returns a Boolean value indicating whether the Timer service is currently active. The Timer service is activated via the start() operation.
isEmpty() Returns a Boolean indicating whether the scheduled notification queue is empty.
removeAllNotification (String type) Removes all scheduled notifications from the Timer service.
Remove Notifications Removes all notifications based on the notification type (dot separated string).
removeNotification (Integer id) Removes a notification from the Timer service based on the identifier.

In addition to the operations listed in Table 7.1, the Timer service exposes operations for reading the values of individual scheduled notifications based on their identifier. You can retrieve the number of occurrences left for the notification with getNbOccurences() operation, retrieve the period value with getPeriod() operation, and read the date when the notification is scheduled to be sent with the getDate() operation. Notice, that these values are not modifiable after you have scheduled the notification to the Timer service. If you need to change the period or number of occurrences of start date, you need to remove the old notification and schedule a new one with a different identifier.

Monitoring Service

The Monitoring service in the JMX agent layer defines a set of MBeans that can be used to monitor the attributes of managed resources. The Monitoring service implements three different types of monitor MBeans. You can utilize these MBeans to provide notifications that indicate attribute changes in the observed MBean. The monitor notifications differ from the usual attribute change notifications in that you can provide a threshold and granularity period to the notifications. In the case of the attribute change notifications, the broadcaster MBean will send an event with every change of the attribute. This can become a burden to the system if the attribute is changed frequently and also can bog down the receiver if it must manage too many notifications from the emitting MBean.

With the Monitoring service, you can configure the notifications to only occur at a given granularity period, for example, every three seconds. You can also set the threshold for the attribute value being monitored. In the case that the value has not changed during the granularity period, or the change has been a minor one not requiring a notification, the notification with the redundant information will not be sent.

The JMX Monitoring service provides three different types of monitor implementations:

  • Counter monitor

  • Gauge monitor

  • String monitor

The counter monitor can be used to track attribute values that act like counters. This means that the value being monitored is integer type, is always greater than or equal to zero, and is only incremented. The gauge monitor can be used to monitor attribute values that are either integer or floating point types and arbitrarily either increase or decrease. The string monitor can be used to monitor attributes of the String type and notify the interested listeners whether the observed attribute value matches an expected string value or differs from it.

All the three monitors are based on a common abstract superclass, Monitor, which contains the implementation for functionality shared with the different monitor types. All the notifications sent by the monitors are instances of the MonitorNotification class. The MonitorNotification class extends the JMX Notification class to provide some additional information for the monitoring events.

Because all the monitors are implemented as MBeans themselves, they can be dynamically configured at runtime The monitors can also be temporarily stopped and restarted through their management interfaces.

Let's look at each of the relevant classes in the Monitoring service next.

Monitor

The Monitor class is an abstract superclass for all concrete monitor classes. Table 7.2 lists the most relevant methods of the Monitor class.

Table 7.2. Partial List of the Methods of the Monitor Class

Operation Description
start() Starts the attribute monitoring. Notice that an explicit call to the start() operation is required to initially activate the service.
stop()

Stops the monitor. The stop() operation can be called at any time by an management application or another MBean.

Unregistering the monitor MBean from the MBeanServer will implicitly stop the monitor.

isActive() Returns a Boolean value true if the monitor has been started.
getObservedObject()
setObservedObject()
Read-write access to the ObservedObject attribute.
getObservedAttribute() Read-write access to the ObservedAttribute attribute.
setObservedAttribute() The observed attribute is a management attribute of an observed MBean set via the setObservedObject() operation.
getGranularityPeriod() Read-write access to the GranularityPeriod attribute.
setGranularityPeriod() The granularity period is set in milliseconds and indicates how often the observed attribute is being checked when monitoring has been activated.

MonitorNotification

The MonitorNotification class extends the Notification class with methods to access both the observed object and the observed attribute. In addition, you can retrieve the derived gauge value from the notification and the threshold value that caused the notification to be triggered.

The MonitorNotification class defines constants used for the notification type for the three different types of monitors supported by all JMX implementations. The notification types are shown in the Table 7.3.

Table 7.3. Notification Types Defined in the MonitorNotification Class

Notification Type Description
THRESHOLD_VALUE_EXCEEDED This notification type is reserved for the counter monitors. It indicates that the countervalue has increased over the threshold that triggers the notification.
THRESHOLD_HIGH_VALUE_EXCEEDED This notification type is reserved for the gauge monitors. It indicates the monitored value has reached or increased over the high value threshold and triggered an event.
THRESHOLD_LOW_VALUE_EXCEEDED This notification type is reserved for the gauge monitors. It indicates that the monitored value has decreased to or below the low value thresh old and triggered an event.
STRING_TO_COMPARE_VALUE_MATCHED This notification type is reserved for the string monitors. It indicates that the monitored string attribute matches the string-to-compare value.
STRING_TO_COMPARE_VALUE_DIFFERED This notification type is reserved for the string monitors. It indicates that the monitored string attribute differs from the string-to-compare value.

Counter Monitor

The counter monitor observes the attributes that are integer types and behave like counters. The integer types in the Java language are instances of the Short, Long, Byte, and Integer classes. The runtime type of the management attribute being observed by the counter monitor must be either one of the aforementioned types or their corresponding primitive types.

The counter behavior is defined by the JMX specification to have an integer value that is always greater than or equal to zero. Also, the counters can only be incremented—never decreased. It is possible for the counters to roll over at a certain value.

The counter monitor uses threshold value to determine when a notification should be sent to the interested listeners. When the observed attribute's value increases to match or exceed the threshold, a notification is sent. The offset is added to the threshold as many times as it is necessary for the threshold value to become greater than the current observed attribute value. However, only one notification is ever sent, no matter how many times the offset must be added to the threshold value. The behavior of a counter monitor is shown in the Figure 7.6.

Notifications sent by a counter monitor.

Figure 7.6. Notifications sent by a counter monitor.

For counters that roll over at a given point, it is necessary to set a modulus value. The modulus value is set to the value point where the observed counter attribute rolls over. When the threshold is increased, it is checked against the modulus value and, if the increased threshold value is greater than the modulus, the threshold is rolled over and set back to its original value before any offset increments.

The derived gauge is the value derived from the observation of an attribute. The derived gauge can be either the exact value of the observed attribute at a given time or a difference value between two consecutive observations. The type of the derived gauge is determined by the difference mode of the counter monitor.

Notice that the notifications from the counter monitor to the listeners must be explicitly enabled via a setNotify() method call.

Table 7.4 lists the relevant operations of a CounterMonitorMBean interface.

Table 7.4. Partial List of CounterMonitor MBean Operations

Operation Description
getDerivedGauge() Returns the derived gauge that is either the current value of the observed attribute or the difference between two consecutive samples, depending on the difference mode.
getDifferenceMode() Sets the difference mode in the counter monitor.
setDifferenceMode()

If difference mode is true, the derived gauge will return the difference between two consecutive observed attribute values.

If difference mode is false, the current value of the attribute is returned with the getDerivedGauge() operation.

getModulus()
setModulus()
Access to the Modulus attribute. The modulus should be set if the counter value can roll over. In that case the maximum value for the counter should be set as the modulus value in order for the threshold value to roll over with the counter value.
getNotify()
setNotify()
Enables monitor event notification.
getOffset()
setOffset()
Access to the Offset attribute of the counter monitor. The offset is the value added to the threshold whenever the counter value exceeds it. The offset is added as many times as is necessary for the threshold value to exceed the counter value again.
getThreshold()
setThreshold()
Access to the Threshold attribute. The threshold indicates the value that will cause a monitor notification to be sent when the counter value exceeds it.

Gauge Monitor

A gauge monitor is a monitor for integer and floating point types that fluctuate between given high and low thresholds. You can use the gauge monitor to monitor values such as memory consumption or thread count that can either increase or decrease and often oscillate around the threshold emitting the notification.

The GaugeMonitor class extends the abstract Monitor class by introducing HighThreshold and LowThreshold attributes. These attributes are used for setting the limits, which trigger monitor notifications. If the observed attribute either increases to or over the high threshold value or decreases to or below a low threshold value, a MonitorNotification instance is sent to all the interested listeners (see Figure 7.7).

The notifications sent by a gauge monitor.

Figure 7.7. The notifications sent by a gauge monitor.

The notifications sent by the gauge monitor when a high or low threshold boundary has been crossed will have either a THRESHOLD_HIGH_VALUE_EXCEEDED or THRESHOLD_LOW_VALUE_EXCEEDED as their notification type. Both of the constant values are declared in the MonitorNotification class. Also, as with the CounterMonitor class, the notifications must be explicitly enabled via setNofityHigh() and setNotifyLow() methods. In a gauge monitor, you can separately enable either the high threshold notifications, low threshold notifications, or both.

The gauge monitor implements a so-called hysteresis mechanism, which means that the notification is sent only once—when the high or low threshold is crossed for the first time. As a result, a fluctuating value around the threshold will not cause repeated notifications to be sent to the listeners. Another notification triggered by the same threshold boundary will only be sent if the value has crossed the other end of the hysteresis interval between the current and the previously triggered notification.

Table 7.5 lists the most relevant methods of the gauge monitor class.

Table 7.5. Descriptions of the Relevant Operations of the Gauge Monitor Management Interface

Operation Description
getDerivedGauge() Returns the last observed value of the attribute. The DerivedGauge can be either the last observed value or the difference between the last two observations depending on the DifferenceMode of the gauge monitor.
getDerivedGaugeTimeStamp() Returns the time stamp of last attribute value observation.
getDifferenceMode()
setDifferenceMode()

Read-Write access to the DifferenceMode attribute. If DifferenceMode is set to true, the DerivedGauge attribute's value is a difference between the last two consecutive observations. Otherwise, DerivedGauge attribute contains the value of the last observation.

getHighThreshold()
getLowThreshold()
Returns the values of the HighThreshold and attributes of the gauge monitor. The high and low threshold values comprise of the hysteresis interval.
setNotifyHigh(boolean value)
setNotifyLow(boolean value)
Enables the notifications triggered by the gauge crossing either the high threshold or low threshold values.
setThresholds(Number high, Number low) Sets the high and low threshold values. Both the high and low values must be of the same type and must match the type of the observed attribute.
start()
stop()
Starts and stops the monitor

Gauge Monitor Example

One useful application of the gauge monitor is to observe the resource use of the application, for example, a server. It is often important for the administrator to be notified when the server is using up a lot of the machine resources so the administrator can alleviate the problem by balancing the load.

You will next see an example of a gauge monitor that observes the number of threads in the JVM. You will build a simple Standard MBean that provides you with the management attribute that contains the thread count and then monitors that attribute and prints a warning when the high threshold of the gauge monitor is exceeded.

The thread monitor MBean implementation is fairly simple. You define a management interface with one read-only attribute, ThreadCount. The management interface declaration and the MBean implementation are shown in Listings 7.8 and 7.9.

Example 7.8. ThreadMonitorMBean.java

package book.jmx.examples;


public interface ThreadMonitorMBean {

    int getThreadCount();
}

Example 7.9. ThreadMonitor.java

package book.jmx.examples;


public class ThreadMonitor implements ThreadMonitorMBean {

    ThreadGroup root = null;

    public ThreadMonitor() {

        ThreadGroup group =
            Thread.currentThread().getThreadGroup();

        while (group.getParent() != null)
          group = group.getParent();

        root = group;
    }

    public int getThreadCount() {
      return root.activeCount();
    }

}

In the ThreadMonitorClient class, you create the MBean server, register the thread monitor MBean to it, and then create and configure the gauge monitor. You set the observed attribute via the setObservedAttribute() method and set the granularity period of the observations to five seconds via the setGranularityPeriod() method.

The gauge monitor is configured to send a notification when the high threshold of the monitor is crossed. To better demonstrate the example, the high threshold is set to a relatively low value of thirty threads and the low threshold to twenty threads. This means that the notification is sent when the ThreadCount first exceeds the thirty thread limit. Another notification will not be sent before the thread count decreases below twenty threads. Because the thread count is likely to fluctuate in a real-life scenario, this prevents the administrator from being spammed with a constant stream of warnings.

After the monitor has been configured, it is registered to the MBean server and started. You then register a listener to the monitor that will print out a warning whenever the high threshold notification is triggered. The threshold is tested by creating 35 sleeper threads in the JVM.

When you run the ThreadMonitorClient application, you should see the following message being printed to the console about five seconds after startup.

Warning, Thread count exceeds 30
Current Thread count = 40

The high threshold value is retrieved from the MonitorNotification class via the getTrigger() method and the actual observed count via the getDerivedGauge() method. The source for the ThreadMonitorClient is shown in Listing 7.10.

Example 7.10. ThreadMonitorClient.java

package book.jmx.examples;

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

public class ThreadMonitorClient {

  private MBeanServer server  = null;
  private ObjectName threads  = null;
  private ObjectName monitor  = 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();

    try {
      // register the MBean reporting thread count
      threads = new ObjectName("Observable:type=Threads");
      server.registerMBean(new ThreadMonitor(), threads);

      // configure the monitor
      GaugeMonitor threadMon = new GaugeMonitor();
      threadMon.setObservedObject(threads);
      threadMon.setObservedAttribute("ThreadCount");
      threadMon.setGranularityPeriod(5 * 1000);
      threadMon.setNotifyHigh(true);

      threadMon.setThresholds(
          new Integer(30), new Integer(20)
      );

      // register the monitor
      monitor = new ObjectName("Monitor:type=Threads");
      server.registerMBean(threadMon, monitor);
      threadMon.start();

      server.addNotificationListener(
          monitor, new MonitorListener(), null, monitor
      );

      // test the monitor by creating 35 extra threads
      for (int i = 0; i < 35; ++i) {
        Thread thread = new Thread(new Runnable() {

            public void run() {
                try {
                    Thread.sleep(10000);
                }
                catch (Exception ignored) { }
            }
        } );

        thread.start();
      }

    }  catch (JMException e) {
      e.printStackTrace();
    }
  }


  // notification listener implementation

  class MonitorListener implements NotificationListener {

    public void handleNotification(Notification n,
                                   Object handback) {

      MonitorNotification notif = (MonitorNotification)n;

      if (notif.getType().equals
       (MonitorNotification.THRESHOLD_HIGH_VALUE_EXCEEDED)
      ) {

        System.out.println(
            "Warning, Thread count exceeds " +
            notif.getTrigger()
        );

        System.out.println(
            "Current Thread count = " +
            notif.getDerivedGauge()
        );
      }
    }
  }


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

    MBeanServer server =
        MBeanServerFactory.createMBeanServer();

    new ThreadMonitorClient().run();
  }
}

To compile and run the thread monitor example, execute the following commands on the console:

C: Examples> javac -d . -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar ThreadMonitor
ThreadMonitorClient.java.java ThreadMonitorMBean.java ThreadMonitorClient.java

C: Examples> java -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar book.jmx.examples
ThreadMonitorClient.java.ThreadMonitorClient

StringMonitor

The third and last of the monitor types is the string monitor. The string monitor enables the monitoring of management attributes that are of the Java String type. It is possible to configure the string monitor to trigger a notification either when the observed string attribute matches that of the compared string or when the observed string attribute differs from the compared string.

The configuration of the string monitor is achieved via the NotifyDiffer and NotifyMatch attributes. Setting the NotifyDiffer to true will enable notifications whenever the observed string attribute differs from the StringToCompare attribute. Similarly, setting the NotifyMatch attribute to true enables notifications to be sent whenever the observed string matches the StringToCompare attribute. Setting both NotifyDiffer and NotifyMatch attributes to true will cause notifications to be sent whenever the observed attribute's condition changes.

Table 7.6 lists the relevant methods of the string monitor MBean.

Table 7.6. Operations of the StringMonitorMBean Interface

Method Description
getDerivedGauge() Returns the value of the observed string attribute. In the case of the string monitor, there is no difference mode. The returned string is always the value of the observed attribute.
getDerivedGaugeTimeStamp() Returns the time stamp of the last observation used for setting the derived gauge.
getStringToCompare()
setStringToCompare()
Read-Write access to the StringToCompare management attribute. The string is used with comparison to the observed attribute.
getNotifyDiffer()
setNotifyDiffer()
Read-Write access to the NotifyDiffer management attribute. Setting this attribute to true will triggernotifications whenever the value of the observed attribute differs from the value of the tringToCompare attribute.
getNotifyMatch()
setNotifyMatch()
Read-Write access to the NotifyMatch management attribute. Setting this attribute to true will trigger notifications whenever the value of the observed attribute differs from the value of the StringToCompare attribute.

Relation Service

As a fourth standard service, the JMX specification defines a Relation service for MBeans. The Relation service can be used for defining relations between MBeans, defining roles to MBeans, and associating MBean instances in different roles as part of the relations.

The Relation service maintains consistency of the relations. Consequently, if an MBean belonging to a relation is unregistered and the relation no longer can be considered consistent, the relations are removed from the service. Notice, however, that the relation service never directly manipulates any MBeans associated with the relations. Instead, the relation service emits notifications on changes in the relation instances, such as creation, updates, and removal of relations.

The Relation service consists of metadata information that is used to describe the relation types and the roles participating in the relation. In addition, as with all other standard services, the relation service itself provides a management interface that allows the manipulation of the relations. You will next go through the relevant classes of the relation service and finish with an example demonstrating the use of this service.

Relation Service Metadata

The Relation service uses two different metadata classes to describe the relation types and role information of relation instances. Each MBean that is associated with a relation through the Relation service participates in a described role of that relation instance. The roles are described with RoleInfo objects and composed of RoleType objects that act as templates for the kinds of relations that can be registered to the Relation service.

RoleInfo

The RoleInfo class of the JMX specification describes the roles of a relation. The RoleInfo class consists of the name of the role, the multiplicity of the role in a given relation, the classname of the MBean participating in the role, and a description of the role. The multiplicity of the role is expressed as a range between a minimum and maximum number of MBeans that can participate in that role.

To create role information for a role named "Monitor", the following code can be used:

RoleInfo monitorInfo = new RoleInfo(
    "Monitor",                              // role name
    "javax.management.monitor.GaugeMonitor",// class name
    true, true,                             // isReadable, isWritable
    0, RoleInfo.ROLE_CARDINALITY_INFINITY,  // cardinality [0..*]
    "Describes a Monitor role."             // description
);

The previous code snippet describes a role that consists of zero or more instances of JMX GaugeMonitor MBean instances.

Similarly, to describe a role for an observable MBean that can be observed by a gauge monitor MBean, the following role info could be created:

RoleInfo observableInfo = new RoleInfo(
    "Observable",                           // role name
    "book.jmx.examples.ThreadMonitor",      // class name
    true, true,                             // isReadable, isWritable
    1, 1,                                   // cardinality [1..1]
    "Describes an observed MBean role."     // description
);

RelationType

Where RoleInfo class describes the metadata of MBean roles in a relation, the RelationType interface is used for describing the metadata of the relation instances. The relation types are collections of role information objects. A RelationType instance describes a relation in terms of what roles can participate in the relation and what consistency rules must be followed for the relation to be valid. The relation type also contains the name of the relation template that is used as an identifier.

To create a relation type metadata for a Relation instance that represents the relation between an observed MBean and the monitor MBeans, you can create a relation template and call it "ObservedMBean". The "ObservedMBean" relation consists of two roles, "Observable" and "Monitor" where one observable component may have zero or more monitors. Using the RelationTypeSupport class, you can create this relation type using the following snippet of code:

RelationTypeSupport relationType = new RelationTypeSupport(
    "ObservedMBean",
    new RoleInfo[] {  observableInfo, monitorInfo }
);

The previous code creates a relation type template that expects the participating components to meet the consistency requirements set in the role information metadata objects.

Relation Service MBean

The Relation service provides a management interface that allows both the manipulation of relations and queries to relations to be made. Table 7.7 offers a brief overview of the relevant operations of the Relation service.

Table 7.7. Relevant Operations of the Relation Service

Method Description
createRelationType()
addRelationType()

createRelationType() method allows registering a new relation type to the service without implementing the RelationType interface directly. This is a so-called internal relation type.

addRelationType() method requires an implementation of the RelationType interface as an argument. This is a so-called external relation type. The benefit of using external relation types comes from the possibility of creating predefined, static types.

createRelation()
addRelation()

The createRelation() method creates a new relation instance to the relation service. The relation instance is associated with a unique ID string and mustprovide the name of one of the registered relation types in the relation service.

The addRelation() method can be used to create a so-called external relation. The addRelation() method takes an object name of an MBean as an argument. This MBean must implement the Relation interface.

getPurgeFlag()
setPurgeFlag()
purgeRelations()
The PurgeFlag attribute indicates whether the relation service should immediately purge relations that are no longer valid when an MBean is unregistered. The purgeRelations() method allows the explicit purging of non-valid relations if the purge flag is not used.

In addition to the methods listed in Table 7.7, Relation service MBeans expose several methods for querying the service for information on the relations. It is possible to query for relation-associated MBeans via the findAssociatedMBeans() method, look for referencing relation instances via the findReferencingRelations() method, and so on. You will see an example of how to use the query methods in Listing 7.10.

RelationNotification

The relation service broadcasts events on create, update, and remove operations on the relation instances. The notifications are instances of the RelationNotification object, which extends the JMX Notification class.

The RelationNotification class adds methods for retrieving more specific information on the operation that occurred on the relation instance. In the case of a role update notification, you can access the new and old role values. For all Relation service notifications, the affected relation type and the relation ID is sent with the notification.

Role and RoleList

To associate an MBean with a role in the Relation service, the Role class is used. The Role object requires a role name, which must be one of the names defined in the role information objects, and a list of role values. The role value list is an array of object names that represent the MBeans associated with the named role. The role list is given as a specific RoleList list object.

Relation Example

To wrap up all the code snippets shown in the previous pages and the discussion on the Relation service, the next example builds a simple application that creates a relation between an observed MBean and a monitoring MBean.

In the example, you define two role information objects that are identical to the "Observable" and "Monitor" role information discussed earlier. You form a one-to-many relation between the ThreadMonitor MBean from the gauge monitor example and the monitoring MBean. In addition to creating the metadata objects for role information and relation type, you add a listener to the relation service that is interested in the relation removal events. When a relation is removed, the code lists the object names of all referenced MBeans based on the relation ID that is retrieved from the Relation service notification. As you may remember, the relation service itself never manipulates the MBeans directly, so it is up to the application developer to decide how to handle the MBeans that are no longer part of the relation.

Because the example uses a one-to-many relation between the observable MBean and the monitoring MBean, unregistering the monitoring MBean will not trigger a removal notification from the relation service. However, when the observed MBean is unregistered, the relation is no longer valid and a notification of a relation removal is sent. The notification is set immediately because the purge relations flag has been set.

The source code for the relation example is shown in Listing 7.11.

Example 7.11. RelationAgent.java

package book.jmx.examples;

import java.util.*;
import javax.management.*;
import javax.management.monitor.*;
import javax.management.relation.*;

public class RelationAgent {

  public static void main(String[] args) {
    try {

    MBeanServer server = MBeanServerFactory.createMBeanServer();


    // create the "observable" MBean
    ThreadMonitor threads = new ThreadMonitor();
    server.registerMBean(threads, new ObjectName("Example:name=Threads"));

    // create the "observer"
    GaugeMonitor mon      = new GaugeMonitor();
    mon.setObservedObject(new ObjectName(":name=Threads"));
    mon.setObservedAttribute("ThreadCount");
    mon.setGranularityPeriod(5 * 1000);
    mon.setNotifyHigh(true);
    mon.setThresholds(new Integer(30), new Integer(20));

    server.registerMBean(mon, new ObjectName("Monitor:target=Threads"));
    mon.start();

    // create relation service, purgeRelations=true
    ObjectName relationService = new ObjectName("Service:name=Relation");
    RelationService service = new RelationService(true);

    service.addNotificationListener(new RelationListener(), null, null);
    server.registerMBean(service, relationService);

    // "monitor" role meta data
    RoleInfo monitorInfo = new RoleInfo(
        "Monitor",                              // role name
        "javax.management.monitor.GaugeMonitor",// class name
        true, true,                             // isReadable, isWritable
        0, RoleInfo.ROLE_CARDINALITY_INFINITY,  // multiplicity [0..*]
        "Describes a Monitor role."             // description
    );

    // "observable" role meta data
    RoleInfo observableInfo = new RoleInfo(
        "Observable",                           // role name
        "book.jmx.examples.ThreadMonitor",      // class name
        true, true,                             // isReadable, isWritable
        1, 1,                                   // multiplicity [1..1]
        "Describes an observed MBean role."     // description
    );

    // relation type template
    RelationTypeSupport relationType = new RelationTypeSupport(
        "ObservedMBean",
        new RoleInfo[] {  observableInfo, monitorInfo }
    );

    // add relation type to the service
    server.invoke(relationService, "addRelationType",
        new Object[] {  relationType } ,
        new String[] {  RelationType.class.getName() }
    );

    // associate MBean to role
    ArrayList list = new ArrayList();
    list.add(new ObjectName("Monitor:target=Threads"));
    Role monitor = new Role("Monitor", list);

    // associate MBean to role
    list = new ArrayList();
    list.add(new ObjectName("Example:name=Threads"));
    Role target = new Role("Observable", list);

    RoleList roleList = new RoleList();
    roleList.add(monitor);
    roleList.add(target);

    // create the relation
    server.invoke(relationService, "createRelation",
        new Object[] {  "MyRelationID", "ObservedMBean", roleList } ,
        new String[] {
            String.class.getName(),
            String.class.getName(),
            RoleList.class.getName()
        }
    );

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

    server.registerMBean(adaptor,
        new ObjectName("adaptor:protocol=HTTP"));

    adaptor.start();

    }  catch (JMException e) {
      e.printStackTrace();
    }  catch (ClassNotFoundException e) {
      e.printStackTrace();
    }

  }

  static class RelationListener implements NotificationListener {

    public void handleNotification(Notification n, Object hb) {

      if (n instanceof RelationNotification) {
        RelationNotification notif = (RelationNotification)n;

        System.out.println(notif.getRelationId());
        System.out.println(notif.getType());
        System.out.println(notif.getMessage());

        if (notif.getType().equals(
            RelationNotification.RELATION_BASIC_REMOVAL)

        ) {
          try {
            String ID = notif.getRelationId();
            ObjectName relationService = new ObjectName(
                "Service:name=Relation"
            );

            MBeanServer server =
                (MBeanServer)MBeanServerFactory.findMBeanServer(null).get(0);

            Map map = (Map)server.invoke(
                relationService,
                "getReferencedMBeans",
                new Object[] {  ID } ,
                new String[] {  String.class.getName() }
            );

            Iterator it = map.keySet().iterator();

            while(it.hasNext()) {
              System.out.println("MBeans referenced by the relation:");
              System.out.println(it.next());
            }
          }  catch (JMException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }

}

To compile and run this, execute the following commands. The commands assume that you have already compiled the earlier thread monitor example in the current working directory.

C: Examples> javac -d . -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar;
RelationAgent.java jmx-1_0_1-ri_bin jmx lib jmxtools.jar RelationAgent.java

C: Examples> java -classpath .;jmx-1_0_1-ri_bin jmx lib jmxri.jar; jmx-1_0_1-ri_bin
RelationAgent.java jmx lib jmxtools.jar book.jmx.examples.RelationAgent

When you start the agent, you should see a notification printed on the console informing of the creation of a relation with ID string "MyRelationID".

MyRelationID
jmx.relation.creation.basic
Creation of relation MyRelationID

You can now point your browser to http://localhost:8082 and see if the relation behavior acts as expected when you unregister the observed MBean or the monitoring MBean.

Summary

The JMX specification defines four standard agent services that can be used to extend the functionality of the basic agent level. All four standard services are mandatory and therefore are implemented by all compliant JMX agents. Each agent service also exposes a management interface allowing standard management of the JMX agent level itself.

The M-Let service can be used to dynamically load new components to the MBean server. This is particularly useful for applications that require updates and maintenance while providing availability to the clients. The Timer service can be used to schedule notifications for tasks that need to be run once or at regular intervals in the system. Monitoring service defines three different types of monitor MBeans that can be used to monitor the state of management attributes of MBeans. Finally, the Relation service allows you to define relations between MBean components and react to changes in case of MBean dependencies.

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

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