Skip navigation

MXBeansmap between arbitrary Java types and a fixed set of types in javax.management.openmbean called the Open Types. This allows clients to interact with MXBeans, without needing to know the original Java types (which might require putting extra jars in their classpath and so on).

Up until now the mapping rules were fixed. Certain types can not be mapped by these rules, for example self-referential types or types such as Object or Number. In the Java 7 platform, we're planning to allow customization of the rules, as part of JSR 255 which is defining version 2.0 of the JMX API. This is a summary of the proposed changes.

What problem are we solving?

There are three sorts of use cases here:

  1. You have some types that you control and that are used in your MXBean interfaces. For example, java.lang.management.MemoryUsage which is referenced by java.lang.management.MemoryMXBean. An update to java.lang.management could update MemoryUsage, for example to add an annotation to it.
  2. You have some types that you do not control that are used in MXBean interfaces that you do control. For example, you are an end-user and you define an MXBean interface MyMemoryMXBean that references java.lang.management.MemoryUsage. You can't change MemoryUsage but you can change MyMemoryMXBean.
  3. You have some types that you do not control that are used in MXBean interfaces that you do not control either. For example, you want to define an MXBean-ified version of some legacy MBeans defined by somebody else.

The proposal is to add one class, one interface, and two annotations to address these cases. The existing class javax.management.StandardMBean acquires two new constructors and the existing class javax.management.JMX acquires two new overloads of existing methods.

MXBeanMapping and @MXBeanMappingClass

The most important change is the new class javax.management.openmbean.MXBeanMapping:

public abstract class MXBeanMapping{
    protected MXBeanMapping(Type javaType, OpenType<?> openType);
    public final Type getJavaType();
    public final OpenType<?>getOpenType();
    public final Class<?>getOpenClass();
    public abstract Object fromOpenValue(Object openValue) throws InvalidObjectException;
    public abstract Object toOpenValue(Object javaValue) throws OpenDataException;
    public void checkReconstructible() throws InvalidObjectException;
}

Suppose we want to define a mapping for the class MyLinkedList, which looks like this:

public class MyLinkedList {
    public MyLinkedList(String name, MyLinkedList next) {...}
    public String getName() {...}
    public MyLinkedList getNext() {...}
}

This is not a valid type for MXBeans, because it contains a self-referential property "next" defined by the getNext() method. (This example comes from Simone Bordet.) So we would like to specify a mapping for it explicitly. When an MXBean interface contains MyLinkedList, that will be mapped into a String[], which is a valid Open Type.

To define this mapping, we first subclass MXBeanMapping:

public class MyLinkedListMapping extends MXBeanMapping {
    public MyLinkedListMapping(Type type) throws OpenDataException {
        super(MyLinkedList.class, ArrayType.getArrayType(SimpleType.STRING));
        if (type != MyLinkedList.class)
            throw new OpenDataException("Mapping only valid for MyLinkedList");
    }

    @Override
    public Object fromOpenValue(Object openValue) throws InvalidObjectException {
        String[] array = (String[]) openValue;
        MyLinkedList list = null;
        for (int i = array.length - 1; i >= 0; i--)
            list = new MyLinkedList(array[i], list);
        return list;
    }

    @Override
    public Object toOpenValue(Object javaValue) throws OpenDataException {
        ArrayList<String> array = new ArrayList<String>();
        for (MyLinkedList list = (MyLinkedList) javaValue; list != null; list = list.getNext())
            array.add(list.getName());
        return array.toArray(new String[0]);
    }
}

The call to the superclass constructor specifies what the original Java type is (MyLinkedList.class) and what Open Type it is mapped to (ArrayType.getArrayType(SimpleType.STRING)). The fromOpenValue method says how we go from the Java type to the Open Type, and the toOpenValue method says how we go from the Open Type to the Java type.

With this mapping defined, we can annotate MyLinkedList with the new annotation @javax.management.openmbean.MXBeanMappingClass:

@MXBeanMappingClass(MyLinkedListMapping.class)
public class MyLinkedList {...}

Now we can use MyLinkedList in an MXBean interface and it will work. This satisfies use case 1 above.

MXBeanMappingFactory and @MXBeanMappingFactoryClass

If we are unable to annotate individual classes, then we can define a mapping factory that is consulted every time a type needs to be mapped. This is also useful if we would like to apply the same set of rules across a whole set of classes (for example, any class that implements List<E>is mapped in the same way as List<E>).

