GlassFish MBean Annotation Library (gmbal)

Functional Specification

by Ken Cavanaugh
Version 1.0 (for GlassFish v3 FCS)
12/11/09

1. Introduction

The GlassFish MBean Annotation Library (gmbal, pronounced as in "gumball") is a runtime annotation processor that creates Open MBeans. This is useful for creating a management API for existing code with minimal effort.  It is intended to be applied to existing modules (which may be OSGi bundles in GlassFish v3, or any other packaging model including standard jar files), but could be used anywhere that it is desired to combine the definition of the management API with the module API and implementation.

Note that gmbal is not limited to use in GlassFish. Gmbal is completely independent of GlassFish, and may be used in a context where GlassFIsh is not present. However, when a gmbal-enabled module in used in GlassFish V3, that module will automatically be manageable using GlassFish V3 admin tools.

There are really two parts to creating a management API for a module: instrumenting the module implementation, and providing a client interface that may be accessed remotely. This is closely related to the AMX work in GFv3.  AMX defines the interface for AMX MBeans, and provides tools and libraries for clients accessing AMX MBeans.  Gmbal is a library that provides a mechanism for implementing AMXv3-compliant MBeans.

1.1. Alignment with related projects

JMX is also defining similar annotations in JSR 255 for JDK 7.  The JSR 255 spec is available at the download page. Gmbal is mostly aligned with the JSR 255 annotations. 

The similarities include:
Differences:
Note that there are two complimentary ways to define MBeans: MXBeans and annotations.  MXBeans are somewhat more convenient to use in cases where EVERY method in a class or interface is part of the management interface, whereas annotations are more convenient for adding a management interface to existing code. But both mechanisms share most of the same rules for MXBean mapping from Java types to Open types as discussed in "OpenType Mapping".

