Skip to main content

Removing getters from Model MBean operations

Posted by emcmanus on February 13, 2007 at 6:43 AM PST

One curiosity about Model MBeans is that attributes also appear
as operations. Is there any way to avoid that?

We encounter this question occasionally, most recently in the
href="http://forum.java.sun.com/thread.jspa?forumID=537&threadID=5135181">JMX
forum on SDN. As the contributor there notes, this is
tracked as href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6339571">RFE
6339571, but won't be implemented until Java SE 7. What
can you do in the mean time?

In order to define an attribute, say Foo, as being
the result of calling the getBar method, Model MBeans
require you to define a href="http://java.sun.com/javase/6/docs/api/javax/management/modelmbean/ModelMBeanAttributeInfo.html">ModelMBeanAttributeInfo
for Foo with the getMethod set to
getBar. That seems reasonable so far. But they
also require you to define a href="http://java.sun.com/javase/6/docs/api/javax/management/modelmbean/ModelMBeanOperationInfo.html">ModelMBeanOperationInfo
for getBar. So if you connect with JConsole, say,
then not only will you see the Foo attribute, you
will also see the getBar operation. There are
reasons for this, which are alluded to in RFE 6339571, but it's
still annoying.

The getBar operation needs to be in the MBeanInfo
so that the Model MBean can find it when you access the
Foo attribute. But it doesn't need to be in the
MBeanInfo that JConsole sees. Is there some way we could
arrange for the MBeanInfo to be different in these two
cases?

The answer is yes, via a hack. We can tweak the serialization
of this MBeanInfo so that operations like getBar
are removed when the MBeanInfo is being sent to a remote client
such as JConsole. This will have no effect on the local
MBeanInfo that the Model MBean itself sees, so the
Foo attribute will continue to work.

We'll define a subclass of href="http://java.sun.com/javase/6/docs/api/javax/management/modelmbean/ModelMBeanInfoSupport.html">ModelMBeanInfoSupport
called NoGetterMBeanInfo, and add a href="http://java.sun.com/javase/6/docs/platform/serialization/spec/output.html#5324">writeReplace
method to this class. The writeReplace method can return a
different object to be serialized in the place of the original
one. Instead of serializing a NoGetterMBeanInfo, which would
require a client such as JConsole to know that class, we can
serialize a ModelMBeanInfoSupport. Since that's a standard class,
every client must know it. And we can arrange for this new
ModelMBeanInfoSupport not to contain any getter methods like
getBar.

We identify getter methods by their href="http://java.sun.com/javase/6/docs/api/javax/management/Descriptor.html">Descriptor:
if the Descriptor contains a role field with the
value "getter", then we assume it's a getter.
Likewise if the value is "setter", we'll assume
it's a setter and also delete it. This is not a foolproof test:
if the value is "operation" then it can still be
used as a getter, but we'll assume that you can either change
your code so that the field has the right value, or change
NoGetterMBeanInfo to use a different test.

Here then is the code for NoGetterMBeanInfo:

import java.util.ArrayList;
import java.util.List;
import javax.management.Descriptor;
import javax.management.MBeanException;
import javax.management.MBeanOperationInfo;
import javax.management.modelmbean.ModelMBeanAttributeInfo;
import javax.management.modelmbean.ModelMBeanConstructorInfo;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.ModelMBeanInfoSupport;
import javax.management.modelmbean.ModelMBeanNotificationInfo;
import javax.management.modelmbean.ModelMBeanOperationInfo;

public class NoGetterMBeanInfo extends ModelMBeanInfoSupport {
    public NoGetterMBeanInfo(ModelMBeanInfo mmbi) {
        super(mmbi);
    }

    @Override
    public NoGetterMBeanInfo clone() {
        return new NoGetterMBeanInfo(this);
    }

    private Object writeReplace() {
        List ops = new ArrayList();
        for (MBeanOperationInfo mboi : this.getOperations()) {
            ModelMBeanOperationInfo mmboi = (ModelMBeanOperationInfo) mboi;
            Descriptor d = mmboi.getDescriptor();
            String role = (String) d.getFieldValue("role");
            if (!"getter".equalsIgnoreCase(role) &&
                    !"setter".equalsIgnoreCase(role))
                ops.add(mmboi);
        }
        ModelMBeanOperationInfo[] mbois = new ModelMBeanOperationInfo[ops.size()];
        ops.toArray(mbois);
        Descriptor mbeanDescriptor;
        try {
            mbeanDescriptor = this.getMBeanDescriptor();
        } catch (MBeanException e) {
            throw new RuntimeException(e);
        }
        return new ModelMBeanInfoSupport(
                this.getClassName(),
                this.getDescription(),
                (ModelMBeanAttributeInfo[]) this.getAttributes(),
                (ModelMBeanConstructorInfo[]) this.getConstructors(),
                mbois,
                (ModelMBeanNotificationInfo[]) this.getNotifications(),
                mbeanDescriptor);
    }
}

To use it, change code where you do something like this...

ModelMBean mbean = new RequiredModelMBean(myModelMBeanInfo);

...into this...

ModelMBean mbean = new RequiredModelMBean(new NoGetterMBeanInfo(myModelMBeanInfo));

Of course this hack will only work if you are using a connector
that is based on Java object serialization, such as the RMI
connector that is part of the Java platform. If you are using a
SOAP-based connector, say, then you will need to look at how to
achieve the same result in that context. (One possibility is to
insert an href="http://java.sun.com/javase/6/docs/api/javax/management/remote/MBeanServerForwarder.html">MBeanServerForwarder
that intercepts a remote getMBeanInfo operation and
rewrites the MBeanInfo as above.)

The forum question asked about how to do this in the context of
Spring; since Spring uses Model MBeans the question arises
frequently. I was able to plug in the NoGetterMBeanInfo by
using a custom href="http://static.springframework.org/spring/docs/2.0.x/api/org/springframework/jmx/export/MBeanExporter.html">MBeanExporter
like this:

import javax.management.JMException;
import javax.management.ObjectName;
import javax.management.modelmbean.ModelMBean;
import javax.management.modelmbean.ModelMBeanInfo;
import org.springframework.jmx.export.MBeanExporter;

public class NoGetterExporter extends MBeanExporter {
    public NoGetterExporter() {
    }

    @Override
    protected void doRegister(Object mbean, ObjectName objectName) throws JMException {
        if (mbean instanceof ModelMBean) {
            ModelMBean mmb = (ModelMBean) mbean;
            ModelMBeanInfo mmbi = (ModelMBeanInfo) mmb.getMBeanInfo();
            mmb.setModelMBeanInfo(new NoGetterMBeanInfo(mmbi));
        }
        super.doRegister(mbean, objectName);
    }
}

You probably already have an MBeanExporter in your Spring
configuration file, with lines looking something like this:

<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    ...
</bean>

You should just be able to change the MBeanExporter class name
to the fully-qualified name of NoGetterExporter.

NoGetterMBeanInfo is a hack, but I hope it's a useful one!

Related Topics >>