A mapping factory is a subclass of javax.management.openmbean.MXBeanMappingFactory:

public abstract class MXBeanMappingFactory{
    public static final MXBeanMappingFactoryDEFAULT;
    protected MXBeanMappingFactory();
    public abstract MXBeanMappingmappingForType(Type t, MXBeanMappingFactory f)
    throws OpenDataException;
}

For example, suppose we can't change MyLinkedList, so we can't add the @MXBeanMappingClass annotation to it. We can achieve the same effect by defining a mapping factory as follows:

public classMyLinkedListMappingFactory extends MXBeanMappingFactory {
    public MyLinkedListMappingFactory() {}

    @Override
    public MXBeanMapping mappingForType(Type t, MXBeanMappingFactory f)
    throws OpenDataException {
        if (t == MyLinkedList.class)
            return new MyLinkedListMapping(t);
        else
            return MXBeanMappingFactory.DEFAULT.mappingForType(t, f);
    }
}

Now we can add the new annotation javax.management.openmbean.MXBeanMappingFactoryClass to any MXBean interface that references MyLinkedList:

@MXBeanMappingFactoryClass(MyLinkedListMappingFactory.class)
public interface SomethingMXBean {
    public MyLinkedList getSomething();
}

This satisfies use case 2 above.

New StandardMBean constructors and JMX.newMXBeanProxy method

The existing class StandardMBean is used to make instances of Standard MBeans and MXBeans when you need to control some aspects of their behaviour, such as the descriptions in the MBeanInfo. We can extend the set of constructors as follows:

public class StandardMBean implements DynamicMBean {
    ...existing constructors...
    public <T> StandardMBean(T impl, Class<T> intf, MBeanOptions options);
    protected StandardMBean(Class<?> intf, MBeanOptions options);
}

