Using the MBeanRegistration Interface to Manage MBean Life Cycles

By Daniel Fuchs

The Java Management Extensions (JMX) have been present in the JDK since J2SE 5.0. In JDK 6, Sun has added a new advanced JMX sample to the JDK sample directory. The new sample -- $JDK_HOME/sample/jmx/jmx-scandir -- builds on the JMX best practices and discusses some usual JMX patterns, and some pitfalls to avoid, when you design a management interface with JMX.

In particular, the jmx-scandir sample shows all the different use cases that can be solved by implementing the MBeanRegistration interface.

This technical tip will focus on one such use case: using the MBeanRegistration interface to manage Management Bean (MBean) life cycles. Readers who are not familiar with JMX and the notion of MBeans should follow the JMX trail from the Java Tutorials before going any further.

The MBeanRegistration Interface

The MBeanRegistration interface is a callback interface. It can be implemented by MBeans, which need to perform specific actions when being registered to or unregistered from their MBeanServer. When an MBean implements MBeanRegistration, methods defined in that interface will be called by the MBeanServer before and after the MBean is registered and unregistered from the MBeanServer.

An MBean that implements the MBeanRegistration interface will obtain a reference to the MBeanServer in which it is being registered, will have a chance to obtain -- and possibly change -- its own ObjectName before being registered, and will get an opportunity to accept or reject its own registration or unregistration.

In the following code extract, the Rack MBean implements the MBeanRegistration interface:

------------------------------------------------------------------
package jmxtechtip;

public interface RackMBean {
   /... defines methods exposed for management .../
}
------------------------------------------------------------------
package jmxtechtip;

public class Rack implements RackMBean, MBeanRegistration {
    /... implements RackMBean methods .../
    /... implements MBeanRegistration methods .../
}
------------------------------------------------------------------

The MBean interface does not extend MBeanRegistration. Only the concrete MBean class implements it. Indeed, it would be a violation to expose the methods defined in MBeanRegistration through the management interface, since the MBeanRegistration methods are designed to be called only by the MBeanServer at registration time.

This technical tip will show you how to implement the methods of MBeanRegistration to register and unregister dependent MBeans.

The Racks and Cards Use Case

Let's assume you are designing a JMX application that will expose a piece of equipment hardware. Your equipment has a rack -- represented by a RackMBean. The rack itself has a number of slots where cards can be plugged in. When plugged in, each card is represented by a CardMBean or by a subclass of CardMBean.

In this example, assume that the RackMBean is completely responsible for creating and registering CardMBean instances. The class Card can be extended to provide Card-specific implementation. The class Rack can be extended to override its createCard(...) method.

Here are the aims you are trying to achieve:

\* In the class Rack, when a RackMBean is being registered, discover which slots already contain cards, and create and register a CardMBean for each of them.

\* In the class Rack, when a RackMBean is being unregistered, unregister all its corresponding instances of Card.

\* In the class Card, ensure that a CardMBean can only be registered by a Rack, and ensure that a CardMBean can only be unregistered by a Rack, and not by, for example, a client JMX application calling MBeanServer.unregisterMBean(...) directly for a CardMBean.

Note: The intent here is not to show how to model a rack or a card. The author chose racks and cards only for the sake of the example, because their relationship is easy to understand.

Implementing the MBeanRegistration Interface

