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:
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:
- We annotate the struct-like class's Constructor to tell JMX how
to recreate an instance of this class
- We provide an inner interface-and-class pair that declare an
MXBean which has a single writable attribute of this type
- 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:
... 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.
( May 29 2007, 04:11:10 PM CEST )
Permalink