The options parameter allows us to specify a number of potentially interesting things:

  • an MXBeanMappingFactory (this is the case we're looking at here)
  • whether this is an MXBean or not
  • perhaps the contents of the MBeanInfo, as an alternative to subclassing and overriding e.g. getDescription(MBeanAttributeInfo)
  • whether the methods of MBeanRegistration should be forwarded to the implementation object if it implements that interface

The question of what the MBeanOptionstype is is still open. It could be a Map<String, ?> where each option is represented by a string constant. This is the approach taken by the JMX Remote API, for example. Or, it could be a special-purpose class called MBeanOptions with methods to set each of the options. I plan to write more on this later; for now I'll assume it's the MBeanOptions option.

If you need to create an MXBean that implements the SomethingMXBean interface above and uses theMyLinkedListMappingFactory, but you can't add an annotation to SomethingMXBean, then you can do so using either of the two existing ways to use StandardMBean, subclassing or delegation, but supplying an option:

MBeanOptions options = MBeanOptions.DEFAULT.withMXBeanMappingFactory(
    new MyLinkedListMappingFactory);
// either use delegation:
SomethingMXBean impl = new SomethingImpl();
StandardMBean mbean = new StandardMBean(impl, SomethingMXBean.class, options);
// or use subclassing:
public class MySomething extends StandardMBean implements SomethingMXBean {
    public MySomething() {
        super(SomethingMXBean.class, options);
    }
}

If you have a custom mapping in your MBean server, then you need the same custom mapping in a client if the client is making a proxy. So we add another overloading of JMX.newMXBeanProxy:

public class JMX {
    ...
    public static <T> TnewMXBeanProxy(
        MBeanServerConnection mbsc,
        ObjectName objectName,
        Class<T> intf,
        MBeanOptionsoptions);
    ...
}

Using this, you can create a proxy like this:

SomethingMXBean proxy = JMX.newMXBeanProxy(
    mbsc, objectName, SomethingMXBean.class,options);

Here the options object can be the same as before.

This satisfies use case 3 above.

Interoperation when there are custom types

MXBeans map arbitrary Java types to Open Types. The addition of custom mappings doesn't change this - the result still has to be Open Types. So for generic clients like JConsole, nothing changes when custom types are added into the mix.

A client that is aware of the MXBean interfaces in use (like SomethingMXBean) can construct a proxy. To do that, it must have the interface available. If the interface has a @MXBeanMappingFactoryClassannotation, or if it contains a type that has a @MXBeanMappingClass annotation, then the classes referenced by those annotations must be present in the client too. It usually isn't any more difficult to arrange for the mapping classes to be present than to arrange for the original MXBean interface to be present.

If the mapping has been specified on the server using an explicit MXBeanMappingFactory, then the same or an equivalent factory must be used on the client. This is the case where there is the most risk of inconsistency. To help check that client and server are using the same MXBeanMappingFactory, the Descriptor of an MXBean using an MXBeanMappingFactory will contain a field naming the factory class, MyLinkedListMappingFactory in the examples above.

Some details

Here are some details I omitted above for clarity.

The methods toOpenValue and fromOpenValue have inconsistentthrows clauses. This reflects a similar inconsistency in the MXBean spec, itself due to historical reasons.

The Java type and Open Type in MXBeanMapping must be supplied to the constructor by the subclass, and cannot be changed thereafter. This could be a limitation but I cannot currently see any cases where you would not be able to supply values to the super-constructor call. MXBeanMapping is a class and not an interface for a number of reasons, notably that it allows us to have final methods (getOpenType, getOpenClass, getJavaType) and methods with default implementations (checkReconstructible). MXBeanMappingFactory is a class not an interface so that we can add methods to it in later versions of the API if necessary.

The method MXBeanMapping.checkReconstructible() is used to determine if it is possible to map back from a value of the OpenType to a value of the original Java type. The default implementation does nothing. A subclass can override this method to throw OpenDataException if this mapping is not possible. See the MXBean specification for a discussion of reconstructible types.

MXBeanMappingClass can be defined like this:

@Documented @Retention(RUNTIME) @Target(TYPE) @Inherited
public @interface MXBeanMappingClass {
    Class<? extends MXBeanMapping> value();
}

The use of Class<? extends MXBeanMapping> means that if you write @MXBeanMappingClass(Something.class) and Something is not a subclass of MXBeanMapping, then you will get a compile error.

The class mentioned in the annotation must have a public constructor with a single parameter of type java.lang.reflect.Type. Unfortunately we can't get the compiler to check that!

The @Inherited annotation implies that if you subclass MyLinkedList then the subclass will inherit the same MXBeanMapping, which might not be what you want. But not inheriting the mapping seems more likely to surprise users.

The signature of MXBeanMappingFactory.mappingForType is this:

public MXBeanMapping mappingForType(Type t, MXBeanMappingFactory f)
    throws OpenDataException

The MXBeanMappingFactory parameter ensures that the same mappings are applied recursively. So if the type to be mapped is MyLinkedList[] in the example above, the mapping will proceed as follows:

MyLinkedListMappingFactory.mappingForType(MyLinkedList[].class, MyLinkedListMappingFactory) →
    MXBeanMappingFactory.DEFAULT.mappingForType(MyLinkedList[].class, MyLinkedListMappingFactory) →
        MyLinkedListMappingFactory.mappingForType(MyLinkedList.class, MyLinkedListMappingFactory)
        ← MyLinkedListMapping
    ← ArrayMapping(MyLinkedListMapping)
← ArrayMapping(MyLinkedListMapping)

where ArrayMapping is the private class that the default mapping factory uses to map arrays.

The key point here is that, even though MyLinkedListMappingFactory forwards any type it doesn't handle to the default mapping factory, the default factory will rerun anycontained type through the user-specified mapping factory. So MyLinkedListMappingFactory doesn't have to recognize MyLinkedList[] or List<MyLinkedList> or whatever in addition to plain MyLinkedList. The general rules for SomeType[] orList<SomeType> in the default factory will apply, and then MyLinkedListMappingFactory will be applied to the contained SomeType, and do the right thing if that is MyLinkedList.

Open questions

This is still a draft proposal, and some questions remain.

As I mentioned above, the exact type of the MBeanOptions parameter to various methods is yet to be determined.

Should the @MXBeanMappingFactory option be inherited? For example, if you define

public interface SubSomethingMXBean extends SomethingMXBean {...}

should you inherit the @MXBeanMappingFactory annotation from SomethingMXBean? I think the answer must be yes, but what if SubSomethingMXBean inherits from more than one MXBean interface, and they have different @MXBeanMappingFactory annotations?

Should the @MXBeanMappingFactory option be applicable to packages? A reminder that you can annotate packages by creating a file called package-info.java in the package with contents like this:

@javax.management.openmbean.MXBeanMappingFactory(MyLinkedListMappingFactory.class)
package com.example.mbeans;

If @MXBeanMappingFactory can apply to packages and is also inherited from superinterfaces then we may have some complicated rules for precedence between the two.

Conclusion

In conclusion, we're specifying something quite simple: how to extend the standard MXBean mappings with custom mappings. But the details turn out not to be so simple!

Acknowledgements

This specification has been discussed in the JSR 255 Expert Group. Mandy Chungalso had some very helpful comments.

[Tags: jmx, mxbeans.]

One question I encounter frequently about the JMX Remote API is how to reduce the time taken to notice that a remote machine is dead when making a connection to it. The default timeout is typically a couple of minutes! Here's one way to do it.

Probably the cleanest technique for connection timeouts in general is to set a connection timeout on the socket. The idea is that instead of using...

 Socket s = new Socket(host, port); 

...you use...

 SocketAddress addr = new InetSocketAddress(host, port); Socket s = new Socket(); s.connect(addr, timeoutInMilliSeconds); 

The problem is that this is at a rather low level. If you're making connections with the JMX Remote API you usually don't see Socket objects at all. It's still possible to use this technique, but it requires a certain amount of fiddling, and the particular fiddling you need depends on which connector protocol you are using.

A lot of the time, a much simpler and more general technique is applicable. You simply create the connection in another thread, and you wait for that thread to complete. If it doesn't complete before your timeout, you just abandon it. It might still take two minutes to notice that the remote machine is dead, but in the meantime you can continue doing other things.

If you're making a lot of connections to a lot of machines, you might want to think twice about abandoning threads, because you might end up with a lot of them. But in the more typical case where you're just making one connection, this technique may well be for you.

Assuming you're using at least Java SE 5, you'll certainly want to use java.util.concurrent to manage the thread creation and communication. There are a few ways of doing it, but the easiest is probably aTimeUnit.SECONDS);