The following code snippet shows how the Rack MBean could implement the MBeanRegistration interface:

 /\*\*
  \* A Rack contains Cards.
  \*/
 public class Rack implements RackMBean, MBeanRegistration {

     // MBeans must be multi-thread safe.
     // 'rackName' and 'server' will be set at registration time, so you
     // must therefore take care of synchronization issues.
     // Use 'volatile' to make sure that you always use an accurate value.
     //
     // Name of this rack, extracted from the ObjectName.
     private volatile String rackName;
         // The MBeanServer in which this MBean is registered
     private volatile MBeanServer server;
         // An array of slots that may be occupied (and contain a card) or may be
     // unoccupied. slots[i]==null means that slot 'i' is unoccupied.
     private final Card[] slots;
         /\*\*      \* Creates a new rack.
      \* @param slotCount Total number of slots.      \*\*/
     public Rack(int slotCount) {
         if (slotCount < 0)
             throw new IllegalArgumentException(Integer.toString(slotCount));
         this.slots = new Card[slotCount];
         ...
     }

     // --------------------------------------------------------------------
     // \*Implementation of the MBeanRegistration Interface\*
     // --------------------------------------------------------------------
         /\*\*
      \* Allows the MBean to perform any operations it needs before
      \* being registered in the MBean server.
      \* If any exception is raised, the MBean will not be registered
      \* in the MBean server.
      \*
      \* RackMBean uses this method to check the validity of the supplied
      \* ObjectName, and in particular that it corresponds to a valid rack.
      \*
      \* It also uses this method to obtain a reference to the MBeanServer in
      \* which it is registered.
      \*
      \* @param server -- The MBean server in which the MBean will be registered.
      \* @param name -- The object name of the MBean. In this implementation, the
      \* supplied name must not be null.
      \*
      \* @return -- The name under which the MBean is to be registered. If null,
      \* the registration will fail.
      \*
      \* @throws Exception -- This exception will be caught by the MBean
      \* server and rethrown as an MBeanRegistrationException.
      \*/
      \*public ObjectName preRegister(MBeanServer server, ObjectName name)
         throws Exception {\*
         // A null ObjectName is not allowed. Let MBeanServer throw
         // the exception.
         if (name == null)             return null;
                 // Get rackname.
         rackName = name.getKeyProperty(RACK_KEY);
         if (rackName == null)
             throw new MalformedObjectNameException("missing rack name: " + name);
                 if (!isValidRack(rackName))
             throw new IllegalArgumentException(rackName + ": not a valid rack");
                 this.server = server;
         return name;
     }

     /\*\*
      \* Allows the MBean to perform any operations needed after having
      \* been registered in the MBean server or after the registration
      \* has failed.
      \*
      \* If the registration is successful, the Rack MBean will
      \* register all its related CardMBeans.
      \*
      \* @param registrationDone -- Indicates whether or not the MBean
      \* has been successfully registered in the MBean server.
      \* The value false means that the registration has failed.
      \*/
     \*public void postRegister(Boolean registrationDone) {\*
                 if (!registrationDone) return;
                 for (int i = 0; i < slots.length; i++) {
             final String cardType = discoverCardType(i);
             if (cardType != null) {
                 try {
                     final Card card = createCard(i, cardType);
                     final ObjectName cardName = createCardName(rackName, i);
                         // Avoid calling an MBeanServer operation
                         // from within a synchronized block.
                     card.registerSelf(server, cardName);
                                         synchronized (slots) {
                         slots[i] = card;
                     }
                 } catch (Exception x) {
                     LOG.warning("Couldn't create CardMBean for " +
                             cardType + "[" + i + "]");
                 }
             }
         }
             }

     /\*\*
      \* Allows the MBean to perform any operations it needs before being
      \* unregistered by the MBean server
      \*
      \* The RackMBean uses this method to unregister all its related
      \* CardMBeans. If for some reason, unregistration of one of these
      \* MBeans fails, the RackMBean will no be unregistered either.
      \*
      \* @throws Exception -- This exception will be caught by the MBean
      \* server and rethrown as an MBeanRegistrationException.
      \*/
      \*public void preDeregister() throws Exception {\*
         for (int i=0; i

In preRegister <#preRegisterRack>, the Rack MBean obtains a reference to the MBeanServer in which it is registered, and a reference to its own ObjectName. It can check that the supplied ObjectName is valid -- in this example, it is considered valid if it contains the rack key -- and that it can indeed model the rack that it is asked to model. Here, this is supposed to be done by the isValidRack(...) method, whose implementation is not shown. If these conditions are not met, the Rack MBean will throw an exception, thus refusing to be registered.

In postRegister <#postRegisterRack>, the Rack MBean checks whether registration was successful. If it wasn't, it stops here. Otherwise, it will discover which slots already have a card plugged in, and for each of those, it will create and register a CardMBean. However, instead of using plain MBeanServer.registerMBean(...), it calls a package-protected method on the created Card instance -- in this example, Card.registerSelf() <#registerSelf>.

This method has been created with logic such that a CardMBean can only be registered in the MBeanServer by invoking its registerSelf(...) method.

Note that postRegister isn't supposed to throw an exception. You have thus protected all Card MBean registrations by a try { } catch { } block. This is OK because according to the application logic, Card MBean registration is not expected to fail at this point. If it does, you will simply log a warning message. Had you wanted the Rack MBean registration to fail whenever a Card MBean couldn't be registered, you would have implemented the Card registration logic in preRegister <#preRegisterRack> instead. However, that logic would have been more complex, since you would have had to implement code to rollback registration of already registered Card MBeans in case one of them failed to be registered.

In preDeregister <#preDeregisterRack>, the Rack MBean unregisters all Card MBeans it has previously registered. Here again, instead of using plain MBeanServer.unregisterMBean(...), it calls a package-protected method on the registered Card instance -- in this example, Card.unregisterSelf() <#unregisterSelf>.

This method has been created with logic such that a Card MBean can only be unregistered from the MBeanServer by invoking its unregisterSelf(...) method. Simply calling MBeanServer.unregisterMBean(...) would fail. This ensures that only the creator of the Card MBean will be able to register and unregister it. If a JMX client attempts to directly call MBeanServer.unregisterMBean(...) on a Card MBean, it will fail.

Now let's see how the Card MBean uses its MBeanRegistration interface. The following code snippet show how the Card MBean was implemented:

 /\*\*
  \* A Card is plugged in a Rack slot.
  \*/
 public class Card implements CardMBean, MBeanRegistration {

     // This MBean will accept to be registered or unregistered
     // only if 'isRegistrationAllowed' is 'true'.
     private volatile boolean isRegistrationAllowed = false;
         private volatile ObjectName  name;
     private volatile MBeanServer server;
         public Card(int occupiedSlot, String cardType) { ... }

     // This method sets 'isRegistrationAllowed' to 'true', calls
     // server.registerMBean(this, name), and finally sets
     // 'isRegistrationAllowed' back to false.
     //
     \*void registerSelf(MBeanServer server, ObjectName name) throws JMException {\*
         \*isRegistrationAllowed = true;\*
         try {
             this.server = server;
             this.name = name;
             this.name = server.registerMBean(this, name).getObjectName();
         } finally {
             \*isRegistrationAllowed = false;\*
         }
     }
     // This method sets 'isRegistrationAllowed' to 'true',
     // calls server.unregisterMBean(name), and finally sets
     // 'isRegistrationAllowed' back to false.
     //
     \*void unregisterSelf() throws JMException {\*
         \*isRegistrationAllowed = true;\*
         try {
             if (server != null && server.isRegistered(name))
                 server.unregisterMBean(name);
             this.server = null;
             this.name = null;
         } finally {
             \*isRegistrationAllowed = false;\*
         }
     }
            /\*\*
      \* Allows the MBean to perform any operations it needs before being
      \* registered in the MBean server.      \*
      \* \*In this example, the CardMBean will refuse to be registered if
      \* 'isRegistrationAllowed' is false -- which means that Card MBeans
      \* can only be registered by {@link #registerSelf registerSelf()}.\*
      \*
      \* {@code registerSelf()} is a package method which is only called by
      \* {@link Rack}.
      \*/
     \*public final ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {\*
                 if (\*!isRegistrationAllowed\*)
             throw new IllegalStateException("Illegal registration attempt");
                     assert server == this.server && name == this.name;

         return name;
     }

     /\*\*
      \* Allows the MBean to perform any operations needed after having
      \* been registered in the MBean server or after the registration has
      \* failed.
      \* \*The CardMBean does not need to do anything here.\*
      \* @param registrationDone -- Indicates whether or not the MBean
      \* has been successfully registered in the MBean server. The value
      \* 'false' means that the registration has failed.
      \*/
     \*public void postRegister(Boolean registrationDone) {}\*

     /\*\*
      \* Allows the MBean to perform any operations it needs before
      \* being unregistered by the MBean server.
      \* \*The CardMBean will refuse to let itself be unregistered if
      \* 'isRegistrationAllowed' is false, which means that Card MBeans
      \* can only be unregistered by {@link #unregisterSelf unregisterSelf()}.\*
      \*
      \* {@code unregisterSelf()} is a package method which is only called by
      \* {@link Rack}.
      \*
      \* @throws Exception -- This exception will be caught by the MBean
      \* server and rethrown as an MBeanRegistrationException.
      \*/
     \*public final void preDeregister() throws Exception {\*
         if (\*!isRegistrationAllowed\*)
             throw new IllegalStateException("Illegal unregistration attempt");                }

     /\*\*
      \* Allows the MBean to perform any operations needed after having been
      \* unregistered in the MBean server.
      \* \*The CardMBean does not need to do anything here.\*
      \*/
     \*public void postDeregister() { }\*

 }

The logic in preRegister <#preRegisterCard>, preDeregister <#preDeregisterCard>, registerSelf <#registerSelf>, and unregisterSelf <#unregisterSelf> prevents the Card MBean from being registered or unregistered by anybody but the Rack MBean that created it. The registerSelf and unregisterSelf methods are kept package-protected so that only the Rack MBean can call them. The preRegister and preDeregister methods are declared final, so that subclasses cannot change that logic.

Note: If you expected that subclasses of Card would need to perform some additional specific actions when being registered or unregistered, you would introduce a new protected hook specific to that purpose, as described in Martin Fowler's note.

However, using a simple volatile boolean isRegistrationAllowed flag still leaves some room for thread contention. Indeed, when the Rack MBean calls unregisterSelf <#unregisterSelf>, another thread could sneak in and call MBeanServer.unregisterMBean right after the isRegistrationAllowed flag is set, but before unregisterSelf completes.

A better solution would thus be to store the isRegistrationAllowed flag in a ThreadLocal.

The following code snippet shows what would need to change in the Card MBean implementation:

     // initialization of isRegistrationAllowed flag:

     // private volatile boolean isRegistrationAllowed = false;
     //     => replaced by:
     private final static ThreadLocal isRegistrationAllowed =        new ThreadLocal() {
           @Override protected Boolean initialValue() {return false;}
     };
          // in registerSelf / unregisterSelf
     ....
         // isRegistrationAllowed = true;
         //     => replaced by:
         \*isRegistrationAllowed.set(true);\*
         try {
             ...
         } finally {
             // isRegistrationAllowed = false;
             //     => replaced by:
             \*isRegistrationAllowed.set(false);\*
         }
     ....

     // in preRegister / preDeregister
     ...
         // if (!isRegistrationAllowed)
         //     => replaced by:                if (\*!isRegistrationAllowed.get()\*)
             throw new IllegalStateException(...);
     ...
Conclusion

This technical tip has shown how an MBean can make use of the MBeanRegistration interface in order to register and unregister dependent MBeans. You have also seen how such dependent MBeans could be protected in such a way that only their creator could register and unregister them.

The advanced JMX sample for JDK 6 -- $JDK_HOME/sample/jmx/jmx-scandir -- shows many other possibilities for using the MBeanRegistration interface.

About the Author

Daniel Fuchs works on the Sun Microsystems Java DMK, JMX, Java SE Team in Grenoble, France.

\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* Sun Microsystems Developer Playground


Sun Microsystems Developer Playground
Join Dana Nourie November 29 at 9-10 AM PDT in Second Life at the Sun Microsystems Developer Playground to chat about how you can learn the Java platform.


Comments:

Post a Comment:
Comments are closed for this entry.
About

John O'Conner

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today