Search code examples
javajmxmxbean

Abstract types in MXBean operation signatures


I'm trying to create an MXBean operation which would return an abstract type w/o properties (the actual type and its attributes are to be determined at run time). My data model can be simply put as follows:

public interface I extends CompositeDataView {
    // empty
}

public final class A implements I {
    private final String foo;

    @ConstructorProperties({"foo"})
    public A(final String foo) {/* ... */}

    public String getFoo() {/* ... */}

    @Override
    public CompositeData toCompositeData(CompositeType ct) {/* ... */}

    public static A from(final CompositeData cd) {/* ... */}
}

public final class B implements I {
    private final String bar;

    @ConstructorProperties({"bar"})
    public B(final String bar) {/* ... */}

    public String getBar() {/* ... */}

    @Override
    public CompositeData toCompositeData(CompositeType ct) {/* ... */}

    public static B from(final CompositeData cd) {/* ... */}
}

... and the MXBean operation signature is:

@MXBean
public interface Baz {
    I f();
}

The operation can return either an instance of A with a foo attribute, or an instance of B with a bar attribute. Of course I'm presented with a shiny NotCompliantMBeanException immediately I try to register the MBean instance:

Caused by: javax.management.openmbean.OpenDataException: Can't map I to an open data type
    at com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory.makeCompositeMapping(DefaultMXBeanMappingFactory.java:458)
    at com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory.makeMapping(DefaultMXBeanMappingFactory.java:292)
    at com.sun.jmx.mbeanserver.DefaultMXBeanMappingFactory.mappingForType(DefaultMXBeanMappingFactory.java:257)

It seems there's something I can do with regular MBeans and Serializable but can't with MXBeans and CompositeDataView. Or am I wrong?


Solution

  • It is possible to do more or less what you want, though not all that straightforward. The key point is that the MXBean spec requires a CompositeData to have at least one item. You can satisfy this requirement by having a property type in your base class, which I've called AnyCompositeData here. The type property can also serve to decide how to translate back from CompositeData to the specific types such as your Foo and Bar. In the code here I stuffed everything into the AnyCompositeData class, though more realistically it would be separate classes of course. I only spelled out the concrete class Foo but it should be obvious how to extend the pattern to support other classes.

    public abstract class AnyCompositeData implements CompositeDataView {
      private final String type;
    
      public AnyCompositeData(String type) {
        this.type = type;
      }
    
      public String getType() {
        return type;
      }
    
      public static AnyCompositeData from(CompositeData cd) {
        switch ((String) cd.get("type")) {
          case "Foo":
            return new Foo((String) cd.get("foo"));
          default:
            throw new IllegalArgumentException("Don't know how to reconstruct: " + cd.get("type"));
        }
      }
    
      public static class Foo extends AnyCompositeData {
        private final String foo;
    
        Foo(String foo) {
          super("Foo");
          this.foo = foo;
        }
    
        public String getFoo() {
          return foo;
        }
    
        @Override
        public CompositeData toCompositeData(CompositeType ct) {
          try {
            String[] items = {"type", "foo"};
            OpenType<?>[] itemTypes = {SimpleType.STRING, SimpleType.STRING};
            Object[] itemValues = {"Foo", foo};
            CompositeType compositeType = new CompositeType("Foo", "Foo", items, items, itemTypes);
            return new CompositeDataSupport(compositeType, items, itemValues);
          } catch (OpenDataException e) {
            throw new RuntimeException(e);
          }
        }
      }
    
      @MXBean
      public interface Baz {
        AnyCompositeData f();
      }
    
      static class BazImpl implements Baz {
        @Override
        public AnyCompositeData f() {
          return new Foo("whatever");
        }
      }
    
      public static void main(String[] args) throws Exception {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        ObjectName objectName = new ObjectName("test:baz=baz");
        mbs.registerMBean(new BazImpl(), objectName);
        Baz bazProxy = JMX.newMXBeanProxy(mbs, objectName, Baz.class);
        AnyCompositeData result = bazProxy.f();
        assert result instanceof Foo;
        assert ((Foo) result).getFoo().equals("whatever");
      }
    }
    

    If you had a lot of subclasses like Foo, then you might want to consider using reflection in some way rather than having the from(CompositeData) method know about all the subclasses.