My first cut at the problem

In my first version of this entry, I proposed a solution with the following outline.

 JMXConnector connectWithTimeout(JMXServiceURL url, long timeout, TimeUnit unit) { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<JMXConnector> future = executor.submit(new Callable<JMXConnector>() { public JMXConnector call() { return JMXConnectorFactory.connect(url); } }); return future.get(timeout, unit); } 

Half an hour after posting, I suddenly realised that this version is incorrect. It reminds of the saying that for every complex problem there is a solution that is simple, obvious, and wrong.

This solution does the right thing when the connection succeeds within the time limit, and also in the case of the problem we are trying to solve, where it takes a very long time to fail. But if the connection succeeds after the time limit, the caller will already have returned, and we'll have made a connection that nobody knows about!

The second attempt

This is the outline of my second attempt, which I believe is correct. There are several refinements we'll need to apply before having a solution that actually works.

 // This is just an outline: the real code appears later JMXConnector connectWithTimeout(JMXServiceURL url, long timeout, TimeUnit unit) { final BlockingQueue<Object> mailbox = new ArrayBlockingQueue<Object>(1); final ExecutorService executor = Executors.newSingleThreadExecutor(); executor.submit(new Runnable() { public void run() { JMXConnector connector = JMXConnectorFactory.connect(url); if (!mailbox.offer(connector)) connector.close(); } }); Object result = mailbox.poll(timeout, unit); if (result == null) { if (!mailbox.offer("")) result = mailbox.take(); } return (JMXConnector) result; } 

To understand how and why this works, notice that exactly one object always gets posted to the mailbox. There are three cases:

  • If the connection attempt finishes before the timeout, then the connector object will be posted to the mailbox and returned to the caller.
  • If the timeout happens, then the main thread will try to stuff the mailbox with an arbitrary object (here the empty string, but any object would do), so the connection thread will realise it has connected too late and close the newly-made connection.
  • If the timeout happens at exactly the same time as the connection is made, then the main thread may find that the mailbox is already full, in which case it again picks up the connector object and returns it.

Making it work

The code above is just an outline, and leaves out some necessary details. We need to refine it in several ways to make it work.

The first refinement we'll need is exception handling. The result of the connection attempt could be an exception instead of a JMXConnector. This doesn't change the reasoning above, but it does complicate the code.

The main thread calls BlockingQueue.poll, which can throw InterruptedException, so we must handle that.

About half of the final version of connectWithTimeout involves footering about with exceptions. It's times like this that I'm inclined to join the checked-exception-haters.

