Skip to main content

Custom types for MXBeans

Posted by emcmanus on May 30, 2007 at 7:00 AM PDT

href="http://java.sun.com/developer/technicalArticles/J2SE/mxbeans/">MXBeans
map between arbitrary Java types and a fixed set of types in href="http://java.sun.com/javase/6/docs/api/javax/management/openmbean/package-summary.html">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, href="http://java.sun.com/javase/6/docs/api/java/lang/management/MemoryUsage.html">java.lang.management.MemoryUsage
    which is referenced by href="http://java.sun.com/javase/6/docs/api/java/lang/management/MemoryMXBean.html">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 href="http://bordet.blogspot.com/">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 ( href="http://java.sun.com/javase/6/docs/api/javax/management/openmbean/ArrayType.html#getArrayType(javax.management.openmbean.OpenType)">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 href="http://java.sun.com/javase/6/docs/api/java/util/List.html">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 MXBeanMappingFactory DEFAULT;

    protected MXBeanMappingFactory();

    public abstract MXBeanMapping mappingForType(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 class MyLinkedListMappingFactory 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 href="http://java.sun.com/javase/6/docs/api/javax/management/StandardMBean.html">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. href="http://java.sun.com/javase/6/docs/api/javax/management/StandardMBean.html#getDescription(javax.management.MBeanAttributeInfo)">getDescription(MBeanAttributeInfo)
  • whether the methods of href="http://java.sun.com/javase/6/docs/api/javax/management/MBeanRegistration.html">MBeanRegistration
    should be forwarded to the implementation object if it
    implements that interface

The question of what the MBeanOptions
type 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 href="http://java.sun.com/javase/6/docs/api/javax/management/remote/JMXConnectorFactory.html#connect(javax.management.remote.JMXServiceURL,%20java.util.Map)">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 href="#SomethingMXBean">SomethingMXBean interface above and
uses the href="#MyLinkedListMF">MyLinkedListMappingFactory, 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> T newMXBeanProxy(

        MBeanServerConnection mbsc,

        ObjectName objectName,

        Class<T> intf,

        MBeanOptions options);

    ...

}

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 href="#options">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 href="http://java.sun.com/developer/technicalArticles/J2SE/jconsole.html">JConsole,
nothing changes when custom types are added into the mix.

A client that is aware of the MXBean interfaces in use (like href="#SomethingMXBean">SomethingMXBean) can construct a
proxy. To do that, it must have the interface available. If
the interface has a href="#MXBeanMFC">@MXBeanMappingFactoryClass annotation, or if it
contains a type that has a href="#MXBeanMappingClass">@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 href="http://java.sun.com/javase/6/docs/api/javax/management/Descriptor.html">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 href="#fromOpenValue">fromOpenValue have inconsistent
throws clauses. This reflects a similar inconsistency
in the MXBean spec, itself due to historical reasons.

The Java type and Open Type in href="#MXBeanMapping">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 href="http://java.sun.com/javase/6/docs/api/javax/management/MXBean.html#MXBean-spec">MXBean
specification for a discussion of reconstructible
types.

MXBeanMappingClass can be defined like this:

@ href="http://java.sun.com/javase/6/docs/api/java/lang/annotation/Documented.html">Documented
@ href="http://java.sun.com/javase/6/docs/api/java/lang/annotation/Retention.html">Retention( href="http://java.sun.com/javase/6/docs/api/java/lang/annotation/RetentionPolicy.html#RUNTIME">RUNTIME)
@ href="http://java.sun.com/javase/6/docs/api/java/lang/annotation/Target.html">Target( href="http://java.sun.com/javase/6/docs/api/java/lang/annotation/ElementType.html#TYPE">TYPE)
@ href="http://java.sun.com/javase/6/docs/api/java/lang/annotation/Inherited.html">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 href="http://java.sun.com/javase/6/docs/api/java/lang/reflect/Type.html">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
any contained 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[] or List<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 href="http://jcp.org/en/jsr/detail?id=255">JSR 255 Expert
Group. Mandy
Chung
also had some very helpful comments.

[Tags: , href="http://java.sun.com/developer/technicalArticles/J2SE/mxbeans"
rel="tag">mxbeans.]

Related Topics >>