The MXBean framework gives you a way to define MBeans with custom types, and map those types to standard ("open") types so generic clients can deal with them. Sometimes you want to do this mapping outside an MBean. For example, we recommend that the custom payload ("userData") of a Notification should use open types, but open values can be a bit painful to construct. It would be nice to use the MXBean framework to map from a custom payload type to an open type. Is there a way to do that?

Let's take an example. Suppose you are tracking coordinates read from some device, and you have a notification that indicates that an (x,y) value has been read that was outside the expected bounds. You'd like the notification to include the value that was read.

In the JMX 2.0 API, you can do this easily, using interfaces I described before:

    public class Coordinate {
        @ConstructorProperties({"x", "y"})
        public Coordinate(int x, int y) {...}
        public int getX() {...}
        public int getY() {...}
    }

    MXBeanMapping coordinateMapping =
        MXBeanMappingFactory.DEFAULT.mappingForType(Coordinate.class, MXBeanMappingFactory.DEFAULT);

    Coordinate outOfBounds = ...;
    Notification notif = new Notification(Coordinate.class.getName(), source, seqNo++);
    notif.setUserData(coordinateMapping.toOpenValue(outOfBounds));
 

Though you don't have to, you can also make the corresponding OpenType available to clients, by putting it in the Descriptor of the corresponding MBeanNotificationInfo. The Descriptor field openType is defined with this meaning, so the code would look like this:

    OpenType<?> coordinateOpenType = coordinateMapping.getOpenType();
    Descriptor notifDescr = new ImmutableDescriptor(
            new String[] {
                "openType",
            }, new Object[] {
                coordinateOpenType
            });
    MBeanNotificationInfo mbni = new MBeanNotificationInfo(
        new String[] {"com.example.bounds.exceeded"},
        Notification.class.getName(),
        "Read a coordinate value outside the expected bounds",
        notifDescr);
 

That's all very well, but the JMX 2.0 API will only be available in the JDK 7 platform. What if you want to do this today?

The good news is that it is possible, although it requires writing some throwaway classes (or generating them at runtime, as I discussed previously). I'll show some general-purpose code later, but for now here's a simple way you could do it. Create an MXBean with an attribute X of type Coordinate, register it in an MBeanServer (perhaps one created specially for the purpose), then arrange for the original value of X to be the Coordinate you want to convert. CallinggetAttribute(objectName, "X") should then give you the converted value, and you can get the OpenType by looking atgetMBeanInfo(objectName).getAttributes()[0].

But actually, that's overkill. You don't need to register an MXBean at all. You can simply use the StandardMBean class to create a private MXBean, and do the same trick. For example, you could write this:

    public interface CoordinateWrapperMXBean {
        public Coordinate getX();
    }
    public class CoordinateWrapper implements CoordinateWrapperMXBean {
        public Coordinate x;
        public Coordinate getX() {
            return x;
        }
    }
    CoordinateWrapper coordinateWrapper = new CoordinateWrapper();
    StandardMBean coordinateMBean = new StandardMBean(
        coordinateWrapper, CoordinateWrapperMXBean.class, true);

    Coordinate outOfBounds = ...;
    coordinateWrapper.x = outOfBounds;
    Object userData = coordinateMBean.getAttribute("X");
 

That's kind of unwieldy, and it's not thread-safe as written, but you can see the general idea.

A general-purpose MXBean conversion class

To reduce the unwieldiness, we can generalize this for any type, in such a way that you only need to write this...

    public interface CoordinateCaller extends Callable<Coordinate> {
        public Coordinate call();
    }
 

...and you can feed it to a simple conversion framework that I'll show below, like this:

    MXBeanConverter coordinateConverter =
        MXBeanConverter.getInstance(CoordinateCaller.class);
    Coordinate outOfBounds = ...;
    Object userData = coordinateConverter.toOpenValue(outOfBounds);
 

You can also get the OpenType like this:

    OpenType<?> coordinateOpenType = coordinateConverter.getOpenType();
 

And you can convert in the opposite direction (for example, when you receive the Notification and you want to reconstruct the original Coordinate object) like this:

    Coordinate outOfBounds =
        coordinateConverter.fromOpenValue(notification.getUserData());
 

If you only have a handful of types that you need to convert, having to create a FooCaller interface for each one shouldn't be too big a burden. If you have a huge number of types, then it would be worth looking at generating each FooCaller interface at runtime, for example using ASM. (Actually, I'd recommend generating an interface with getX and setX methods instead.)

By the way, if you look closely at the CoordinateCaller interface...

    public interface CoordinateCaller extends Callable<Coordinate> {
        public Coordinate call();
    }
 

...you might reasonably wonder why you need to define thecall() method when aCallable<Coordinate> already has one. Ideally you would not have to, but this is a shortcoming of the current MXBean framework.

The approach used here is a variant on thegetAttribute("X") approach I explained above. Since we're using a call() method and not agetX() method, we need to use invoke and not getAttribute on the StandardMBean.

To do the reverse conversion, we make an MXBean proxy for the CoordinateCaller, and arrange for its call to MBeanServerConnection.invoke to return the open-type value, so that the proxy will convert it back into a Coordinate that it returns from its call() method. (Don't worry if you didn't understand that; you don't need to in order to use the converter.)

OK, so here's the code:

package mxbeanconverter;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.Callable;
import javax.management.JMX;
import javax.management.MBeanServer;
import javax.management.MBeanServerDelegate;
import javax.management.StandardMBean;
import javax.management.openmbean.OpenType;

public class MXBeanConverter<T> {
    private final StandardMBean mbean;
    private final Callable<T> proxy;
    private final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();

    public OpenType getOpenType() {
        return (OpenType) mbean.getMBeanInfo().getOperations()[0]
                .getDescriptor().getFieldValue("openType");
    }

    public Object toOpenValue(T javaValue) {
        threadLocal.set(javaValue);
        try {
            return mbean.invoke("call", null, null);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
            // Should not happen
        }
    }

    public T fromOpenValue(Object openValue) {
        threadLocal.set(openValue);
        try {
            return proxy.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
            // Should not happen
        }
    }

    private MXBeanConverter(Class<? extends Callable<T>> intf) {
        mbean = makeMBean(intf);
        proxy = makeProxy(intf);
    }

    public static <T> MXBeanConverter<T> getInstance(Class<? extends Callable<T>> intf) {
        return new MXBeanConverter(intf);
    }

    private <I extends Callable<T>> StandardMBean makeMBean(Class<I> intf) {
        I impl = intf.cast(Proxy.newProxyInstance(
                intf.getClassLoader(), new Class<?>[] {intf}, threadLocalIH));
        return new StandardMBean(impl, intf, true);
    }

    private <I extends Callable<T>> I makeProxy(Class<I> intf) {
        MBeanServer threadLocalMBS = (MBeanServer) Proxy.newProxyInstance(
                MBeanServer.class.getClassLoader(),
                new Class<?>[] {MBeanServer.class},
                threadLocalIH);
        return intf.cast(JMX.newMXBeanProxy(
                threadLocalMBS, MBeanServerDelegate.DELEGATE_NAME, intf));
    }

    // An InvocationHandler that returns threadLocal.get() from every method.
    // This means that we can make an implementation of FooCallable where
    // the call() method returns threadLocal.get(), and it also means that
    // we can make an implementation of MBeanServer where
    // invoke(...any parameters...) returns threadLocal.get().
    private class GetThreadLocalIH implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) {
            return threadLocal.get();
        }
    }
    private final InvocationHandler threadLocalIH = new GetThreadLocalIH();
}

[Tags: jmx mxbean ]