The second refinement is to clean up the connect thread when we're finished with it. The outline code doesn't call shutdown() on the ExecutorService, so every time connectWithTimeout is called, a new single-thread executor is created, and therefore a new thread. If you're lucky, the garbage-collector will pick up your executors and their threads at some stage, but you don't want to depend on luck.

A more subtle point about threads is that the outline code will create non-daemon threads. Your application will not exit when the main thread exits if there are any non-daemon threads. So as written, if you have a thread stuck in a connection attempt and your application is otherwise finished, it will stay around until the connection attempt finally times out. That's pretty much exactly the sort of thing we're trying to avoid. So we'll need to arrange to create a daemon thread instead.

All right, so here's the real code.

 public static JMXConnector connectWithTimeout( final JMXServiceURL url, long timeout, TimeUnit unit) throws IOException { final BlockingQueue<Object> mailbox = new ArrayBlockingQueue<Object>(1); ExecutorService executor = Executors.newSingleThreadExecutor(daemonThreadFactory); executor.submit(new Runnable() { public void run() { try { JMXConnector connector = JMXConnectorFactory.connect(url); if (!mailbox.offer(connector)) connector.close(); } catch (Throwable t) { mailbox.offer(t); } } }); Object result; try { result = mailbox.poll(timeout, unit); if (result == null) { if (!mailbox.offer("")) result = mailbox.take(); } } catch (InterruptedException e) { throw initCause(new InterruptedIOException(e.getMessage()), e); } finally { executor.shutdown(); } if (result == null) throw new SocketTimeoutException("Connect timed out: " + url); if (result instanceof JMXConnector) return (JMXConnector) result; try { throw (Throwable) result; } catch (IOException e) { throw e; } catch (RuntimeException e) { throw e; } catch (Error e) { throw e; } catch (Throwable e) { // In principle this can't happen but we wrap it anyway throw new IOException(e.toString(), e); } } private static <T extends Throwable> T initCause(T wrapper, Throwable wrapped) { wrapper.initCause(wrapped); return wrapper; } private static class DaemonThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { Thread t = Executors.defaultThreadFactory().newThread(r); t.setDaemon(true); return t; } } private static final ThreadFactory daemonThreadFactory = new DaemonThreadFactory(); 

The initCause method is only used once but it's handy to have around for those troublesome exceptions that don't have a Throwable cause parameter.

I think it would be awfully nice if java.util.concurrent supplied DaemonThreadFactory rather than everyone having to invent it all the time.

Shouldn't this be simpler?

I admit I'm a bit uncomfortable with the code here. I'd be happier if I didn't need to reason about it in order to convince myself that it's correct. But I don't see any simpler way of using the java.util.concurrent API to achieve the same effect. Uses of cancel or interrupt tend to lead to race conditions, where the task can be cancelled after it has already delivered its result, and again we can get a JMXConnector leak; or we might close a JMXConnector that the main thread is about to return. I'd be interested in suggestions for simplification.

Conclusion of the foregoing

This is a useful technique in many cases, subject to the caution above. It's not limited to the JMX Remote API, either; you might use it when accessing a remote web service or EJB or whatever, without having to figure out how to get hold of the underlying Socket so you can set its timeout.

My thanks to Sébastien Martin for the discussion that led to this entry.

[Tags: jmx timeout concurrent.]

The Web Services Connector for JMX Agents being defined by JSR 262 is available in Early Access.

I described the background to this JSR in an earlier blog entry. In short, it serves two purposes:

  • Provide an alternative to the RMI connector for JMX-aware clients (such as JConsole). Because the JSR 262 connector uses SOAP over HTTP, it can reuse a web server configuration you have already established; for example it can reuse the authentication you have defined. It can also function better across firewalls.
  • Interoperate with non-JMX-aware clients. Because the JSR 262 connector is based on the WS-Management standard, it can interoperate with other software that also respects that standard. For example, it can interoperate with WinRM, Microsoft's WS-Management implementation in Windows Vista.

This Early Access Release is part of a formal Early Draft Review in the Java Community Process. The draft specification is available for study and comment. Send any comments to jsr-262-comments@jcp.org. The formal review period ends on the 23rd of May, but feel free to send comments at any time. Of course the sooner you send comments, the more likely you will be able to influence the final specification.

An early version of the Reference Implementation (RI) is also available for download. We are very interested in your experiences with it. The best address to send questions and comments on the RI is users@ws-jmx-connector.dev.java.net. You'll need to join this list before you can send messages to it.

Jean-Fran