AMX in GlassFish v3 is being generalized to define exactly what an MBean must do to be manageable in the GlassFish v3 admin tools (see Lloyd's GlassFish V3 AMX SPI specification). Gmbal will follow all mandatory and most of the optional requirements of this specification so that any module that uses gmbal to define MBeans will automatically participate in the GFv3 admin tools when run in a GFv3 container.  However, this will not prevent the use of gmbal in these same modules as a standalone MBean definition library.

This last point needs a little more explanation.  The main interface that matters for running in or out of a GFv3 container is ManagedObjectManagerFactory.createXXX (XXX is either Standalone or Federated). The createStandalone method is needed for running outside of GFv3, and takes the domain name as an argument. createFedederated must be called inside of GFv3, and this requires the AMXv3-compliant ObjectName of the ManagedObjectManager root MBean's parent.  Calling the appropriate method is probably best handled in most cases by having an OSGi bundle for a module that is responsible for integrating the module into GFv3. 

For example, the orb-iiop bundle integrates the standalone ORB bundles into GFv3. orb-iiop handles registration of all of the GFv3-specific interceptors and other initialization when the GFv3 ORB instance is created. orb-iiop will also need to set the ORB property com.sun.corba.ee.ORBGmbalRootParentName to the string representation of the ObjectName of the ORB's gmbal root parent.  The ORB will then use this property to call either createStandalone (if it is not set) or createFederated (with the value of the property as the ObjectName passed into the call).

1.2. Similar projects

Besides JSR 255 (discussed in the previous section), there are at least two other projects that also define MBeans using annotations.

The Spring project also defines a set of annotations to use for creating MBeans.  Eamonn McManus has pointed to this as one of the starting points for JSR 255 in a blog post from August 2007.  JSR 255 and Spring share the @ManagedAttribute and @ManagedOperation annotations, but the Spring annotations include a description fields, whereas JSR 255 has moved the description into a separate annotation (as I have in gmbal as well).

The WebObject project from INRIA in France has created the SpoonJMX project, which also allows the use of annotations to define MBeans. SpoonJMX uses @ManagedResource to define an MBean class.  It uses @ManagedAttribute and @ManagedOperation as in the other systems.  SpoonJMX defines a @ObjectKeyName annotation, which I have adopted for use in gmbal as well under the name of @NameValue.  SpoonJMX also defines more attributes on the annotations than either gmbal or JSR 255.

2. The Management Data Model

Gmbal supports a simple hierarchical model of management data as defined in JSR 77 and AMX.
Gmbal Class Diagram
The components of the data model are as follows:
Note that all values consumed or produced by Attributes and Operations are instances of OpenTypes.

This diagram is a simplification of the actual implementation of gmbal.  In particular,  the metadata, attributes, and operations are maintained in a MBeanSkeleton class, which is shared by all MBeanImpls for instances of the same class.  The TypeConverter that handles conversion between Java types and the corresponding OpenType is also not included here.

3. Interfaces

Javadocs are available as part of the gmbal project. 

There are basically 3 elements to the gmbal API:
  1. class ManagedObjectManagerFactory, which provides factory methods for creating instance of the ManagedObjectManager interface.
  2. interface ManagedObjectManager, which provides register/deregister methods (and a number of other capabilities).
  3. A number of annotations.
Here is a summary of the annotations:
Annotation Class
Annotation Fields
Applicable Element Type
Relation to JSR 255
Purpose
@ManagedObject
none
Class or Interface
255 uses @MXBean
Defines a class whose instances are processed into MBeans.
@ManagedData
String name (defaults to class name)
Class or Interface
MXBeans assume all methods are in CompositeData
Defines a class whose instances are process into Open data for Open Mbeans.
@ManagedAttribute
String id (defaults to value from Method name)
Method or field
Same
Defines a method or field that represents an attribute either in ManagedData or ManagedObject
@ManagedOperation
String id (defaults to value from Method name)
Impact impact (defaults to UNKNOW; Impact enum corresponds to MBeanOperationInfo impact values)
Method
Same
Defines a method that represents an operation in ManagedObject
@Description
String value

String key (the key to use in a generated resource bundle; default "" means to derive key from name of annotated element)
Class, Interface,  Method, or Field
Same (but JSR 255 includes I18N)
Defines the descriptive text associated with a Class, Interface, or Method. This may require I18N, as discussed here.
@IncludeSubclass
Class[] value
Class or Interface for ManagedData
N/A
Lists subclasses of the annotated class that should also be scanned for annotations which are included only on instances of the appropriate type
@NameValue
none
Method
N/A
Defines a method (must be a getter) whose result is the value of the name attribute in the ObjectName of the MBean for an instance of the class
@ParameterNames
String[] value (defaults to "") (if present, must have same length as number of arguments in the annotated method)
Method
N/A (JSR 255 will take this information from reflection, which will make arg names available in JDK 7)
Defines the method names to be used on an MBean operation. If this annotation is not present or has the default value, the names will be argNum, where Num is the position of the argument starting from 0
@InheritedAttribute
  • String description
  • String id (default "" which means take from method name)
  • String methodName (default "" which means use id)
one of methodName or id must NOT be ""

Class or Interface
N/A
Defines a method inherited from a class that cannot be annotated as an attribute. See "addAnnotation" for a discussion of the use of this annotation.
@InheritedAttributes
InheritedAttribute[] value
Class or Interface
N/A
Allows including multiple inherited attributes
@AMXMetadata
  • boolean isSingleton() (true  if only one MBean of this type is allowed as a child of  another MBean)
  • String group() (value is the group for this mbean, defaults to "other")
  • String[] subTypes() (value is the list of allowable types for children of this bean)
  • String genericInterfaceName() (used in AMX proxy construction)
  •  boolean immutableInfo() (true if the MBeanInfo can never change)
  • String interfaceClassName() (also used in AMX proxy construction)
  • String type() (the type to use in an ObjectName or in an admin CLI path expression:  defaults to class name, but see also  stripPrefix)
 (This is just an application of @DescriptorKey, with a little special support in the gmbal implementation)
Class or Interface
N/A (but JMX supports descriptors; see below)
Adds GFv3 and AMX specific metadata to be defined for MBeans (see "Metadata Support" for more details).
@DescriptorKey
String value
Annotation method
same as JSR 255
Define metadata that can be added to any MBean (see "Metadata Support" for details)
@DescriptorField
String value (must be name=value)
Class, Interface, or Method
same as JSR 255
Define metadata directly on MBean (See "Metadata Support" for details)

Note that @ManagedAttribute and @Description may be used on fields. There is however a limitation: any such field MUST be final, and have a known immutable type.  This is necessary to guarantee that access to the field is safe in the presence of multiple thread.  The legal types are basically the SimpleTypes from JMX OpenType, with a couple of additions:

3.1. The ManagedObjectManagerFactory

The ManagedObjectManagerFactory is primarily used to create ManagedObjectManagers.  It is the only concrete class in the gmbal API, which is defined in the package org.glassfish.gmbal.  Obviously the implementation has other concrete classes, but these are contained in subpackages under org.glassfish.gmbal.

3.1.1. getMethod

This is a simple wrapper around Class.getDeclaredMethod that converts the checked exceptions from getDeclaredMethod into unchecked exceptions.  It is intended for use with the addAnnotation method.

3.1.2. create

There are two versions of create: createStandalone( String ) and createFederated( ObjectName ).  createStandalone( String ) is used to create a standalone hierarchy: the String is the domain to use for all ObjectNames created from the resulting ManagedObjectManager.  In this version, the root has no parent.

createFederated( ObjectName ) is used to create a hierarchy of MBeans that are rooted under a parent MBean that is not managed by the created instance of ManagedObjectManager.  In this case, the parent of the ManagedObjectManager is identified by the object name (called the rootParentName) that is passed to the create call.  Note that the root parent MBean MUST be managed by the same MBeanServer as the ManagedObjectManager.  The ObjectName MUST be an AMX-compliant ObjectName: in particular, it MUST define pp, type, and name attributes.

Note that createFederated must obey the parent-child restriction in AMX.  That is, a child cannot be created
before its parent has been created.  It is possible that the root parent ObjectName passed to createFederated does not correspond to a valid MBean in the MBeanServer at the time that createFederated is called.  In this case, the Gmbal ManagedObjectManager  created by the createFederated call must defer registration of its MBeans until after the root parent has been registered.  The MOM monitors the root parent in the MBeanServer, registering and de-registering all MBeans in the mom's tree as the root parent is registered and de-registered.

For examples of the usage of these methods, see "Using Gmbal".

3.2. The ManagedObjectManager

The ManagedObjectManager is the main API for gmbal.  It contains a tree of MBeans with a single root,
which may optionally be federated into a large MBean hierarchy.

The most important methods are createRoot, register, and unregister.  Any application of gmbal must use these methods.  For examples of the usage of the key methods, see "Using Gmbal".

3.2.1. createRoot

There are several methods to create a root in the ManagedObjectManager.  createRoot() creates a root that simply acts as an AMX container.  Its type and name are both set to GMBALROOT.

createRoot( Object ) and createRoot( Object, String ) both use the given object as the root, and just as in the register methods, these methods create an MBean from the Object using the annotations on its class. The createRoot( Object ) method will derive the name from the object itself by calling the @NameValue method from the class.  If no such method is available, the name is set to the type value.  The second method allows the name to specified explicitly.  This can be useful in case where either @NameValue is not used, or there are multiple ManagedObjectManager instances with the same root type that share the same parent.  In this case, it is essential that each root have a distinct name.last.  There is also a createRoot() method, which creates a simple internal root that only provides the required AMXv3 attributes.

createRoot may only be called when the ManagedObjectManager does not already contain a root. This is true immediately after a ManagedObjectManagerFactory.create call, or after unregistering the root.

3.2.2. getRoot

getRoot returns the current root of the ManagedObjectManager.  If no root is available, getRoot throws an IllegalStateException.

3.2.3. register and registerAtRoot

There are two forms of register: register( Object parent, Object obj ) and register( Object parent, Object obj, String name ).  The first form obtains the name from obj (by using @NameValue), the second allows explicit specification of the name.  The returned result is an instance of NotificationEmitter that may be used to register a NoticationListener to listen for attribute change notifications on the MBean that was created for obj. 

There are also two forms of registerAtRoot: registerAtRoot( Object obj ) and registerAtRoot( Object obj, String name).  These methods are exactly equivalent to mom.register( mom.getRoot(), obj ) and mom.register( mom.getRoot(), obj, name ).

All of these methods can only be called after a successful createRoot call.

3.2.4. unregister

unregister( Object ) unregisters an object that was previously registered.  It also deletes any children of the object in depth-first order.  Unregister removes the generated MBean from the ManagedObjectManager and also unregisters the MBean from the ManagedObjectManager's MBeanServer.

Unregister can only be called after a successful createRoot call.

3.2.5. getObject/getObjectName

The register methods return a NotificationEmitter which can be used to register a NotificationListener to listen for attribute change events.  But sometimes access to the ObjectName is needed.  getObjectName returns the ObjectName that was created for an object that was passed to the register method.  getObject is used to handle the opposite case: going from the ObjectName to the registered Object.  Note that in both case the registered Object is the implementation object passed to a register method, NOT the internally created dynamic MBean (which is not directly provided).

These methods can only be called after a successful createRoot call.

3.2.6. stripPrefix and stripPackagePrefix

A type prefix is simply a prefix of a fully qualified Java class name. By default, the MBean ObjectName type field is set to the full qualified class name of the implementation class, stripped of the longest type prefix of the class name.  For example, in CORBA the type prefixes are set to:

com.sun.corba.se
com.sun.corba.se.spi
com.sun.corba.se.spi.orbutil
com.sun.corba.se.impl
com.sun.corba.se.impl.orbutil

This helps keep the ObjectNames to a reasonable length.  Applications of gmbal are also free to specify the exact type name to use through an annotation.

stripPackagePrefix is used to indicate that ALL package prefixes should be removed from the class name for the default type.

These methods may only be called before a successful createRoot call.

3.2.7. MBeanServer and ResourceBundle

Accessors (getter and setter) are provided for the MBeanServer and the ResourceBundle.

The MBeanServer is used for all register and unregister operations in the ManagedObjectManager instance. It defaults to the
platform MBeanServer.

The ResourceBundle is used to internationalize all descriptions.  If set, the description value is used as a key in the resource bundle,
otherwise the description value is taken as the actual description.

3.2.8. addAnnotation

The best way to explain this method is to start with an example.  CORBA defines a class org.omg.PortableServer.Servant.   The definition of Servant is a standard, and so I cannot add annotations to the source code for Servant.  Gmbal is flexible enough that we can put the annotation almost anywhere, so what are the possible subclasses of Servant? Mostly one subclass is used in GlassFish for dynamic RMI-IIOP: ReflectiveTie, but others are possible, including Ties generated by the idlj compiler.  We also cannot add non-standard annotations to standard-compliant code generated by a code generator.

This becomes an issue when adding annotations to things like the ORB's POA, which has a method:
@ManagedData
Servant get_servant() { ... }
that I would like to use as an attribute.  How should gmbal represent Servant in this case?  I may want to represent the Servant as ManagedData, using one or more methods (such as byte[] _object_id()) on the Servant as attributes, but there is no place that I can put an annotation for this.

The solution I have adopted in gmbal is to allow annotations to be added on "dummy" classes, and then use  ManagedObjectManager.addAnnotation to add the annotation to the appropriate type.  For example, the above example can be handled by:
@ManagedData
@InheritedAttributes( {
@InheritedAttribute( methodName="_object_id", id="objectId",
description="The Object Id for this Servant" ),
... // several other methods
} )
public interface DummyServant {}

mom.addAnnotation( Servant.class,
DummyServant.class.getAnnotation( ManagedObject.class ) ) ;
mom.addAnnotation( Servant.class,
DummyServant.class.getAnnotation( InheritedAttributes.class ) ) ;
This behaves exactly as if the annotations on DummyServant were on Servant, allowing the appropriate  annotation processing to take place.

InheritedAttribute can also be used when it is desired to use a method inherited from a super class as an attribute or operation.  This is useful in the case where the superclass is a class that for whatever reason cannot be modified with an annotation.  For example, the GlassFish v3 ORB defines an interface TaggedProfileTemplate (in com.sun.corba.se.spi.ior) as follows:
@ManagedData
@Description( "A template for creating a TaggedProfile" )
@IncludeSubclass( { com.sun.corba.se.spi.ior.iiop.IIOPProfileTemplate.class } )
@InheritedAttribute( description="List of TaggedComponents for this template",
methodName="iterator", id="TaggedComponents" )
public interface TaggedProfileTemplate extends List<TaggedComponent>,
Identifiable, WriteContents, MakeImmutable
{
...
}

Here the iterator method is inherited from List<TaggedComponent>.  The iterator() method then has type Iterator<T>, where T is bound to TaggedComponent. This results in a CompositeData attribute with id TaggedComponents, which returns an OpenType which is an array of whatever TaggedComponent maps to (CompositeData as discussed in "Handling Polymorphism").

3.2.9. Debugging support

Gmbal is a fairly complex system, and it is possible to apply it in ways that lead to incorrect results. To avoid problems, following the suggested patterns in "Using Gmbal" will help.  Mistakes such as using an @ManagedAttribute operation on a method that is not a valid attribute should be obvious from the exception thrown by the register call.  But perhaps the problem is simply that the MBean definition is not exactly what was expected, due to the pattern of annotations across multiple class and interface definitions. Some assistance may be needed from gmbal to see what exactly is happening, and also to aid in identifying bugs in gmbal itself.

To help with these kinds of problems, gmbal has several features that can be useful for debugging:
These operations can be used to get useful information about the operation of gmbal.  The setXXXDebugLevel methods currently dump
information to System.out, but the code has a flag that can be set to dump information to a Logger.  The Logger may become the
default dump mechanism in the future.

3.2.10. Close

The close call is provided to unregister all MBeans still registered with the MBeanServer, and to release all internal state maintained by the ManagedObjectManager. Note that the registered MBeans are unregistered and destroyed bottom up, to preserve the AMX requirements that children cannot exist without their parent existing (see section 5.1 "Parent must exist" of the AMX SPI specification).

3.2.11. suspendJMXRegistration/resumeJMXRegistration

It is sometimes useful to be able to suspend and resume registration of gmbal-generated MBeans with the MBean server. This is mainly useful when it is necessary to create one or more MBeans in a constructor in order to avoid complex refactoring of the code. Normally ManagedObjectManager.register( this ) should NOT be used in a constructor, because it exposes a partially constructed object to the MBean server, which may then invoke methods on the partially constructed object in order to execute operations, or get or set attributes. However, gmbal itself will NEVER invoke any method or access any field of an object unless an MBean operation requires it, so it is safe to pass partially constructed objects into gmbal itself. So avoiding MBeanServer registration by suspending registration allows this case to be handled easily.

3.2.12. getAMXClient

This returns an AMXClient that is a convenient wrapper for accessing the features of the MBean corresponding to the object passed to getAMXClient.

3.2.13. getDomain

This method returns the domain for all MBeans created in the mom.  This is either the domain passed to createStandalone, or the domain of the ObjectName passed to createFederated.

3.2.14. isManagedObject

This method is a convenience API that simply checks whether or not the object passed to isManagedObject is annotated as a @ManagedObject or not.

3.2.15. suppressDuplicateRootReport

This controls the behavior of setting the MOM's root.  If true, no error is reported when the root has already been registered with the JMX MBeanServer.  This method was added to allow GFv3 monitoring to avoid reporting an uninteresting error.

4. Further notes on the API

There are a number of details about how the APIs work that require further discussion.

4.1. Processing of method names for IDs

The following rules are applied to generate attribute and operation IDs from method names:

  1. If the method is annotated with @ManagedAttribute:
    1. If the method is a getter (that is, it has no arguments and a non-void result):
      1. If the method name starts with "get" or "is", remote the prefix.  The rest of the method name is the attribute ID for an attribute in a ManagedObject.  If the attribute is in a ManagedData, then the first character is forced to lower case.
      2. Otherwise the method name is the id.
    2. If the method is a setter (that, has one argument and a void result):
      1. If the method name starts with "set", remove "set". The rest of the method name is the attribute ID for an attribute in a ManagedObject.  If the attribute is in a ManagedData, then the first character is forced to lower case. 
      2. Otherwise the method name is the id.
    3. Otherwise the method is not a valid attribute, and an exception will be thrown
  2. If the method is annotated with @ManagedOperation:
    1. The id is the method name.
These rules are applied unless the ManagedAttribute or ManagedOperation annotation has a non-default id attribute, in which case the id from the annotation is used.

4.2. I18N support

Gmbal supports I18N by supplying a ResourceBundle to the ManagedObjectManager.  If this option is used, all values of @Description annotations must be resource bundle keys.  The appropriate ResourceBundle must then be associated with the ManagedObjectManager when the ManagedObjectManager is created.

Note: this feature is not implemented in the GFv3 FCS version of Gmbal.

4.3. OpenType mapping

Gmbal mostly follows the OpenType mapping rules for MXBeans (see the Type Mapping Rules), but of course
MXBeans are only available on JDK 6 and later, and gmbal supports JDK 5. There are a few differences:
  1. Gmbal supports all Collection, Iterator, Iterable, and Enumeration types where MXBeans support List, Set, and SortedSet.
  2. Gmbal also maps Dictionary as well as Map to TabularType.
  3. Gmbal maps all @ManagedObject types to ObjectName (in MXBeans, this is any MXBean interface).
  4. Gmbal only maps other types to CompositeData if the type has an @ManagedData annotation.  If not, gmbal maps the type to a String using its toString() method.
  5. Gmbal does not support reconstructing a Java type from CompositeData. Gmbal could be extended in this area if necessary.
  6. Gmbal will provide more support for converting generic types into generic MBean types, which can be instantiated into specific MBeans based on the actual type parameters supplied to an instance of the generic type.
Gmbal does not currently provide support for setting attributes with CompositeData values (but this would be easy to add,  following the rules used for MXBeans).

4.4. Metadata support

Gmbal fully supports extensible metadata on MBeans by using the JMX ModelMBean Descriptor, which is available on JDK 5. JDK 6 has applied Descriptor support to ALL MBean metadata, but gmbal must continue to support JDK 5 for now. Gmbal supports the @DescriptorKey and @DescriptorFields annotations defined in JSR 255 for associating metadata with MBeans.  Gmbal also defines an annotation @AMXMetadata (which itself uses the meta-annotation @DescriptorKey) in order to support the metadata needed by AMX.

@AMXMetadata can be used most easily on a simple mbean::
@AMXMetadata
public interface MyBean1 { ... }

In this case, MyBean1 is a singleton (only one of this type may appear in the same parent), its MBean info never changes, the proxy interface name defaults to a generic implementation, the group is other, and the list of valid subtypes is empty, which means any type may appear as a child.  Note that the @AMXMetadata annotation is not needed at all in this case.

Something like

@AMXMetadata( isSingleton=false, type=Widget )
public interface MyBean2 { ... }

defines an AMX MBean which is not a singleton.  Also, the type will be set to Widget in the ObjectName created for an instance of MyBean2.

The use of @DescriptorKey is discussed (with a good example) in the Java 6 Javadoc for DescriptorKey. Eamonn McManus discusses @DescriptorKey and @DescriptorFields in a rather lengthy blog post "Defining MBeans with Annotations".  Either annotation allows almost any sort of metadata to be added to MBeans defined by gmbal, with no changes to gmbal required to add the additional metadata.

4.5. AMX support for MBeans

Gmbal is fully compliant with all requirements and recommendations of the GlassFish v3 AMX SPI.
This includes:

4.6. Using Gmbal

The basic usage of Gmbal is as follows:
  1. Add annotations to existing application objects (here application means anything you want to manage, part of GFv3 or not) to expose attributes and operations.
  2. In some cases you may need to expose some data by defining new methods.  Currently all gmbal visible annotations must be public, but I may relax that (at the cost of requiring an access check permission for reflection).
  3. In the initialization of a module, create an appropriate ManagedObjectManager (call it mom).  This requires thinking about where in the federated hierarchy the root of the ManagedObjectManager should live.  In some cases, this may be different depending on the environment in which the instrumented code runs.
  4. Whenever an Object is created that requires a management interface, register that object with the mom.
  5. Whenever an Object is no longer needed, unregister that object with the mom.
  6. Whenever the module lifecycle ends, call mom.close() to clean up the root object and any other remaining registered objects.
Here is an example of typical code for using gmbal. All examples are taken from the experimental version of the GlassFish v3 ORB (available in the experimental ORB repository from the GFv3 ORB project on Kenai.com):

First, from the com.sun.corba.se.spi.orb.ORB.createORBManagedObjectManager method:
    // mom is declared the ORB object, which stays around the entire time the ORB is in use.
// Here I am using createStandalone. This needs to be replaced with a call to createFederated
// when running in GlassFish v3. This will be driven through the ORB configuration.
mom = ManagedObjectManagerFactory.createStandalone( "com.sun.corba" ) ;

// The ORB already has a system for setting debug options, so I just extended it for gmbal.
if (mbeanFineDebugFlag) {
mom.setRegistrationDebug( ManagedObjectManager.RegistrationDebugLevel.FINE ) ;
} else if (mbeanDebugFlag) {
mom.setRegistrationDebug( ManagedObjectManager.RegistrationDebugLevel.NORMAL ) ;
} else {
mom.setRegistrationDebug( ManagedObjectManager.RegistrationDebugLevel.NONE ) ;
}

mom.setRuntimeDebug( mbeanRuntimeDebugFlag ) ;

// Here I add all of the package prefixes that I do NOT want to see in ObjectNames.
// The longest match is stripped from the class name to create the default value for the
// type.
mom.stripPrefix( "com.sun.corba.se" ) ;
mom.stripPrefix( "com.sun.corba.se.spi" ) ;
mom.stripPrefix( "com.sun.corba.se.impl" ) ;
mom.stripPrefix( "com.sun.corba.se.spi.orbutil" ) ;
mom.stripPrefix( "com.sun.corba.se.impl.orbutil" ) ;
    // Here I am using the guaranteed to be unique ORB id as the name of the topmost ORB mbean.
mom.createRoot( this, getUniqueOrbId() ) ;


In order to integrate the ORB with GlassFish v3, there is quite a bit of GlassFish v3-specific code that initializes the ORB. This code will need to be placed in an appropriate OSGi bundle (either in the EJB remote bundle, or possible in a separate embedded ORB support bundle). Then the ORB SPI needs to be extended to support a call like setMBeanRootParent( ObjectName ).  If this method is called (which would normally only happen from inside the ORB support code in GlassFish v3), the initialization sequence needs to call ManagedObjectManagerFactory.createFederated instead of createStandalone.  Everything else stays exactly the same, giving an easy way to run the ORB either in GlassFish v3, or standalone (for example, as part of JDK 7, if we get to that).

Next we need to see an example of MBean creation code.  Again the example is from the GFv3 ORB, this time in the timer subsystem (from the orblibrary package com.sun.corba.se.spi.orbutil.newtimer). The timer subsystem defines a number of different interfaces (e.g. Timer, TimerGroup, TimerFactory, StatsEventHandler among others), instances of which are registered as MBeans. Each of these classes extends a base interface called Named defined as follows:

public interface Named {
/** Return the TimerFactory that created this Named.
*/
@ManagedAttribute
@Description( "TimerFactory that created this Timer or TimerGroup" )
TimerFactory factory() ;

/** A short name for this Controllable.
*/
@ManagedAttribute
@NameValue
@Description( "Name of this Timer or TimerGroup" )
String name() ;
}

Note that the name() attribute carries the @NameValue attribute.  This simplifies the registration which takes place in TimerFactoryImpl.  The TimerFactoryImpl contains all of the factory methods for creating the various timer interfaces mentioned previously. TimerFactoryImpl defines several private methods as follows:

    private void manage( Named obj ) {
// Note that no extra parameters are needed here, because Named.getName
// is an NameValue.
if (mom != null) {
// System.out.println( "Registering " + obj ) ;
mom.registerAtRoot( obj ) ;
}
}

private void manage( Named parent, Named obj ) {
// Note that no extra parameters are needed here, because Named.getName
// is an NameValue.
if (mom != null) {
// System.out.println( "Registering " + obj ) ;
mom.register( parent, obj ) ;
}
}

private void unmanage( Named obj ) {
if (mom != null)
mom.unregister( obj ) ;
}

These methods support the case where no ManagedObjectManager (mom) is present, which may be a useful pattern in other cases. For example, Grizzly must be able to run in as small a configuration as possible, but Grizzly would like to have MBeans available as an option. Consequently, the gmbal annotations and API (plus one small utility class) are defined in the gmbal-api-only bundle (about 14K).  Another bundle (gmbal, about 240K) includes everything in gmbal-api-only, plus a full implementation.  One  of the bundles gmbal-api-only or gmbal needs to be present in all cases, and all of the code in this example requires ONLY gmbal-api-only.  If gmbal is NOT available, ManagedObjectManagerFactory.createStandalone and createFederated will return an instance of a no-op implementation of ManagedObjectManager. This would allow the above code to avoid the null check, which was required in an older version of gmbal.

Now consider actually creating instances of various timer classes. FIrst, the TimerManager is itself represented by an MBean, so it must be registered. It is tempting to do this in the constructor (in fact, this mistake has been made in the ORB). You should not do this!  Calling mom.register( this ) in the constructor violates one of the cardinal rules of Java concurrent programming: do not publish objects before construction is complete.  As soon as the mom.register call returns, the MBean has been registered with the ManagedObjectManager's MBeanServer, and this means that remove invocations on unknown thread could arrive at any moment, including before the constructor has returned. Note that any method of an object that is used at an attribute or operation MUST be thread safe, as such a method can be invoked at any by one or more threads from remote JMX connectors.

However, there are times when the constructor is the only reasonable place to do the registration. This happen in situations where:

To avoid this problem, the ManagedObjectManager now supports suspend/resume on registration of MBeans with the MBeanServer through the methods suspendJMXRegistration() and resumeJMXRegistration(). When registration is suspended, newly created MBeanImpl instances are kept in a queue.  Once resume is called, all enqueued MBeanImpl instances are registered with the MBeanServer.

In the timer library case, the TimerFactory is constructed by a TimerFactoryBuilder as follows:

    public synchronized static TimerFactory make( ManagedObjectManager mom, 
String name, String description ) {

if (fmap.get( name ) != null)
throw new IllegalArgumentException(
"There is currently a TimerFactory named " + name ) ;

TimerFactory result = new TimerFactoryImpl( mom, name, description ) ;
fmap.put( name, result ) ;
mom.register( result ) ; // registered AFTER construction as a child of mom's root
return result ;
}

Here's an example of registering a TimerGroup (from TimerFactory):

    public synchronized TimerGroup makeTimerGroup( String name, 
String description ) {

checkArgs( timerGroups.keySet(), name, description ) ;

TimerGroupImpl result = new TimerGroupImpl( nextIndex, this, name,
description ) ;
manage( this, result ) ;

mapId( result ) ;
timerGroups.put( result.name(), result ) ;
add( result ) ; // Remember, a TimerFactory is a TimerGroup
// containing all
// Controllables that it creates!

return result ;
}

Here the TimerGroup has an MBean that is registered as a child of the TimerFactory. The TimerGroup interface itself is annotated as follows:

@ManagedObject
@Description( "A group of Timers or other TimerGroups, which may be enabled or disabled together" )
public interface TimerGroup extends Controllable {
@ManagedOperation
@Description( "Add a new Timer or TimerGroup to this TimerGroup" )
boolean add( Controllable con ) ;

@ManagedOperation
@Description( "Remove a new Timer or TimerGroup from this TimerGroup" )
boolean remove( Controllable con ) ;
}

Controllable is fairly complex, and defines a number of attributes:

@ManagedObject
@IncludeSubclass( { Timer.class, TimerGroup.class, TimerFactory.class } )
public interface Controllable extends Named {
    /** A longer description giving some details of the meaning of this
     * Controllable.
     */
    @ManagedAttribute
    @Description( "The purpose of the Timer or TimerGroup" )
    String description() ;

    /** A small id for this controllable.  Each controllable created from
     * the same TimerFactory will have a unique ID.  All ids will be small
     * integers starting at 0 (so indexing tables by timer ID is supported).
     */
    @ManagedAttribute
    @Description( "An internal identifier for the Timer or TimerGroup" )
    int id() ;objectClass

    /** Return an unmodifiable set of the contents of this Controllable.
     * May always be empty for some subclasses of Controllable.
     */
    @ManagedAttribute
    @Description( "Set of Timers or TimerGroups contained in a TimerGroup" )
    Set<? extends Controllable> contents() ;

    /** Enable this controllable.  All Timers that are either enabled, or
     * reachable via contents() from an enabled Controllable are activated,
     * and will cause TimerEvents to be generated when passed to the
     * TimerEventController enter and exit methods.
     */
    @ManagedOperation
    @Description( "Enable this Timer, or all Timers and TimerGroups contained in this TimerGroup" )
    void enable() ;

    /** Disable this controllable.
     */
    @ManagedOperation
    @Description( "Disable this Timer, or all Timers and TimerGroups contained in this TimerGroup" )
    void disable() ;

    /** Return true if enable() was called, otherwise false if enable() was never
     * called, or disable() was last called.
     */
    @ManagedOperation
    @Description( "True if this Timer or TimerGroup is enabled" )
    boolean isEnabled() ;
}

Because TimerGroup extends Controllable extends Named, the TimerGroup inherits the name() attribute which is annotated with @NameValue. This means that the result of calling timerGroup.name() is used as the value of the name attribute in the ObjectName for the MBean constructed for timerGroup.

Finally, when an object is no longer needed, the ManagedObjectManager.unregister method must be called. In some case this is handled through an explicit method to remove the object, e.g. for TimerEventHandle in the TimerFactory.removeTimerEventHandler method:

    public synchronized void removeTimerEventHandler( 
TimerEventHandler handler ) {

timerEventHandlers.remove( handler.name() ) ;
unmanage( handler ) ;
}

If the mbeans should be registered as long as the module exists, ManagedObjectManager.close() can be used to unregister everything. In the case of the ORB, the close call should happen in the ORB.destroy code. 

4.7. Handling Polymorphism

There are cases where an attribute has a type defined by a base class or interface, but the actual implementation can be one of several different classes.  For example, the ORB has a TaggedComponent interface, representing part of the data in a remote reference, and there are several concrete subclasses of TaggedComponent that are used.  This is not a problem for @ManagedObject classes, since they are reference by ObjectNames, and an ObjectName can reference any type of  MBean.  But this does not work for @ManagedData, because @ManagedData is mapped to a CompositeData type, and CompositeData does not support polymorphism.

Gmbal supports a limit form of polymorphism to handle this case using the @IncludeSubclass annotation.  @IncludeSubclass gives the subclasses of a @ManagedData type which themselves can contribute attributes to the CompositeData for the parent class.  Gmbal generates a CompositeData type that contains the union of all fields in all of the subclasses. At runtime, only those attributes actually present in a particular instance of the @ManagedData class are present in the CompositeData for the instance.  In this way, gmbal supports a limit form of polymorphism under a closed-world assumption.

Here is an example from the CORBA case:

first, the base interface:
/** Generic interface for all tagged components.  Users of the ORB may
* create implementations of this class and also corresponding factories
* of type TaggedComponentFactory. The factories can be registered with an
* ORB instance, in which case they will be used to unmarshal IORs containing
* the registered tagged component.
*/
@ManagedData
@Description( "Base class for all TaggedComponents" )
@IncludeSubclass( { AlternateIIOPAddressComponent.class,
CodeSetsComponent.class, JavaCodebaseComponent.class,
MaxStreamFormatVersionComponent.class, ORBTypeComponent.class,
RequestPartitioningComponent.class,
GenericTaggedComponent.class } )
public interface TaggedComponent extends Identifiable
{
org.omg.IOP.TaggedComponent getIOPComponent( ORB orb ) ;
}

then, an example of two of the subclasses:

@ManagedData
@Description( "Component containing an alternate IIOP address to use" )
public interface AlternateIIOPAddressComponent extends TaggedComponent
{
@ManagedAttribute
@Description( "The Alternate address" )
public IIOPAddress getAddress() ;
}

@ManagedData
@Description( "Component representing Codebase URLs for downloading code" )
public interface JavaCodebaseComponent extends TaggedComponent
{
@ManagedAttribute
@Description( "List of URLs in the codebase" )
public String getURLs() ;
}

The result of this is that any reference to the TaggedComponent type in a @ManagedObject will mapped to CompositeData with the Address and URLs attributes.  However, the actual CompositeData to which an instance of TaggedComponent is mapped will only have Address if it isA AlternateIIOPAddressComponent, otherwise that attribute will be unavailable.  If TaggedComponent defined any attributes, those attributes would be present in all instance of CompositeData representing a TaggedComponent (in this case the org.omg.IOP.TaggedComponent type is simply not interesting enough to expose as an Attribute).

4.8. Issues with generic types

This is best explained with an example.  Suppose we have interfaces defined as follows:

@ManagedData
public interface ExampleData<S,T> {
S getInfo() ;

List<T> getData() ;
}

public interface Example<S,T> {
@ManagedAttribute
ExampleData<S,T> getInfo() ;
}

@ManagedObject
public interface MyBean extends Example<Date,List<String>> {
}
What sort of MBean should we create for MyBean?  If we just use reflection on MyBean, we find an attribute info that returns an ExampleData<S,T>, which would turn into a CompositeData with two attributes:
But we actually have a lot more information than this: if we "evaluate" the types, we see that MyBean extends Example<Data,List<String>>, so the Example.getInfo method has type MethodData<Date,List<String>>, and so MyBean.getInfo returns a composite data with 2 attributes:
So instead of erasing the types, we evaluate the type variable by replacing the type variables with the types used to instantiate them.  Another way of looking at this is that annotating a generic interface or class with @ManagedData or @ManagedObject is basically creating a template for creating the actual CompositeData or Open MBean.

4.9. Issues with recursive types

CompositeData cannot be defined directly from class definitions that have recursive types.  Here is an example of the problem:
@ManagedData
public interface A {
@ManagedAttribute
B getB() ;
}

@ManagedData
public interface B {
@ManagedAttribute
A getA() ;
}
The problem here is that we need to construct an instance of CompositeType to represent the OpenType to which A and B are  mapped.  But CompositeData is immutable, so in order to construct the CompositeType for A, we need the CompositeType of B, which can't be constructed without using the CompositeType of A.  So a direct mapping fails here, and gmbal will detect this problem and throw an exception.

The way to work around the problem is to add a level of indirection so that one of the classes refers to the other indirectly. For example, support A has a convenient String name attribute we can use (which is a key for instances of A).  Then the above can be expressed as:
@ManagedData 
public interface A {
@ManagedAttribute
String getName() ;

@ManagedAttribute
B getB() ;
}

@ManagedData
public interface B {
@ManagedAttribute( id="A" )
String getARef() ; // returns getA().getName()

A getA() ;
}
In this way the relationship between A and B can still be represented in the management API without needing recursive references between different data types that are represented with CompositeData.

Note that it is possible to automate this to some extent, but I currently have no plans to extend gmbal to automatically handle this problem.

4.10 Method call order requirements

As noted in the methods on the ManagedObjectManager, there are constraints on the order in which certain methods may be called. This is done to avoid problems where some MBeans are created, then other methods are called (like addAnnotation or stripPrefix) which could change the attributes, operations, ObjectNames, or metadata on any Gmbal MBean that has already been created.  This divides the methods up into 3 categories:
Any violation of this requirement results in an IllegalStateException.

4.11. Computing the type name

The type name used in the ObjectName is computed as follows from the class of the registered object:
  1. If the class contains a final static field of type String name AMX_TYPE, the value of the field is the type.
  2. Otherwise, if the class has an annotation @AMXMetadata and the type field is not "", the type field is the type.
  3. Otherwise, if the package prefix of the class name matches one of the stripPrefix call arguments, that part of the package prefix is removed from the full class name, and the remainder (minus the initial dot) is the type name.
  4. Otherwise, if stripPackagePrefix was called, the entire package prefix is removed (if any), and the remaining class name is the type name.
  5. Otherwise, the full class name is the type name.
The same algorithm is used for determining the name of a CompositeData type for @ManagedData, with a few slight differences:
  1. If the class contains a final static field of type String name GMBAL_TYPE, the value of the field is the name.
  2. Otherwise, if the class's @ManagedData annotation has a name element with a value other than "", the value of the name element is the name.
  3. Otherwise, if the package prefix of the class name matches one of the stripPrefix call arguments, that part of the package prefix is removed from the full class name, and the remainder (minus the initial dot) is the name.
  4. Otherwise, if stripPackagePrefix was called, the entire package prefix is removed (if any), and the remaining class name is the type name.
  5. Otherwise, the full class name is the name.

5. Dependencies

The only dependency that gmbal has is JDK 5.  It does not require anything from JMX that is not in JDK 5.  It also has no ant
 dependencies on GlassFish v3  or CORBA.

6. Testing

All tests for gmbal are written in Java using JUnit, and reside in the test directory in the project.

The following tests are currently available:

Test Package Name
Tests Included
org.glassfish.gmbal
ManagedObjectManager and basic annotation processing
org.glassfish.gmbal.generic
Facet mechanism used for dynamic inheritance
org.glassfish.gmbal.impl
TypeConverter test
org.glassfish.gmbal.logex
Testing the log wrapper generator
org.glassfish.gmbal.typelib
Tests the typelib (derived from a JDK 7 test suite)

7. Project Details

The gmbal project is available in a mercurial workspace at https://kenai.com/hg/gmbal~master.  Note that gmbal will NOT be built as part of the  GFv3 build.  This is necessary because gmbal is also used outside of GFv3, and we need to avoid circular build dependencies (e.g. GFv3 dependsOn CORBA dependsOn gmbal, which creates a build loop if GFv3 also contains gmbal).

gmbal uses the following Java packages:
Package Name
OSGi  Bundle
Purpose
org.glassfish.gmbal
gmbal-api-only, gmbal
all public interfaces
org.glassfish.gmbal.util
gmbal-api-only, gmbal
contains code needed to support the API-Impl split (reflective construction of the implementation)
org.glassfish.gmbal.generic
gmbal
General-purpose code for debugging, plus simple function programming support
org.glassfish.gmbal.impl
gmbal
The full implementation. 
org.glassfish.gmbal.logex
gmbal
Log wrapper code for logging exceptions and managing other I18N data
org.glassfish.gmbal.tools.argparser
build only
argument parser used in copyright utilities (from CORBA)
org.glassfish.gmbal.tools.file
build only
file handling utilities and copyright process (from CORBA)
org.glassfish.gmbal.typelib
gmbal
utility library for type evaluation

The gmbal project also contains the package org.glassfish.probe.provider.annotations, which is present in gmbal so that projects that are not part of GFv3 may support GlassFish monitoring probes without depending at build-time or runtime on anything other than a single probe bundle.

The resulting OSGi bundles are currently (version 3.0-FCS) sized at:

gmbal-api-only
22 KBytes
gmbal
272 Kbytes

Any gmbal client needs to use either gmbal-api-only or gmbal, but not both.  Both bundles are available in the standard place in the maven repository, with group ID org.glassfish.gmbal.

The probe annotations are in the gfprobe-provider-client bundle, which is currently 2576 bytes.
Source bundles are also provided for both gmbal (gmbal-source) and grprobe-provider-client (gfprobe-provider-client-source).