Many management tasks are schedule driven. Incremental workstation backups are scheduled to take place every morning at 2:00 A.M. Web server logs are collected and aggregated every hour . New content or a new service is scheduled to go online at midnight. The list goes on and on. To help developers and administrators handle scheduled activities, JMX provides a basic timer service. Instances of the timer service send JMX notifications at specified dates and times. The notifications may be scheduled to occur just once or to recur with a given frequency. The number of times a recurring notification is sent may be specified beforehand, or the notification may continue to recur indefinitely. Management applications react to timer service notifications by listening for them with notification listeners. The code in Listing 7.1 illustrates the basics of timer service use. Listing 7.1 A Service That Uses Timer Service to RestartRestartingServiceMBean.java package net.jnjmx.ch7; public interface RestartingServiceMBean { void start(); void stop(); } RestartingService.java package net.jnjmx.ch7; import java.util.Date; import javax.management.MBeanServer; import javax.management.MBeanServerFactory; import javax.management.Notification; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.management.timer.Timer; public class RestartingService implements RestartingServiceMBean, NotificationListener { private long tzero = -1; public RestartingService() {} /** Start the service and remember the start time */ public void start() { if (tzero < 0) { tzero = System.currentTimeMillis(); System.out.println("Restarted at: " + tzero); } } /** Stop the service if it is running */ public void stop() { if (tzero > 0) tzero = -1; } /** Restart the service */ public void handleNotification(Notification notification, Object hb) { stop(); start(); } public static void main(String[] args) throws Exception { MBeanServer mbs = MBeanServerFactory.createMBeanServer(); // Create a timer service instance and start it ObjectName ton = new ObjectName("examples:id=RestartTimer"); mbs.createMBean("javax.management.timer.Timer", ton); mbs.invoke(ton, "start", new Object[] {}, new String[] {}); // Schedule a jnjmx.examples.restart notification every 24 hours Integer nid = (Integer) mbs.invoke(ton, "addNotification", new Object[] {"jnjmx.examples.restart", "restart", null, tomorrow(), new Long(Timer.ONE_DAY) }, new String[] {"java.lang.String", "java.lang.String", "java.lang.Object", "java.util.Date", "long" }); // Listen for restart notifications mbs.addNotificationListener(ton, new RestartingService(), null, new Object()); // If you have something useful to do, do it here ... Thread.sleep(Timer.ONE_DAY + 7); } /** Compute 24 hours from now in milliseconds */ private static Date tomorrow() { return new Date(System.currentTimeMillis() + Timer.ONE_DAY); } } To use the timer service we need an instance of it, so we use the MBeanServer's createMBean() method to create and register one. Now we can schedule a notification. We always have to specify the notification type, associated message, user data, and date. The type, message, and user data will be included in the notification that is delivered to listeners at the appointed date. The notification period (the number of milliseconds to delay between recurring notifications) and the number of times the notification is to be sent may be specified as well; if we don't specify how many times to send the notification, it will be sent indefinitely. Finally, each notification is identified by an ID, an integer that uniquely identifies the notification in the context of a given timer service instance. To listen for the notification we scheduled, we attach an instance of NotificationListener to the timer service instance. When the notification is sent, the listener's handleNotification() method will be invoked with an instance of TimerNotification that encapsulates all of the information associated with the notification when it was scheduled. 7.1.1 The Notification QueueThe JMX timer service is organized around a priority queue of scheduled notifications. Each entry in the queue is an ordered pair consisting of a date and a set of notifications. The queue is prioritized by date so that the earliest ”that is, next chronological ”date is at the head of the queue. Entries are added to the queue via one of the timer service's addNotification() methods . Entries are removed from the queue either by the timer service itself after the notification has been sent the specified number of times, or via one of its removeNotification() methods. Figure 7.1 illustrates the timer service's use of its notification queue. Figure 7.1. Notification Queues at Three Points in Time. (a) The notification queue prior to 8:30 A.M., April 14, 2002. (b) At 8:30 A.M., April 14, 2002, Notification 11 and Notification 12 are sent to all of this timer service's listeners. (c) The notification queue prior to 12:00 A.M., April 15, 2002.
The queue in Figure 7.1 contains three entries: 1. (08:30am 04/14/2002, {Notification 11 , Notification 12 }) 2. (12:00am 04/15/2002, {Notification 21 }) 3. (05:00pm 04/17/2002, {Notification n1 , Notification n2 , Notification n3 }) At 8:30 A.M. on April 14, 2002, Notification 11 and Notification 12 are sent to any listeners that have been added to this timer service instance. Notification 12 is a recurring notification with a period of five days, so it is added back to the queue in the entry (8:30am 04/19/2002, {Notification 12 }) . Now the timer service will wait until 12:00 A.M. on April 15, 2002, when it will send Notification 21 . 7.1.2 Timer NotificationsThe notifications that the timer service sends are a subclass of the basic JMX Notification class: public class TimerNotification extends Notification { // package private constructor; only the timer service may // create instances public Integer getNotificationID() { ... } } The value returned by the getNotificationID() method is the identifier associated with this notification when it was added to the notification queue. Management applications commonly use this notification ID to control which notifications their notification listeners receive. Recall that when we add a NotificationListener instance to an MBean via the MBeanServer's addNotificationListener() method, our listener receives all notifications sent by that MBean unless we provide a NotificationFilter instance to indicate which ones we're really interested in. Listing 7.2 is a simple NotificationFilter implementation that filters TimerNotification instances according to their notification IDs. Listing 7.2 A Notification ID “Based Instance of NotificationFilterimport java.util.HashSet; import javax.management.Notification; import javax.management.NotificationFilter; import javax.management.timer.TimerNotification; public class TimerFilter implements NotificationFilter { private final HashSet idset; /** * Create a new TimerFilter instance with no notification IDs enabled */ public TimerFilter() { this(new HashSet()); } /** * Create a new TimerFilter instance with the specified notification IDs * enabled * @param idset the set of enabled notification IDs */ public TimerFilter(Collection ids) { this.idset = new HashSet(ids); } /** * Determine whether or not the given notification is enabled * @param notification the notification in question * @return true if notification is an enabled TimerNotification instance */ public boolean isNotificationEnabled(Notification notification) { if !(notification instanceof TimerNotification) { return false; } return idset.contains(notification.getNotificationID()); } /** * Add a notification ID to the enabled set * @param id notification ID to enable */ public void enableNotificationID(Integer id) { idset.add(id); } /** * Remove a notification ID from the enabled set * @param id notification ID to disable */ public void disableNotificationID(Integer id) { id.remove(id); } } If a management application were interested in only notifications with IDs in the set {7, 11, 13}, it would create an instance of TimerFilter , enable those IDs, and then call addNotificationListener() with a listener and the TimerFilter instance it just configured: ... TimerFilter tf = new TimerFilter(); tf.enableNotificationID(new Integer(7)); tf.enableNotificationID(new Integer(11)); tf.enableNotificationID(new Integer(13)); mbs.addNotificationListener(objname, listener, tf, new Object()); If the application loses interest in the notification with ID 11, it simply disables that notification via TimerFilter : ... tf.disableNotificationID(new Integer(11)); Now invocations of handleNotification() will be limited to notifications 7 and 13. 7.1.3 The Timer ClassThe Timer class implements the management interface defined by the TimerMBean interface. That interface is composed of a set of attributes that provide information about the notification queue controlled by the timer and a set of operations that allow you to start and stop the timer itself, add notifications to and remove notifications from the notification queue, and get information about a specific notification on the notification queue. 7.1.3.1 TimerMBean AttributesThe TimerMBean interface defines the five attributes listed in Table 7.1. Table 7.1. TimerMBean Attributes
7.1.3.2 Timer Notification OperationsThe TimerMBean interface defines a suite of addNotification() and removeNotification() operations that management applications and MBeans use to schedule their notifications: 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 nbOccurences) All of the addNotification() operations construct a TimerNotification object, place it on the notification queue, and return an Integer ID for the notification rather than a reference to the notification itself. The only difference among the three versions is whether the new notification's period and number of occurrences are specified. Table 7.2 describes the notification's scheduling behavior in each possible case. The timer service doesn't support scheduling notifications in the past; that is, the value of the date parameter can't be earlier than the current date and time. If it is, all of the addNotification() methods throw an IllegalArgumentException . We can remove notifications en masse, or all notifications of a specified type, by calling removeAllNotifications() , or we can remove just the notification with a specific notification ID using removeNotification() : public void removeAllNotifications() public void removeNotifications(String type) public void removeNotification(Integer id) Table 7.2. Timer Notification Scheduling Behaviors
Finally, the TimerMBean interface supplies methods that access the information about scheduled notifications: public Vector getNotificationIDs(String type) public String getNotificationMessage(Integer id) public Object getNotificationUserData(Integer id) public Long getNbOccurences(Integer id) public Long getPeriod(Integer id) public Date getDate(Integer id) Use getNotificationIDs() to obtain a vector containing the notification IDs for all scheduled notifications of a specified type. Except for getPeriod() and getNbOccurences() (yes, occurrences is misspelled in the method name), each of the other five methods does just what its name implies: returns the value of the notification field indicated by its name . The getPeriod() method returns the specified notification's period unless the notification is a one-time occurrence, in which case getPeriod() returns null . The getNbOccurences() method returns the number of occurrences remaining on the specified notification's schedule; if the notification recurs indefinitely, a null value is returned. 7.1.3.3 Starting and StoppingThe final timer methods are responsible for controlling the service's execution: public void start() public void stop() When start() is called, the Timer instance begins processing its notification queue and sending notifications at the appointed dates and times. Calling stop() halts the timer's processing of the notification queue. Of course nothing is ever that simple. What happens if a notification's appointed date and time come and go before the timer is started? It depends on the value of the SendPastNotifications attribute. SendPastNotifications is a boolean -valued attribute, so there are only two possibilities. Table 7.3 explains what happens in each. Table 7.3. The Effect of SendPastNotifications on Timer Service Startup
Setting SendPastNotifications to true will ensure that no scheduled notifications are "lost" as the result of a delay in starting the timer service. The downside is that a "notification storm" may occur when the timer service is started. In a notification storm , tens or hundreds of notifications arrive simultaneously and are dispatched to handlers, which then take some action in response. At the very least there will be a CPU spike as the notifications are dispatched and handled; the CPU spike may trigger other notifications, which will compound the problem, possibly snowballing into a system crash. |