JMX MXBean Notifications, User Data, and Open Types

The MXBean runtime support classes in Java 6 provide all the help you need in order to be able to declare complex types for attributes and operations, but don't provide any help for converting complex types for other uses, such as for the payload of a Notification.

This blog article shows how to add helper methods to convert an existing struct-like class into Open Data.

We'll start with the same example as my previous blog article about AttributeChangeNotifications, (to get the full source code, please download it from that blog article).

We'll modify the example so that it sends a dedicated Notification type when the queue is cleared.

First of all, let's start off with a simple struct-like class used as part of a Notification payload:






package com.example;

import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class QueueNotificationInfo implements Serializable {
   
    public final static String QUEUE_CLEARED = "com.example.queue-cleared";
   
    List<String> entries;
   
    /\*\*
     \* Creates a new instance of QueueNotificationInfo
     \*/
    public QueueNotificationInfo(List<String> entries) {
        this.entries = entries;
    }
   
    public List<String> getEntries() {
        return Collections.unmodifiableList(entries);
    }
   
}

A simple Struct-like class used in a JMX Notification payload
Note that this class has been declared Serializable so that it can be serialized as part of the Notification - this is already a sign that the class isn't appropriate for MXBeans!

Now lets see the update code that sends a notification with this payload:





public class QueueSampler extends StandardEmitterMBean implements QueueSamplerMXBean {

   ...

  
    public void clearQueue() {
        synchronized (queue) {
           
            Notification notif = new Notification(QueueNotificationInfo.QUEUE_CLEARED,
                    this, seqNo++);
            notif.setUserData(
new QueueNotificationInfo(new ArrayList(queue)));
           
            queue.clear();
            sendNotification(notif);
        }
    }
}
The 'clearQueue' method has been updated to send a QUEUE_CLEARED notification
Well, that was easy.

If we connect jconsole to this example program, and subscribe to this MBean for notifications, we don't see any Notification! Why's this? Simply because the usual jconsole CLI doesn't have this new Notification class on its classpath.

If we use the netbeans JMX plug-in and launch the application with jconsole attached, lo-and-behold, we get a Notification when we click on the 'clearQueue' operation:

image showing jconsole with notification

Well, there's the user data, but it's not very legible, and especially, it is not an OpenData type, so can create classloading issues for any generic JMX code (such as the jconsole CLI, or JSR 262). How do we go about converting it to Open Data, and how do we go about converting it back?

The rules for mapping from a complex type to Open Data are specified in the MXBean documentation, but doing this serialization and deserialization by hand is cumbersome and error-prone.

The JMX runtime already has support for performing such mappings dynamically at run-time, when mapping MXBeans, so instead, we can leverage that.

By adding some serialization and deserialization code into the use of QueueNotificationInfo, we leverage JMX's runtime.

We do 3 important things here:
  1. We annotate the struct-like class's Constructor to tell JMX how to recreate an instance of this class
  2. We provide an inner interface-and-class pair that declare an MXBean which has a single writable attribute of this type
  3. We provide static methods that leverage the MXBean for Open-Data serialization and deserialization
The additional code below is boiler-plate stuff that can be reused with very little modification, other than changing the declared types:




package com.example;

import java.beans.ConstructorProperties;
import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import javax.management.Attribute;
import javax.management.AttributeNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.ReflectionException;
import javax.management.StandardMBean;

public class QueueNotificationInfo implements Serializable {
   
    public final static String QUEUE_CLEARED = "com.example.queue-cleared";
   
    List<String> entries;
   
    /\*\*
     \* Creates a new instance of QueueNotificationInfo
     \*/
    @ConstructorProperties({ "entries" })
    public QueueNotificationInfo(List<String> entries) {
        this.entries = entries;
    }
   
    public List<String> getEntries() {
        return Collections.unmodifiableList(entries);
    }

    /\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/

    // internal mxbean implementation used to convert QueueNotificationInfo
    // to and from mxbean OpenData format
   
    public static interface NotificationInfoSerializerInterface {
        public QueueNotificationInfo getNotificationInfo();
        public void setNotificationInfo(QueueNotificationInfo info);
    }   
   
    public static class NotificationInfoSerializer
            extends StandardMBean implements NotificationInfoSerializerInterface {

        private QueueNotificationInfo info;
        public NotificationInfoSerializer() {
            super(NotificationInfoSerializerInterface.class, true);
        }
        public QueueNotificationInfo getNotificationInfo() {
            return info;
        }
        public void setNotificationInfo(QueueNotificationInfo info) {
            this.info = info;
        }
    }
   
    private final static String ATTRIBUTE_NAME="NotificationInfo";
   
    public static QueueNotificationInfo deserialize(Object userData) {
        NotificationInfoSerializer me = new NotificationInfoSerializer();
        Attribute attr = new Attribute(ATTRIBUTE_NAME, userData);
        try {
            me.setAttribute(attr);
            return me.getNotificationInfo();
        } catch (AttributeNotFoundException ex) {
            throw new RuntimeException("Unexpected exception", ex);
        }  catch (InvalidAttributeValueException ex) {
            throw new RuntimeException("Unexpected exception", ex);
        }  catch (MBeanException ex) {
            throw new RuntimeException("Unexpected exception", ex);
        }  catch (ReflectionException ex) {
            throw new RuntimeException("Unexpected exception", ex);
        }
    }
    public static Object serialize(QueueNotificationInfo info) {
        try {
            NotificationInfoSerializer me =
                    new NotificationInfoSerializer();
            me.setNotificationInfo(info);
            return me.getAttribute(ATTRIBUTE_NAME);
        } catch (AttributeNotFoundException ex) {
            throw new RuntimeException("Unexpected exception", ex);
        } catch (MBeanException ex) {
            throw new RuntimeException("Unexpected exception", ex);
        } catch (ReflectionException ex) {
            throw new RuntimeException("Unexpected exception", ex);
        }
    }
       
}

The struct-like notification payload instrumented to be convertable into Open Data.

Then it becomes a simple matter of updating the construction of the Notification to use the new Serializer:





public class QueueSampler extends StandardEmitterMBean implements QueueSamplerMXBean {

   ...

  
    public void clearQueue() {
        synchronized (queue) {
           
            Notification notif = new Notification(QueueNotificationInfo.QUEUE_CLEARED,
                    this, seqNo++);
            notif.setUserData(QueueNotificationInfo.serialize(new QueueNotificationInfo(new ArrayList(queue))));
           
            queue.clear();
            sendNotification(notif);
        }
    }

}
The 'clearQueue' method has been updated to use the serializer

... and if you switch back to jconsole (you can use the CLI version too, now that we have OpenData information in the Notification), you should be able to get to this:

updated jconsole image showing opendata

... Thus seeing the OpenData Notification payload in all its glory.

As a footnote, I should point out that helper methods to convert to and from OpenData are planned as part of JSR 255, which I trust will make all of the above much simpler.

Comments:

Post a Comment:
Comments are closed for this entry.
About

nickstephen

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today