Using JMX as a Microkernel


When JBoss starts up, one of the first steps performed is to create an MBean server instance (javax.management.MBeanServer). The JMX MBean server in the JBoss architecture plays the role of a microkernel. All other manageable MBean components are plugged into JBoss by registering with the MBean server. In that sense, the kernel is only a framework, and not a source of actual functionality. The functionality is provided by MBeans, and in fact all major JBoss components are manageable MBeans interconnected through the MBean server.

The Startup Process

This section describes the JBoss server startup process. The following steps occur during the JBoss server startup sequence:

1.

The run startup script initiates the boot sequence, using the org.jboss.Main.main(String[]) method entry point.

2.

The Main.main method creates a thread group named jboss and then starts a thread that belongs to this thread group. This thread invokes the Main.boot method.

3.

The Main.boot method processes the Main.main arguments and then creates an org.jboss.system.server.ServerLoader, using the system properties along with any additional properties specified as arguments.

4.

The XML parser libraries, jboss-jmx.jar,concurrent.jar, and extra libraries and classpaths given as arguments are registered with the ServerLoader.

5.

The JBoss server instance is created, using the ServerLoader.load(ClassLoader) method, with the current thread context class loader passed in as the ClassLoader argument. The returned server instance is an implementation of the org.jboss.system.server.Server interface. The creation of the server instance entails the following:

  • A java.net.URLClassLoader is created with the URLs of the JARs and directories registered with the ServerLoader. This URLClassLoader uses the ClassLoader passed in as its parent, and it is pushed as the thread context class loader.

  • The classname of the implementation of the Server interface to use is determined by the jboss.server.type property. This defaults to org.jboss.system.server.ServerImpl.

  • The Server implementation class is loaded and instantiated, using its no-arg constructor. The thread context class loader present on entry into the ServerLoader.load method is restored, and the server instance is returned.

6.

The server instance is initialized with the properties passed to the ServerLoader constructor, using the Server.init(Properties) method.

7.

The server instance is then started using the Server.start() method. The default implementation performs the following tasks:

  • Sets the thread context class loader to the class loader used to load the ServerImpl class.

  • Creates an MBeanServer under the jboss domain, using the MBeanServerFactory.createMBeanServer(String) method.

  • Registers the ServerImpl and ServerConfigImpl MBeans with the MBean server.

  • Initializes the unified class loader repository to contain all JARs in the optional patch directory, as well as the server configuration file conf directory (for example, server/default/conf). For each JAR and directory, an org.jboss.mx.loading.UnifiedClassLoader is created and registered with the unified repository. One of these UnifiedClassLoaders is then set as the thread context class loader. This effectively makes all UnifiedClassLoaders available through the thread context class loader.

  • The org.jboss.system.ServiceController MBean is created. ServiceController manages the JBoss MBean service's life cycle. We will discuss the JBoss MBean services notion in detail later in this chapter, in the section "JBoss MBean Services."

  • The org.jboss.deployment.MainDeployer is created and started. MainDeployer manages deployment dependencies and directing deployments to the correct deployer.

  • The org.jboss.deployment.JARDeployer is created and started. JARDeployer handles the deployment of JARs that are simple library JARs.

  • The org.jboss.deployment.SARDeployer is created and started. SARDeployer handles the deployment of JBoss MBean services.

  • The MainDeployer is invoked to deploy the services defined in the conf/jboss-service.xml of the current server file set.

  • Restores the thread context class loader.

The JBoss server starts out as nothing more than a container for the JMX MBean server, and then it loads its personality based on the services defined in the jboss-service.xml MBean configuration file from the named configuration set passed to the server on the command line. Because MBeans define the functionality of a JBoss server instance, it is important to understand how the core JBoss MBeans are written and how you should integrate your existing services into JBoss by using MBeans. That is the topic of the next section.

JBoss MBean Services

As you have seen, JBoss relies on JMX to load in the MBean services that make up a given server instance's personality. All the bundled functionality provided with the standard JBoss distribution is based on MBeans. The best way to add services to the JBoss server is to write your own JMX MBeans.

There are two classes of MBeans: those that are independent of JBoss services and those that are dependent on JBoss services. MBeans that are independent of JBoss services are the trivial case. You can write them per the JMX specification and add them to a JBoss server by adding an mbean tag to the deploy/user-service.xml file. Writing an MBean that relies on a JBoss service such as naming requires you to follow the JBoss service pattern. The JBoss MBean service pattern consists of a set of life cycle operations that provide state change notifications. The notifications inform an MBean service when it can create, start, stop, and destroy itself. The management of the MBean service life cycle is the responsibility of three JBoss MBeans: SARDeployer, ServiceConfigurator, and ServiceController.

The SARDeployer MBean

JBoss manages the deployment of its MBean services via a custom MBean that loads an XML variation of the standard JMX MLet configuration file. This custom MBean is implemented in the org.jboss.deployment.SARDeployer class. The SARDeployer MBean is loaded when JBoss starts up, as part of the bootstrap process. The SAR acronym stands for service archive.

SARDeployer handles services archives. A service archive can be either a JAR that ends with a .sar suffix and contains a META-INF/jboss-service.xml descriptor or a standalone XML descriptor with a naming pattern that matches *-service.xml. The DTD for the service descriptor is jboss-service_4.0.dtd and is shown in Figure 2.15.

Figure 2.15. The DTD for the MBean service descriptor parsed by SARDeployer.


The following are the elements of the DTD:

  • loader-repository This element specifies the name of the UnifiedLoaderRepository MBean to use for the SAR to provide SAR-level scoping of classes deployed in the SAR. It is a unique JMX ObjectName string. It may also specify an arbitrary configuration by including a loader-repository-config element. The optional loaderRepositoryClass attribute specifies the fully qualified name of the loader repository implementation class. It defaults to org.jboss.mx.loading.HeirachicalLoaderRepository3.

  • loader-repository-config This optional element specifies an arbitrary configuration that may be used to configure the loadRepositoryClass. The optional configParserClass attribute gives the fully qualified name of the org.jboss.mx.loading.LoaderRepositoryFactory.LoaderRepositoryConfigParser implementation to use to parse the loader-repository-config content.

  • local-directory This element specifies a path within the deployment archive that should be copied to the server/<config>/db directory for use by the MBean. The path attribute is the name of an entry within the deployment archive.

  • classpath This element specifies one or more external JARs that should be deployed with the MBean(s). The optional archives attribute specifies a comma-separated list of the JAR names to load, or the * wildcard to signify that all JARs should be loaded. The wildcard works only with file URLs, and HTTP URLs if the web server supports the WebDAV protocol. The codebase attribute specifies the URL from which the JARs specified in the archive attribute should be loaded. If the codebase attribute is a path rather than a URL string, the full URL is built by treating the codebase value as a path relative to the JBoss distribution server/<config> directory. The order of JARs specified in the archives, as well as the ordering across multiple classpath elements, is used as the classpath ordering of the JARs. Therefore, if you have patches or inconsistent versions of classes that require a certain ordering, you should use this feature to ensure the correct ordering. Both the codebase and archives attribute values may reference a system property, using the pattern ${x} to refer to replacement of the x system property.

  • mbean This element specifies an MBean service. The required code attribute gives the fully qualified name of the MBean implementation class. The required name attribute gives the JMX ObjectName of the MBean. The optional xmbean-dd attribute specifies the path to the XMBean resource if this MBean service uses the JBoss XMBean descriptor to define a model MBean management interface.

  • constructor The constructor element defines a non-default constructor to use when instantiating the MBean. The arg element specifies the constructor arguments in the order of the constructor signature. Each arg has a type attribute and a value attribute.

  • attribute Each attribute element specifies a name/value pair of the attribute of the MBean. The name of the attribute is given by the name attribute, and the attribute element body gives the value. The body may be a text representation of the value or an arbitrary element and child elements, if the type of the MBean attribute is org.w3c.dom.Element. For text values, the text is converted to the attribute type by using the JavaBeans java.beans.PropertyEditor mechanism.

    The text value of an attribute may reference a system property x by using the pattern ${x}. In this case, the value of the attribute will be the result of System.getProperty("x"), or null, if no such property exists.

  • server/mbean/depends and server/mbean/depends-list These elements specify a dependency from the MBean, using the element to the MBean(s) named by the depends or depends-list element. Note that the dependency value can be another mbean element that defines a nested mbean.

When the SARDeployer is asked to deploy a service, it performs several steps. Figure 2.16 is a sequence diagram that shows the init through start phases of a service.

  • Methods prefixed with 1.1 These methods correspond to the load and parse of the XML service descriptor.

  • Methods prefixed with 1.2 These methods correspond to processing each classpath element in the service descriptor to create an independent deployment that makes the JAR or directory available through a UnifiedClassLoader registered with the unified loader repository.

  • Methods prefixed with 1.3 These methods correspond to processing each localdirectory element in the service descriptor. This makes a copy of the SAR elements specified in the path attribute to the server/<config>/db directory.

  • Method prefixed with 1.4 These methods process each deployable unit nested in the service. A child deployment is created and added to the service deployment info subdeployment list.

  • Method prefixed with 2.1 These methods mean that the UnifiedClassLoader of the SAR deployment unit is registered with the MBean server so that it can be used for loading the SAR MBeans.

  • Method prefixed with 2.2 These methods mean that for each MBean element in the descriptor, create an instance and initialize its attributes with the values given in the service descriptor. This is done by calling the ServiceController.install method.

  • Method prefixed with 2.4.1 These methods mean that for each MBean instance created, obtain its JMX ObjectName and ask the ServiceController to handle the creating step of the service life cycle. The ServiceController handles the dependencies of the MBean service. Only if the service's dependencies are satisfied is the service's create method invoked.

  • Methods prefixed with 3.1 These methods correspond to the start of each MBean service defined in the service descriptor. For each MBean instance created, obtain its JMX ObjectName and ask the ServiceController to handle the start step of the service life cycle. The ServiceController handles the dependencies of the MBean service. Only if the service's dependencies are satisfied is the service start method invoked.

Figure 2.16. A sequence diagram highlighting the main activities performed by the SARDeployer to start a JBoss MBean service.


The Service Life Cycle Interface

The JMX specification does not define any type of life cycle or dependency management for MBeans. The JBoss ServiceController MBean introduces this notion. A JBoss MBean is an extension of the JMX MBean in that an MBean is expected to decouple creation from the life cycle of its service duties. This is necessary to implement any type of dependency management. For example, if you are writing an MBean that needs a JNDI naming service in order to be able to function, your MBean needs to be told when its dependencies are satisfied. This ranges from difficult to impossible to do if the only life cycle event is the MBean constructor. Therefore, JBoss introduces a service life cycle interface that describes the events a service can use to manage its behavior. The following shows the org.jboss.system.Service interface:

 package org.jboss.system; public interface Service {     public void create() throws Exception;     public void start() throws Exception;     public void stop();     public void destroy(); } 

The ServiceController MBean invokes the methods of the Service interface at the appropriate times of the service life cycle. The following section discusses the methods in more detail.

The ServiceController MBean

JBoss manages dependencies between MBeans via the org.jboss.system.ServiceController custom MBean.

The SARDeployer delegates to the ServiceController when initializing, creating, starting, stopping, and destroying MBean services. Figure 2.17 shows a sequence diagram that highlights interaction between the SARDeployer and ServiceController.

Figure 2.17. The interaction between the SARDeployer and ServiceController to start a service.


The ServiceController MBean has four key methods for the management of the service life cycle: create, start, stop, and destroy.

The create(ObjectName) Method

The create(ObjectName) method is called whenever an event occurs that affects the named service's state. This could be triggered by an explicit invocation by the SARDeployer, a notification of a new class, or another service reaching its created state.

When a service's create method is called, all services on which the service depends have also had their create method invoked. This gives an MBean an opportunity to check that required MBeans or resources exist. A service cannot utilize other MBean services at this point, as most JBoss MBean services do not become fully functional until they have been started via their start method. Because of this, service implementations often do not implement create in favor of just the start method because that is the first point at which the service can be fully functional.

The start(ObjectName) Method

The start(ObjectName) method is called whenever an event occurs that affects the named service's state. This could be triggered by an explicit invocation by the SARDeployer, a notification of a new class, or another service reaching its started state.

When a service's start method is called, all services on which the service depends have also had their start method invoked. Receipt of a start method invocation signals a service to become fully operational because all services on which the service depends have been created and started.

The stop(ObjectName) Method

The stop(ObjectName) method is called whenever an event occurs that affects the named service's state. This could be triggered by an explicit invocation by the SARDeployer, notification of a class removal, or a service on which other services depend reaching its stopped state.

The destroy(ObjectName) Method

The destroy(ObjectName) method is called whenever an event occurs that affects the named service's state. This could be triggered by an explicit invocation by the SARDeployer, notification of a class removal, or a service on which other services depend reaching its destroyed state.

Service implementations often do not implement destroy in favor of simply implementing the stop methodor neither stop nor destroy if the service has no state or resources that need cleanup.

Specifying Service Dependencies

To specify that an MBean service depends on other MBean services, you need to declare the dependencies in the mbean element of the service descriptor. This is done using the depends and depends-list elements. One difference between these two elements relates to the usage of the optional-attribute-name attribute. If you track the ObjectNames of dependencies by using single-valued attributes, you should use the depends element. If you track the ObjectNames of dependencies by using java.util.List-compatible attributes, you would use the depends-list element. If you only want to specify a dependency and don't care to have the associated service ObjectName bound to an attribute of your MBean, then you use whatever element is easiest. The following shows examples of service descriptor fragments that illustrate the usage of the dependency-related elements:

 <mbean code="org.jboss.mq.server.jmx.Topic"        name="jms.topic:service=Topic,name=testTopic">     <!-- Declare a dependency on the "jboss.mq:service=DestinationManager" and          bind this name to the DestinationManager attribute -->     <depends optional-attribute-name="DestinationManager">         jboss.mq:service=DestinationManager     </depends>     <!-- Declare a dependency on the "jboss.mq:service=SecurityManager" and          bind this name to the SecurityManager attribute -->     <depends optional-attribute-name="SecurityManager">         jboss.mq:service=SecurityManager     </depends>     <!-- ... -->     <!-- Declare a dependency on the          "jboss.mq:service=CacheManager" without          any binding of the name to an attribute-->     <depends>jboss.mq:service=CacheManager</depends> </mbean> <mbean code="org.jboss.mq.server.jmx.TopicMgr"        name="jboss.mq.destination:service=TopicMgr">     <!-- Declare a dependency on the given topic destination mbeans and          bind these names to the Topics attribute -->     <depends-list optional-attribute-name="Topics">         <depends-list-element>jms.topic:service=Topic,name=A</depends-list-element>         <depends-list-element>jms.topic:service=Topic,name=B</depends-list-element>         <depends-list-element>jms.topic:service=Topic,name=C</depends-list-element>     </depends-list> </mbean> 

Another difference between the depends and depends-list elements is that the value of the depends element may be a complete MBean service configuration rather than just the ObjectName of the service. Listing 2.13 shows an example from the hsqldb-service.xml descriptor. In this listing the org.jboss.resource.connectionmanager.RARDeployment service configuration is defined using a nested mbean element as the depends element value. This indicates that the org.jboss.resource.connectionmanager.LocalTxConnectionManager MBean depends on this service. The jboss.jca:service=LocalTxDS,name=hsqldbDS ObjectName will be bound to the ManagedConnectionFactory-Name attribute of the LocalTxConnectionManager class.

Listing 2.13. An Example of Using the depends Element to Specify the Complete Configuration of a Depended-on Service
 <mbean code="org.jboss.resource.connectionmanager.LocalTxConnectionManager"        name="jboss.jca:service=LocalTxCM,name=hsqldbDS">     <depends optional-attribute-name="ManagedConnectionFactoryName">         <!--embedded mbean-->         <mbean code="org.jboss.resource.connectionmanager.RARDeployment"                name="jboss.jca:service=LocalTxDS,name=hsqldbDS">             <attribute name="JndiName">DefaultDS</attribute>             <attribute name="ManagedConnectionFactoryProperties">                 <properties>                     <config-property name="ConnectionURL"                                      type="java.lang.String">                         jdbc:hsqldb:hsql://localhost:1476                     </config-property>                     <config-property name="DriverClass" type="java.lang.String">                         org.hsqldb.jdbcDriver                     </config-property>                     <config-property name="UserName" type="java.lang.String">                         sa                     </config-property>                     <config-property name="Password" type="java.lang.String"/>                 </properties>             </attribute>             <!-- ... -->         </mbean>     </depends>     <!-- ... --> </mbean> 

Identifying Unsatisfied Dependencies

The ServiceController MBean supports two operations that can help determine which MBeans are not running due to unsatisfied dependencies. The first operation is listIncompletelyDeployed. This returns a java.util.List of org.jboss.system.ServiceContext objects for the MBean services that are not in the RUNNING state.

The second operation is listWaitingMBeans. This operation returns a java.util.List of the JMX ObjectNames of MBean services that cannot be deployed because the class specified by the code attribute is not available.

Hot Deployment of Components, the URLDeploymentScanner

The URLDeploymentScanner MBean service provides the JBoss hot deployment capability. This service watches one or more URLs for deployable archives and deploys the archives as they appear or change. It also undeploys previously deployed applications if the archive from which the application was deployed is removed. The configurable attributes include the following:

  • URLs This property specifies a comma-separated list of URL strings for the locations that should be watched for changes. Strings that do not correspond to valid URLs are treated as file paths. Relative file paths are resolved against the server home URL (for example, JBOSS_DIST/server/default for the default config file set). If a URL represents a file, the file is deployed and watched for subsequent updates or removal. If a URL ends in /, to represent a directory, the contents of the directory are treated as a collection of deployables and scanned for content that is to be watched for updates or removal. The requirement that a URL end in a / to identify a directory follows the RFC 2518 convention and allows discrimination between collections and directories that are simply unpacked archives.

    The default value for the URLs attribute is deploy/, which means that any SARs, EARs, JARs, WARs, RARs, and so on dropped into the server/<name>/deploy directory will be automatically deployed and watched for updates.

    Examples of URLs attribute values include the following:

    • deploy/ scans ${ jboss.server.url} /deploy/, which is local or remote, depending on the URL used to boot the server.

    • ${jboss.server.home.dir}/deploy/ scans ${jboss.server.home.dir)/deploy, which is always local.

    • file:/var/opt/myapp.ear deploys myapp.ear from a local location.

    • file:/var/opt/apps/ scans the specified directory.

    • http://www.test.com/netboot/myapp.ear deploys myapp.ear from a remote location.

    • http://www.test.com/netboot/apps/ scans the specified remote location using WebDAV. This will work only if the remote HTTP server supports the WebDAV PROPFIND command.

  • ScanPeriod This property specifies the time, in milliseconds, between runs of the scanner thread. The default is 5000 (5 seconds).

  • URLComparator This property specifies the classname of a java.util.Comparator implementation used to specify a deployment ordering for deployments found in a scanned directory. The implementation must be able to compare two java.net.URL objects passed to its compare method. The default setting is the org.jboss.deployment.DeploymentSorter class, which orders based on the deployment URL suffix. The ordering of suffixes is deployer, deployer.xml, sar, rar, ds.xml, service.xml, har, jar, war, wsr, ear, zip, bsh, and last.

    An alternate implementation is the org.jboss.deployment.scanner.PrefixDeploymentSorter class. This orders the URLs based on numeric prefixes. The prefix digits are converted to an int (ignoring leading zeros), and smaller prefixes are ordered ahead of larger numbers. Deployments that do not start with any digits will be deployed after all numbered deployments. Deployments with the same prefix value are further sorted by the DeploymentSorter logic.

  • Filter This property specifies the classname of a java.io.FileFilter implementation that is used to filter the contents of scanned directories. Any file not accepted by this filter will not be deployed. The default is org.jboss.deployment.scanner.DeploymentFilter, which is an implementation that rejects the following patterns:

     #* %* ,* .* _$* *# *$ *% *.BAK *.old *.orig *.rej *.bak *.sh *,v *~ .make.state .nse_de pinfo CVS CVS.admin RCS RCSLOG SCCS TAGS core tags 

  • RecursiveSearch This property indicates whether deploy subdirectories are seen to be holding deployable content. If this attribute is false, deploy subdirectories that do not contain a dot (.) in their name are seen to be unpackaged JARs with nested subdeployments. If this attribute is true, then deploy subdirectories are just groupings of deployable content. The difference between the two views shown is related to the depth-first deployment model JBoss supports. The false setting, which treats directories as unpackaged JARs with nested content, triggers the deployment of the nested content as soon as the JAR directory is deployed. The true setting simply ignores the directory and adds its content to the list of deployable packages and calculates the order based on the previous filter logic. The default is true.

  • Deployer This property specifies the JMX ObjectName string of the MBean that implements the org.jboss.deployment.Deployer interface operations. The default setting is to use the MainDeployer created by the bootstrap startup process.

Writing JBoss MBean Services

Writing a custom MBean service that integrates into the JBoss server requires the use of the org.jboss.system.Service interface pattern if the custom service is dependent on other services. When a custom MBean depends on other MBean services, you cannot perform any service-dependent initialization in any of the javax.management.MBeanRegistration interface methods because JMX has no notion of dependency. Instead, you must manage dependency state by using the Service interface create and/or start methods. You can do this by using any one of the following approaches:

  • Add any of the Service methods that you want called on your MBean to your MBean interface. This allows your MBean implementation to avoid dependencies on JBoss-specific interfaces.

  • Have your MBean interface extend the org.jboss.system.Service interface.

  • Have your MBean interface extend the org.jboss.system.ServiceMBean interface. This is a subinterface of org.jboss.system.Service that adds the getName(), getState(), and getStateString() methods.

Which approach you choose depends on whether you want your code to be coupled to JBoss-specific code. If you don't, then you should use the first approach. If you don't care about dependencies on JBoss classes, the simplest approach is to have your MBean interface extend from org.jboss.system.ServiceMBean and your MBean implementation class extend from the abstract org.jboss.system.ServiceMBeanSupport class. This class implements the org.jboss.system.ServiceMBean interface. ServiceMBeanSupport provides implementations of the create, start, stop, and destroy methods that integrate logging and JBoss service state management tracking. Each method delegates any subclass-specific work to the createService, startService, stopService, and destroyService methods, respectively. When subclassing ServiceMBeanSupport, you should override one or more of the createService, startService, stopService, and destroyService methods, as required.

A Standard MBean Example

This section develops a simple MBean that binds a HashMap into the JBoss JNDI namespace at a location determined by its JndiName attribute to demonstrate what is required to create a custom MBean. Because the MBean uses JNDI, it depends on the JBoss naming service MBean and must use the JBoss MBean service pattern to be notified when the naming service is available.

Version 1 of the classes, shown in Listing 2.14, is based on the service interface method pattern. This version of the interface declares the start and stop methods needed to start up correctly without using any JBoss-specific classes.

Listing 2.14. The JNDIMapMBean Interface and Implementation Based on the Service Interface Method Pattern
 package org.jboss.chap2.ex1; // The JNDIMap MBean interface import javax.naming.NamingException; public interface JNDIMapMBean {     public String getJndiName();     public void setJndiName(String jndiName) throws NamingException;     public void start() throws Exception;     public void stop() throws Exception; } package org.jboss.chap2.ex1; // The JNDIMap MBean implementation import java.util.HashMap; import javax.naming.InitialContext; import javax.naming.Name; import javax.naming.NamingException; import org.jboss.naming.NonSerializableFactory; public class JNDIMap implements JNDIMapMBean {     private String jndiName;     private HashMap contextMap = new HashMap();     private boolean started;     public String getJndiName()     {         return jndiName;     }     public void setJndiName(String jndiName) throws NamingException     {         String oldName = this.jndiName;         this.jndiName = jndiName;         if (started) {             unbind(oldName);             try {                 rebind();             } catch(Exception e) {                 NamingException ne = new NamingException ("Failedto update jndiName");                ne.setRootCause(e);                throw ne;             }        }     }     public void start() throws Exception     {         started = true;         rebind();     }     public void stop()     {         started = false;         unbind(jndiName);     }     private void rebind() throws NamingException     {         InitialContext rootCtx = new InitialContext();         Name fullName = rootCtx.getNameParser("").parse(jndiName);         System.out.println("fullName="+fullName);         NonSerializableFactory.rebind(fullName, contextMap, true);     }     private void unbind(String jndiName)     {         try {             InitialContext rootCtx = new InitialContext();             rootCtx.unbind(jndiName);             NonSerializableFactory.unbind(jndiName);         } catch(NamingException e) {             e.printStackTrace();         }     } } 

Version 2 of the classes, shown in Listing 2.15, uses the JBoss ServiceMBean interface and ServiceMBeanSupport class. In this version, the implementation class extends the ServiceMBeanSupport class and overrides the startService and stopService methods. JNDIMapMBean also implements the abstract getName method to return a descriptive name for the MBean. The JNDIMapMBean interface extends the org.jboss.system.ServiceMBean interface and only declares the setter and getter methods for the JndiName attribute because it inherits the service life cycle methods from ServiceMBean. This is the third approach mentioned at the start of the section "JBoss MBean Services," earlier in this chapter.

Listing 2.15. The JNDIMap MBean Interface and Implementation Based on the ServiceMBean Interface and ServiceMBeanSupport Class
 package org.jboss.chap2.ex2; // The JNDIMap MBean interface import javax.naming.NamingException; public interface JNDIMapMBean extends org.jboss.system.ServiceMBean {     public String getJndiName();     public void setJndiName(String jndiName) throws NamingException; } package org.jboss.chap2.ex2; // The JNDIMap MBean implementation import java.util.HashMap; import javax.naming.InitialContext; import javax.naming.Name; import javax.naming.NamingException; import org.jboss.naming.NonSerializableFactory; public class JNDIMap extends org.jboss.system.ServiceMBeanSupport     implements JNDIMapMBean {     private String jndiName;     private HashMap contextMap = new HashMap();     public String getJndiName()     {         return jndiName;     }     public void setJndiName(String jndiName)         throws NamingException     {         String oldName = this.jndiName;         this.jndiName = jndiName;         if (super.getState() == STARTED) {             unbind(oldName);             try {                 rebind();             } catch(Exception e) {                 NamingException ne = new NamingException ("Failed to update jndiName");                 ne.setRootCause(e);                 throw ne;             }         }     }     public void startService() throws Exception     {         rebind();     }     public void stopService()     {         unbind(jndiName);     }     private void rebind() throws NamingException     {         InitialContext rootCtx = new InitialContext();         Name fullName = rootCtx.getNameParser("").parse(jndiName);         log.info("fullName="+fullName);         NonSerializableFactory.rebind(fullName, contextMap, true);     }     private void unbind(String jndiName)     {         try {             InitialContext rootCtx = new InitialContext();             rootCtx.unbind(jndiName);             NonSerializableFactory.unbind(jndiName);         } catch(NamingException e) {             log.error("Failed to unbind map", e);         }     } } 

The source code for these MBeans, along with the service descriptors, is located in the examples/src/main/org/jboss/chap2/{ex1,ex2} directories.

The jboss-service.xml descriptor for Version 1 is shown here:

 <!-- The SAR META-INF/jboss-service.xml descriptor --> <server>     <mbean code="org.jboss.chap2.ex1.JNDIMap"            name="chap2.ex1:service=JNDIMap">         <attribute name="JndiName">inmemory/maps/MapTest</attribute>         <depends>jboss:service=Naming</depends>     </mbean> </server> 

The JNDIMap MBean binds a HashMap object under the inmemory/maps/MapTest JNDI name, and the client code fragment demonstrates retrieving the HashMap object from the inmemory/maps/MapTest location. The corresponding client code is shown here:

 // Sample lookup code InitialContext ctx = new InitialContext(); HashMap map = (HashMap) ctx.lookup("inmemory/maps/MapTest"); 

XMBean Examples

This section develops a variation of the JNDIMap MBean introduced in the preceding section. This MBean exposes its management metadata by using the JBoss XMBean framework. The core managed component is exactly the same core code from the JNDIMap class, but it does not implement any specific management-related interface. This example illustrates the following capabilities that are not possible with a standard MBean:

  • The ability to add rich descriptions to attributes and operations

  • The ability to expose notification information

  • The ability to add persistence of attributes

  • The ability to add custom interceptors for security and remote access through a typed interface

Version 1: The Annotated JNDIMap XMBean

Let's start with a simple XMBean variation of the standard MBean version of JNDIMap that adds descriptive information about the attributes and operations and their arguments. The following shows the jboss-service.xml descriptor and the jndimap-xmbean1.xml XMBean descriptor (the source can be found in the src/main/org/jboss/chap2/xmbean directory of the book examples):

 <?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE server PUBLIC                      "-//JBoss//DTD MBean Service 3.2//EN"                      "http://www.jboss.org/j2ee/dtd/jboss-service_3_2.dtd"> <server>     <mbean code="org.jboss.chap2.xmbean.JNDIMap"            name="chap2.xmbean:service=JNDIMap"            xmbean-dd="META-INF/jndimap-xmbean.xml">         <attribute name="JndiName">inmemory/maps/MapTest</attribute>         <depends>jboss:service=Naming</depends>     </mbean> </server> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mbean PUBLIC           "-//JBoss//DTD JBOSS XMBEAN 1.0//EN"           "http://www.jboss.org/j2ee/dtd/jboss_xmbean_1_0.dtd"> <mbean>     <description>The JNDIMap XMBean Example Version 1</description>     <descriptors>         <persistence persistPolicy="Never" persistPeriod="10"             persistLocation="data/JNDIMap.data" persistName="JNDIMap"/>         <currencyTimeLimit value="10"/>         <state-action-on-update value="keep-running"/>     </descriptors>     <class>org.jboss.test.jmx.xmbean.JNDIMap</class>     <constructor>         <description>The default constructor</description>         <name>JNDIMap</name>     </constructor>     <!-- Attributes -->     <attribute access="read-write" getMethod="getJndiName" setMethod=?"setJndiName">         <description>             The location in JNDI where the Map we manage will be bound         </description>         <name>JndiName</name>         <type>java.lang.String</type>         <descriptors>             <default value="inmemory/maps/MapTest"/>         </descriptors>     </attribute>     <attribute access="read-write" getMethod="getInitialValues"                setMethod="setInitialValues">         <description>The array of initial values that will be placed into the             map associated with the service. The array is a collection of             key,value pairs with elements[0,2,4,...2n] being the keys and             elements [1,3,5,...,2n+1] the associated values. The             "[Ljava.lang.String;" type signature is the VM representation of the             java.lang.String[] type. </description>         <name>InitialValues</name>         <type>[Ljava.lang.String;</type>         <descriptors>             <default value="key0,value0"/>         </descriptors>     </attribute>     <!-- Operations -->     <operation>         <description>The start lifecycle operation</description>         <name>start</name>     </operation>     <operation>         <description>The stop lifecycle operation</description>         <name>stop</name>     </operation>     <operation impact="ACTION">         <description>Put a value into the map</description>         <name>put</name>         <parameter>             <description>The key the value will be store under</description>             <name>key</name>             <type>java.lang.Object</type>     </parameter>     <parameter>         <description>The value to place into the map</description>         <name>value</name>         <type>java.lang.Object</type>     </parameter>     </operation>     <operation impact="INFO">         <description>Get a value from the map</description>         <name>get</name>         <parameter>             <description>The key to lookup in the map</description>             <name>get</name>             <type>java.lang.Object</type>         </parameter>         <return-type>java.lang.Object</return-type>     </operation>     <!-- Notifications -->     <notification>         <description>The notification sent whenever a value has got into the map             managed by the service</description>         <name>javax.management.Notification</name>         <notification-type>org.jboss.chap2.xmbean.JNDIMap.get</notification-type>     </notification>     <notification>         <description>The notification sent whenever a value is put into the map             managed by the service</description>         <name>javax.management.Notification</name>         <notification-type>org.jboss.chap2.xmbean.JNDIMap.put</notification-type>     </notification> </mbean> 

You can build, deploy, and test the XMBean as follows:

 [examples]$ ant -Dchap=chap2 -Dex=xmbean1 run-example ... run-examplexmbean1:      [copy] Copying 1 file to /tmp/jboss-4.0.1/server/default/deploy      [java] JNDIMap Class: org.jboss.mx.modelmbean.XMBean      [java] JNDIMap Operations:      [java]  + void start()      [java]  + void stop()      [java]  + void put(java.lang.Object chap2.xmbean:service=JNDIMap, java.lang.Object cha p2.xmbean:service=JNDIMap)      [java]  + java.lang.Object get(java.lang.Object chap2.xmbean:service=JNDIMap)      [java]  + java.lang.String getJndiName()      [java]  + void setJndiName(java.lang.String chap2.xmbean:service=JNDIMap)      [java]  + [Ljava.lang.String; getInitialValues()      [java]  + void setInitialValues([Ljava.lang.String; chap2.xmbean: service=JNDIMap)      [java] handleNotification, event: null      [java] key=key0, value=value0      [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean:s ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=3, timeStamp=10986315 27823,message=null,userData=null]      [java] JNDIMap.put(key1, value1) successful      [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean:s ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=4, timeStamp=10986315 27940,message=null,userData=null]      [java] JNDIMap.get(key0): null      [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean:s ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=5, timeStamp=10986315 27985,message=null,userData=null]      [java] JNDIMap.get(key1): value1      [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean:s ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=6, timeStamp=10986315 27999,message=null,userData=null] 

The functionality of this code is largely the same as that of the Standard MBean, with the notable exception of the JMX notifications. A Standard MBean has no way of declaring that it will emit notifications. An XMBean may declare the notifications it emits by using notification elements, as shown in the Version 1 descriptor. We see the notifications from the get and put operations on the test client console output. Note that there is also a jmx.attribute.change notification emitted when the InitialValues attribute is changed. This is because the ModelMBean interface extends the ModelMBeanNotificationBroadcaster, which supports

AttributeChangeNotificationListeners.

The other major difference between the Standard and XMBean versions of JNDIMap is the descriptive metadata. Look at the chap2.xmbean:service=JNDIMap in the JMX Console, and you will see the attributes section, as shown in Figure 2.18.

Figure 2.18. The Version 1 JNDIMapXMBean JMX Console view.


Notice that the JMX Console now displays the full attribute description, as specified in the XMBean descriptor, rather than the MBean Attribute text seen in standard MBean implementations. Scroll down to the operations, and you will also see that these now also have nice descriptions of their function and parameters.

Version 2: Adding Persistence to the JNDIMap XMBean

In Version 2 of the XMBean, you add support for persistence of the XMBean attributes. The updated XMBean deployment descriptor is shown here:

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mbean PUBLIC           "-//JBoss//DTD JBOSS XMBEAN 1.0//EN"           "http://www.jboss.org/j2ee/dtd/jboss_xmbean_1_0.dtd"> <mbean>     <description>The JNDIMap XMBean Example Version 2</description>     <descriptors>         <persistence persistPolicy="OnUpdate" persistPeriod="10"             persistLocation="${jboss.server.data.dir}" persistName="JNDIMap.ser"/>         <currencyTimeLimit value="10"/>         <state-action-on-update value="keep-running"/>         <persistence-manager value="org.jboss.mx.persistence.ObjectStreamPersistenceManager"/>     </descriptors>   <class>org.jboss.test.jmx.xmbean.JNDIMap</class>     <constructor>         <description>The default constructor</description>         <name>JNDIMap</name>     </constructor>     <!-- Attributes -->     <attribute access="read-write" getMethod="getJndiName" setMethod="setJndiName">         <description>             The location in JNDI where the Map we manage will be bound         </description>         <name>JndiName</name>         <type>java.lang.String</type>         <descriptors>             <default value="inmemory/maps/MapTest"/>         </descriptors>     </attribute>     <attribute access="read-write" getMethod="getInitialValues"                setMethod="setInitialValues">         <description>The array of initial values that will be placed into the             map associated with the service. The array is a collection of             key,value pairs with elements[0,2,4,...2n] being the keys and             elements [1,3,5,...,2n+1] the associated values</description>         <name>InitialValues</name>         <type>[Ljava.lang.String;</type>         <descriptors>             <default value="key0,value0"/>         </descriptors>     </attribute>     <!-- Operations -->     <operation>         <description>The start lifecycle operation</description>         <name>start</name>     </operation>     <operation>         <description>The stop lifecycle operation</description>         <name>stop</name>     </operation>     <operation impact="ACTION">         <description>Put a value into the map</description>         <name>put</name>         <parameter>             <description>The key of the value will be stored under</description>             <name>key</name>             <type>java.lang.Object</type>         </parameter>         <parameter>             <description>The value to place into the map</description>             <name>value</name>             <type>java.lang.Object</type>         </parameter>     </operation>     <operation impact="INFO">             <description>Get a value from the map</description>         <name>get</name>         <parameter>             <description>The key to lookup in the map</description>             <name>get</name>             <type>java.lang.Object</type>         </parameter>         <return-type>java.lang.Object</return-type>     </operation>     <!-- Notifications -->     <notification>         <description>The notification sent whenever a value has got into the map             managed by the service</description>         <name>javax.management.Notification</name>         <notification-type>org.jboss.chap2.xmbean.JNDIMap.get</notification-type>     </notification>     <notification>         <description>The notification sent whenever a value is put into the map             managed by the service</description>         <name>javax.management.Notification</name>         <notification-type>org.jboss.chap2.xmbean.JNDIMap.put</notification-type>     </notification> </mbean> 

You build, deploy, and test the Version 2 XMBean as follows:

[View full width]

[examples]$ ant -Dchap=chap2 -Dex=xmbean2 -Djboss.deploy.conf=rmi-adaptor run-example ... run-examplexmbean2: [java] JNDIMap Class: org.jboss.mx.modelmbean.XMBean [java] JNDIMap Operations: [java] + void start() [java] + void stop() [java] + void put(java.lang.Object chap2.xmbean:service=JNDIMap, java.lang.Object chap2.xmbean:service=JNDIMap) [java] + java.lang.Object get(java.lang.Object chap2.xmbean:service=JNDIMap) [java] + java.lang.String getJndiName() [java] + void setJndiName(java.lang.String chap2.xmbean:service=JNDIMap) [java] + [Ljava.lang.String; getInitialValues() [java] + void setInitialValues([Ljava.lang.String; chap2.xmbean: service=JNDIMap) [java] handleNotification, event: null [java] key=key10, value=value10 [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean :s ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=7, timeStamp=10986326 93716,message=null,userData=null] [java] JNDIMap.put(key1, value1) successful [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean :s ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=8, timeStamp=10986326 93857,message=null,userData=null] [java] JNDIMap.get(key0): null [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean :s ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=9, timeStamp=10986326 93896,message=null,userData=null] [java] JNDIMap.get(key1): value1 [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean :s ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=10, timeStamp=1098632 693925,message=null,userData=null]

There is nothing manifestly different about this version of the XMBean at this point because you have done nothing to test that changes to attribute value are actually persisted. You perform that test by running the example xmbean2a several times:

 [examples] ant -Dchap=chap2 -Dex=xmbean2a run-example ...      [java] InitialValues.length=2      [java] key=key10, value=value10 [examples] ant -Dchap=chap2 -Dex=xmbean2a run-example ...      [java] InitialValues.length=4      [java] key=key10, value=value10      [java] key=key2, value=value2 [examples] ant -Dchap=chap2 -Dex=xmbean2a run-example ...      [java] InitialValues.length=6      [java] key=key10, value=value10      [java] key=key2, value=value2                     [java] key=key3, value=value3 

The org.jboss.chap2.xmbean.TestXMBeanRestart used in this example obtains the current InitialValues attribute setting and then adds another key/value pair to it. The client code is shown here:

 package org.jboss.chap2.xmbean; import javax.management.Attribute; import javax.management.ObjectName; import javax.naming.InitialContext; import org.jboss.jmx.adaptor.rmi.RMIAdaptor; /**  *  A client that demonstrates the persistence of the xmbean  *  attributes. Every time it runs it looks up the InitialValues  *  attribute, prints it out and then adds a new key/value to the  *  list.  *  *  @author Scott.Stark@jboss.org  *  @version $Revision: 1.5 $  */ public class TestXMBeanRestart {     /**      * @param args the command line arguments      */     public static void main(String[] args) throws Exception     {         InitialContext ic = new InitialContext();         RMIAdaptor server = (RMIAdaptor) ic.lookup("jmx/rmi/RMIAdaptor");         // Get the InitialValues attribute         ObjectName name = new ObjectName("chap2.xmbean:service=JNDIMap");         String[] initialValues = (String[])             server.getAttribute(name, "InitialValues");         System.out.println("InitialValues.length="+initialValues.length);         int length = initialValues.length;         for (int n = 0; n < length; n += 2) {             String key = initialValues[n];             String value = initialValues[n+1];             System.out.println("key="+key+", value="+value);         }         // Add a new key/value pair         String[] newInitialValues = new String[length+2];         System.arraycopy(initialValues, 0, newInitialValues,                          0, length);         newInitialValues[length] = "key"+(length/2+1);         newInitialValues[length+1] = "value"+(length/2+1);         Attribute ivalues = new             Attribute("InitialValues", newInitialValues);         server.setAttribute(name, ivalues);     } } 

At this point, you might even shut down the JBoss server, restart it, and then rerun the initial example to see if the changes are persisted across server restarts:

 [examples]$ ant -Dchap=chap2 -Dex=xmbean2 run-example ... run-examplexmbean2:      [java] JNDIMap Class: org.jboss.mx.modelmbean.XMBean      [java] JNDIMap Operations:      [java]  + void start()      [java]  + void stop()      [java]  + void put(java.lang.Object chap2.xmbean:service=JNDIMap, java.lang.Object chap2.xmbean:service=JNDIMap)      [java]  + java.lang.Object get(java.lang.Object chap2.xmbean:service=JNDIMap)      [java]  + java.lang.String getJndiName()      [java]  + void setJndiName(java.lang.String chap2.xmbean:service=JNDIMap)      [java]  + [Ljava.lang.String; getInitialValues()      [java]  + void setInitialValues([Ljava.lang.String; chap2.xmbean: service=JNDIMap)      [java] handleNotification, event: null      [java] key=key10, value=value10      [java] key=key2, value=value2      [java] key=key3, value=value3      [java] key=key4, value=value4      [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean:service=JNDIMap,type= org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=3,timeStamp=10986336 64712,message=null,userData=null]      [java] JNDIMap.put(key1, value1) successful      [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean:service=JNDIMap, type=org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=4, timeStamp=10986336 64821,message=null,userData=null]      [java] JNDIMap.get(key0): null      [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean:service=JNDIMap,type= org.jboss.chap2.xmbean.JNDIMap.get,sequenceNumber=5,timeStamp=10986336 64860,message=null,userData=null]      [java] JNDIMap.get(key1): value1      [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean:service=JNDIMap,type= org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=6,timeStamp=10986336 64877,message=null,userData=null]      [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean:service=JNDIMap,type= org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=7,timeStamp=10986336 64895,message=null,userData=null]      [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean:service=JNDIMap,type= org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=8,timeStamp=10986336 64899,message=null,userData=null]      [java] handleNotification, event: javax.management.Notification [source=chap2.xmbean:service=JNDIMap,type= org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=9,timeStamp=10986336 65614,message=null,userData=null] 

You can see that the last InitialValues attribute setting is in fact visible.

Version 3: Adding Security and Remote Access to the JNDIMap XMBean

This last example version (Version 3) of the JNDIMap XMBean demonstrates customization of the server interceptor stack as well as the exposing of a subset of the XMBean management interface via a typed proxy to a remote client using RMI/JRMP. On the server side, you will add a simple security interceptor that allows access to attributes or operations only by a user specified in the interceptor configuration. You will also use another custom interceptor to implement the MBean detached invoker pattern described later in this chapter, in the section "Remote Access to Services, Detached Invokers." Implementing this pattern in an invoker rather than the XMBean demonstrates how to introduce a remote access aspect without having to modify the existing JNDIMap implementation.

You use the JRMPProxyFactory service to expose the ClientInterface to remote clients:

 public interface ClientInterface {     public String[] getInitialValues();     public void setInitialValues(String[] keyValuePairs);     public Object get(Object key);     public void put(Object key, Object value); } 

The test client will obtain the ClientInterface proxy from JNDI and interact with the XMBean through RMI-style calls instead of the RMIAdaptor and MBean Server-style used previously:

 package org.jboss.chap2.xmbean; import javax.naming.InitialContext; import org.jboss.security.SecurityAssociation; import org.jboss.security.SimplePrincipal; /**  *  A client that accesses an XMBean through its RMI interface  *  @author Scott.Stark@jboss.org  *  @version $Revision: 1.5 $  */ public class TestXMBean3 {     /**      * @param args the command line arguments      */     public static void main(String[] args) throws Exception     {         InitialContext ic = new InitialContext();         ClientInterface xmbean = (ClientInterface)             ic.lookup("secure-xmbean/ClientInterface");         // This call should fail because we have not set a security context         try {             String[] tmp = xmbean.getInitialValues();             throw new IllegalStateException("Was able to call getInitialValues");         } catch(Exception e) {             System.out.println("Called to getInitialValues failed as expected: "                                + e.getMessage());         }         // Set a security context using the SecurityAssociation         SecurityAssociation.setPrincipal(new SimplePrincipal("admin"));         // Get the InitialValues attribute         String[] initialValues = xmbean.getInitialValues();         for(int n = 0; n < initialValues.length; n += 2) {             String key = initialValues[n];             String value = initialValues[n+1];             System.out.println("key="+key+", value="+value);         }         // Invoke the put(Object, Object) op         xmbean.put("key1", "value1");         System.out.println("JNDIMap.put(key1,                         value1) successful");         Object result0 = xmbean.get("key0");         System.out.println("JNDIMap.get(key0): "+result0);         Object result1 = xmbean.get("key1");         System.out.println("JNDIMap.get(key1): "+result1);         // Change the InitialValues         initialValues[0] += ".1";         initialValues[1] += ".2";         xmbean.setInitialValues(initialValues);         initialValues = xmbean.getInitialValues();         for(int n = 0; n < initialValues.length; n += 2) {             String key = initialValues[n];             String value = initialValues[n+1];             System.out.println("key="+key+", value="+value);         }     } } 

The deployment descriptor is shown here:

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mbean PUBLIC           "-//JBoss//DTD JBOSS XMBEAN 1.0//EN"           "http://www.jboss.org/j2ee/dtd/jboss_xmbean_1_0.dtd"           [<!ATTLIST interceptor adminName CDATA #IMPLIED>]> <mbean>     <description>The JNDIMap XMBean Example Version 3</description>     <descriptors>         <interceptors>             <interceptor code="org.jboss.chap2.xmbean.ServerSecurityInterceptor"                          adminName="admin"/>             <interceptor code="org.jboss.chap2.xmbean.InvokerInterceptor"/>             <interceptor code="org.jboss.mx.interceptor.PersistenceInterceptor2"/>             <interceptor code="org.jboss.mx.interceptor.ModelMBeanInterceptor"/>             <interceptor code="org.jboss.mx.interceptor.ObjectReferenceInterceptor"/>         </interceptors>         <persistence persistPolicy="Never"/>         <currencyTimeLimit value="10"/>         <state-action-on-update value="keep-running"/>     </descriptors>     <class>org.jboss.test.jmx.xmbean.JNDIMap</class>     <constructor>         <description>The default constructor</description>         <name>JNDIMap</name>     </constructor>     <!-- Attributes -->     <attribute access="read-write" getMethod="getJndiName" setMethod="setJndiName">         <description>             The location in JNDI where the Map we manage will be bound         </description>         <name>JndiName</name>         <type>java.lang.String</type>         <descriptors>             <default value="inmemory/maps/MapTest"/>         </descriptors>     </attribute>     <attribute access="read-write" getMethod="getInitialValues"                setMethod="setInitialValues">         <description>The array of initial values that will be placed into the             map associated with the service. The array is a collection of             key,value pairs with elements[0,2,4,...2n] being the keys and             elements [1,3,5,...,2n+1] the associated values</description>         <name>InitialValues</name>         <type>[Ljava.lang.String;</type>         <descriptors>             <default value="key0,value0"/>         </descriptors>     </attribute>     <!-- Operations -->     <operation>         <description>The start lifecycle operation</description>         <name>start</name>     </operation>     <operation>         <description>The stop lifecycle operation</description>         <name>stop</name>     </operation>     <operation impact="ACTION">         <description>Put a value into the map</description>         <name>put</name>         <parameter>             <description>The key of the value will be stored under</description>             <name>key</name>             <type>java.lang.Object</type>         </parameter>         <parameter>             <description>The value to place into the map</description>             <name>value</name>             <type>java.lang.Object</type>         </parameter>     </operation>     <operation impact="INFO">         <description>Get a value from the map</description>         <name>get</name>         <parameter>             <description>The key to lookup in the map</description>             <name>get</name>             <type>java.lang.Object</type>         </parameter>         <return-type>java.lang.Object</return-type>     </operation> </mbean> 

The addition over the previous versions of the JNDIMap XMBean is the interceptors element. It defines the interceptor stack through which all MBean attribute access and operations pass. The first two interceptors, org.jboss.chap2.xmbean.ServerSecurityInterceptor and org.jboss.chap2.xmbean.InvokerInterceptor, are the sample custom interceptors. The remaining three interceptors are the standard ModelMBean interceptors. Because you have a persistence policy of Never, you could in fact remove the standard org.jboss.mx. interceptor.PersistenceInterceptor2. The JMX interceptors are an ordered chain of filters. The standard base class of an interceptor is shown here:

 package org.jboss.mx.interceptor; import javax.management.MBeanInfo; import org.jboss.mx.server.MBeanInvoker; /**  * Base class for all interceptors.  *  * @see org.jboss.mx.interceptor.StandardMBeanInterceptor  * @see org.jboss.mx.interceptor.LogInterceptor  *  * @author <a href="mailto:juha@jboss.org">Juha Lindfors</a>.  * @version $Revision: 1.5 $  *  */ public class AbstractInterceptor implements Interceptor {     // Attributes ----------------------------------------------------     protected Interceptor next = null;     protected String name = null;     protected MBeanInfo info;     protected MBeanInvoker invoker;     // Constructors --------------------------------------------------     public AbstractInterceptor()     {         this(null);     }     public AbstractInterceptor(String name)     {         this.name = name;     }     public AbstractInterceptor(MBeanInfo info,                                MBeanInvoker invoker)     {         this.name = getClass().getName();         this.info = info;         this.invoker = invoker;     }     // Public --------------------------------------------------------     public Object invoke(Invocation invocation)         throws InvocationException     {         return getNext().invoke(invocation);     }     public Interceptor getNext()     {         return next;     }     public Interceptor setNext(Interceptor interceptor)     {         this.next = interceptor;         return interceptor;     } } 

The custom interceptors for the Version 3 XMBean example are ServerSecurityInterceptor and the InvokerInterceptor. The ServerSecurityInterceptor intercepts invoke operations and validates that the Invocation context includes an admin principal:

 package org.jboss.chap2.xmbean; import java.security.Principal; import org.jboss.logging.Logger; import org.jboss.mx.interceptor.AbstractInterceptor; import org.jboss.mx.interceptor.Invocation; import org.jboss.mx.interceptor.InvocationException; import org.jboss.security.SimplePrincipal; /**  * A simple security interceptor example that restricts access to a  * single principal  *  * @author Scott.Stark@jboss.org  * @version $Revision: 1.5 $  */ public class ServerSecurityInterceptor extends AbstractInterceptor {     private static Logger log = Logger.getLogger(ServerSecurityInterceptor.class);     private SimplePrincipal admin = new SimplePrincipal("admin");     public String getAdminName()     {         return admin.getName();     }     public void setAdminName(String name)     {         admin = new SimplePrincipal(name);     }     public Object invoke(Invocation invocation)         throws InvocationException     {         String opName = invocation.getName();         // If this is not the invoke(Invocation) op just pass it along         if (opName.equals("invoke") == false) {             return getNext().invoke(invocation);         }         Object[] args = invocation.getArgs();         org.jboss.invocation.Invocation invokeInfo =             (org.jboss.invocation.Invocation) args[0];         Principal caller = invokeInfo.getPrincipal();         log.info("invoke, opName="+opName+", caller="+caller);         // Only the admin caller is allowed access         if (caller == null || caller.equals(admin) == false) {             throw new InvocationException(new SecurityException("Caller=" +                                                        caller +                                                        " is not allowed access"));         }         return getNext().invoke(invocation);     } } 

The InvokerInterceptor implements the detached invoker pattern. (This is discussed in detail later in this chapter, in the section "Remote Access to Services, Detached Invokers.").

 package org.jboss.chap2.xmbean; import java.lang.reflect.Method; import java.util.HashMap; import javax.management.Descriptor; import javax.management.MBeanInfo; import org.jboss.logging.Logger; import org.jboss.mx.interceptor.AbstractInterceptor; import org.jboss.mx.interceptor.Invocation; import org.jboss.mx.interceptor.InvocationException; import org.jboss.mx.server.MBeanInvoker; import org.jboss.invocation.MarshalledInvocation; /** An interceptor that handles the  *  * @author Scott.Stark@jboss.org  * @version $Revision: 1.5 $  */ public class InvokerInterceptor     extends AbstractInterceptor {     private static Logger log = Logger.getLogger(InvokerInterceptor.class);     private Class exposedInterface = ClientInterface.class;     private HashMap methodMap = new HashMap();     private HashMap invokeMap = new HashMap();     public InvokerInterceptor(MBeanInfo info,                               MBeanInvoker invoker)     {         super(info, invoker);         try {             Descriptor[] descriptors = invoker.getDescriptors();             Object resource = invoker.getResource();             Class[] getInitialValuesSig = {};             Method getInitialValues =                 exposedInterface.getDeclaredMethod("getInitialValues",                                                    getInitialValuesSig);             Long hash = new Long(MarshalledInvocation.calculateHash (getInitialValues));             InvocationInfo invokeInfo =                 new InvocationInfo("InitialValues",                                    Invocation.ATTRIBUTE,                                    Invocation.READ, getInitialValuesSig,                                    descriptors, resource);             methodMap.put(hash, getInitialValues);             invokeMap.put(getInitialValues, invokeInfo);             log.debug("getInitialValues hash:"+hash);             Class[] setInitialValuesSig = {String[].class};             Method setInitialValues =                 exposedInterface.getDeclaredMethod("setInitialValues",                                                    setInitialValuesSig);             hash = new Long(MarshalledInvocation.calculateHash(setInitialValues));             invokeInfo = new InvocationInfo("InitialValues",                                             Invocation.ATTRIBUTE,                                             Invocation.WRITE,                                             setInitialValuesSig,                                             descriptors, resource);             methodMap.put(hash, setInitialValues);             invokeMap.put(setInitialValues, invokeInfo);             log.debug("setInitialValues hash:"+hash);             Class[] getSig = {Object.class};             Method get = exposedInterface.getDeclaredMethod("get",                                                             getSig);             hash = new Long(MarshalledInvocation.calculateHash(get));             invokeInfo = new InvocationInfo("get",                                             Invocation.OPERATION,                                             Invocation.READ, getSig,                                             descriptors, resource);             methodMap.put(hash, get);             invokeMap.put(get, invokeInfo);             log.debug("get hash:"+hash);             Class[] putSig = {Object.class, Object.class};             Method put = exposedInterface.getDeclaredMethod("put",                                                             putSig);             hash = new Long(MarshalledInvocation.calculateHash(put));             invokeInfo = new InvocationInfo("put",                                             Invocation.OPERATION,                                             Invocation.WRITE, putSig,                                             descriptors, resource);             methodMap.put(hash, put);             invokeMap.put(put, invokeInfo);             log.debug("putt hash:"+hash);         } catch(Exception e) {             log.error("Failed to init InvokerInterceptor", e);         }     }     public Object invoke(Invocation invocation)         throws InvocationException     {         String opName = invocation.getName();         Object[] args = invocation.getArgs();         Object returnValue = null;         if (opName.equals("invoke") == true) {             org.jboss.invocation.Invocation invokeInfo =                 (org.jboss.invocation.Invocation) args[0];             // Set the method hash to Method mapping             if (invokeInfo instanceof MarshalledInvocation) {                 MarshalledInvocation mi = (MarshalledInvocation) invokeInfo;                 mi.setMethodMap(methodMap);             }                  // Invoke the exposedInterface method via reflection if             // this is an invoke             Method method = invokeInfo.getMethod();             Object[] methodArgs = invokeInfo.getArguments();             InvocationInfo info = (InvocationInfo) invokeMap.get(method);             Invocation methodInvocation = info.getInvocation(methodArgs);             returnValue = getNext().invoke(methodInvocation);         } else {             returnValue = getNext().invoke(invocation);         }         return returnValue;     }     /**      * A class that holds the ClientInterface method info needed to build      * the JMX Invocation to pass down the interceptor stack.      */     private class InvocationInfo     {         private int type;         private int impact;         private String name;         private String[] signature;         private Descriptor[] descriptors;         private Object resource;         InvocationInfo(String name, int type, int impact,                        Class[] signature, Descriptor[] descriptors,                        Object resource)         {             this.name = name;             this.type = type;             this.impact = impact;             this.descriptors = descriptors;             this.resource = resource;             this.signature = new String[signature.length];             for(int s = 0; s < signature.length; s ++) {                 this.signature[s] = signature[s].getName();             }         }         Invocation getInvocation(Object[] args)         {             return new Invocation(name, type, impact, args, signature,                                   descriptors, resource);         }     } } 

The deployment descriptor should include the interceptor stack:

 <?xml version='1.0' encoding='UTF-8' ?> <server>     <mbean code="org.jboss.chap2.xmbean.JNDIMap"         name="chap2.xmbean:service=JNDIMap,version=3"         xmbean-dd="META-INF/jndimap-xmbean3.xml">         <attribute name="JndiName">inmemory/maps/MapTest</attribute>         <depends>jboss:service=Naming</depends>     </mbean>     <!-- The JRMP invoker proxy configuration for                         the naming service -->     <mbean code="org.jboss.invocation.jrmp.server.JRMPProxyFactory"            name="jboss.test:service=proxyFactory,type=jrmp,target=JNDIMap">         <!-- Use the standard JRMPInvoker from                         conf/jboss-service.xml -->        <attribute name="InvokerName">jboss:service=invoker,type=jrmp</attribute>        <attribute name="TargetName">chap2.xmbean:service=JNDIMap,version=3</attribute>        <attribute name="JndiName">secure-xmbean/ClientInterface</attribute>        <attribute name="ExportedInterface">            org.jboss.chap2.xmbean.ClientInterface        </attribute>        <attribute name="ClientInterceptors">            <iterceptors>                <interceptor>org.jboss.proxy.ClientMethodInterceptor</interceptor>                <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor>                <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>            </iterceptors>        </attribute>        <depends>jboss:service=invoker,type=jrmp</depends>        <depends>chap2.xmbean:service=JNDIMap,version=3</depends>     </mbean> </server> 

The following shows the results of running this example:

 [examples] ant -Dchap=chap2 -Dex=xmbean3 config ... config:      [echo] Preparing rmi-adaptor configuration fileset      [copy] Copying 60 files to /tmp/jboss-3.2.6/server/rmi-adaptor    [delete] Deleting directory /tmp/jboss-3.2.6/server/rmi-adaptor/deploy/jmx-invoker-adap tor-server.sar    [delete] Deleting directory /tmp/jboss-3.2.6/server/rmi-adaptor/deploy/management [examples]$ ant -Dchap=chap2 -Dex=xmbean3 run-example ... run-examplexmbean3:      [java] Called to getInitialValues failed as expected: Caller=null is not allowed access      [java] key=key0, value=value0      [java] JNDIMap.put(key1, value1) successful      [java] JNDIMap.get(key0): null      [java] JNDIMap.get(key1): value1      [java] key=key0.1, value=value0.2 [examples]$ ant -Dchap=chap2 -Dex=xmbean3 run-example ... run-examplexmbean3:      [java] Called to getInitialValues failed as expected: Caller=null is not allowed access      [java] key=key0.1, value=value0.2      [java] JNDIMap.put(key1, value1) successful      [java] JNDIMap.get(key0): null      [java] JNDIMap.get(key1): value1      [java] key=key0.1.1, value=value0.2.2 

Deployment Ordering and Dependencies

You have seen how to manage dependencies by using the service descriptor depends and depends-list tags. The deployment ordering supported by the deployment scanners provides a coarse-grained dependency management in that there is an order to deployments. If dependencies are consistent with the deployment packages, then this is a simpler mechanism than having to enumerate the explicit MBean-to-MBean dependencies. By writing your own filters, you can change the coarse-grained ordering performed by the deployment scanner.

When a component archive is deployed, its nested deployment units are processed in a depth-first ordering. Structuring of components into an archive hierarchy is yet another way to manage deployment ordering. You need to explicitly state your MBean dependencies if your packaging structure does not happen to resolve the dependencies.

Let's consider an example of a component deployment that consists of an MBean that uses an EJB. Here is the structure of the sample EAR:

 output/chap2/chap2-ex3.ear +- META-INF/MANIFEST.MF +- META-INF/jboss-app.xml +- chap2-ex3.jar (archive) [EJB jar] | +- META-INF/MANIFEST.MF | +- META-INF/ejb-jar.xml | +- org/jboss/chap2/ex3/EchoBean.class | +- org/jboss/chap2/ex3/EchoLocal.class | +- org/jboss/chap2/ex3/EchoLocalHome.class +- chap2-ex3.sar (archive) [MBean sar] | +- META-INF/MANIFEST.MF | +- META-INF/jboss-service.xml | +- org/jboss/chap2/ex3/EjbMBeanAdaptor.class +- META-INF/application.xml 

The EAR contains chap2-ex3.jar and chap2-ex3.sar. chap2-ex3.jar is the EJB archive, and chap2-ex3.sar is the MBean service archive. The service is implemented here as a Dynamic MBean to provide an illustration of the use of Dynamic MBeans:

 package org.jboss.chap2.ex3; import java.lang.reflect.Method; import javax.ejb.CreateException; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.AttributeNotFoundException; import javax.management.DynamicMBean; import javax.management.InvalidAttributeValueException; import javax.management.JMRuntimeException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanConstructorInfo; import javax.management.MBeanInfo; import javax.management.MBeanNotificationInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanException; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.naming.InitialContext; import javax.naming.NamingException; import org.jboss.system.ServiceMBeanSupport; /**  * An example of a DynamicMBean that exposes select attributes and  * operations of an EJB as an MBean.  * @author Scott.Stark@jboss.org  * @version $Revision: 1.5 $  */ public class EjbMBeanAdaptor extends ServiceMBeanSupport     implements DynamicMBean  {     private String helloPrefix;     private String ejbJndiName;     private EchoLocalHome home;     /** These are the mbean attributes we expose     */ private MBeanAttributeInfo[] attributes = {     new MBeanAttributeInfo("HelloPrefix", "java.lang.String",                      "The prefix message to append to the session echo reply",                      true, // isReadable                      true, // isWritable                      false), // isIs     new MBeanAttributeInfo("EjbJndiName", "java.lang.String",                      "The JNDI name of the session bean local home",                      true, // isReadable                      true, // isWritable                      false) // isIs }; /**  * These are the mbean operations we expose  */ private MBeanOperationInfo[] operations; /**  * We override this method to set up our echo operation info. It  * could also be done in a ctor.  */ public ObjectName preRegister(MBeanServer server,                               ObjectName name)     throws Exception {     log.info("preRegister notification seen");     operations = new MBeanOperationInfo[5];     Class thisClass = getClass();     Class[] parameterTypes = {String.class};     Method echoMethod =         thisClass.getMethod("echo", parameterTypes);     String desc = "The echo op invokes the session bean echo method and"         + " returns its value prefixed with the helloPrefix attribute value";     operations[0] = new MBeanOperationInfo(desc, echoMethod);     // Add the Service interface operations from our super class     parameterTypes = new Class[0];     Method createMethod =         thisClass.getMethod("create", parameterTypes);     operations[1] = new MBeanOperationInfo("The             JBoss Service.create", createMethod);     Method startMethod =             thisClass.getMethod("start", parameterTypes);         operations[2] = new MBeanOperationInfo("The                 JBoss Service.start", startMethod);         Method stopMethod =             thisClass.getMethod("stop", parameterTypes);         operations[3] = new MBeanOperationInfo("The                 JBoss Service.stop", startMethod);         Method destroyMethod =              thisClass.getMethod("destroy", parameterTypes);         operations[4] = new MBeanOperationInfo("The                 JBoss Service.destroy", startMethod);         return name;     }     // --- Begin ServiceMBeanSupport overides     protected void createService() throws Exception     {         log.info("Notified of create state");     }     protected void startService() throws Exception     {         log.info("Notified of start state");         InitialContext ctx = new InitialContext();         home = (EchoLocalHome) ctx.lookup(ejbJndiName);     }     protected void stopService()     {         log.info("Notified of stop state");     }     // --- End ServiceMBeanSupport overides     public String getHelloPrefix()     {         return helloPrefix;     }     public void setHelloPrefix(String helloPrefix)     {         this.helloPrefix = helloPrefix;     }        public String getEjbJndiName()     {         return ejbJndiName;     }     public void setEjbJndiName(String ejbJndiName)     {         this.ejbJndiName = ejbJndiName;     }     public String echo(String arg)         throws CreateException, NamingException     {         log.debug("Lookup EchoLocalHome@"+ejbJndiName);         EchoLocal bean = home.create();         String echo = helloPrefix + bean.echo(arg);         return echo;     }     // --- Begin DynamicMBean interface methods     /**      * Returns the management interface that describes this dynamic      * resource. It is the responsibility of the implementation to      * make sure the description is accurate.      *      * @return the management interface descriptor.      */     public MBeanInfo getMBeanInfo()     {         String classname = getClass().getName();         String description = "This is an MBean that uses a session bean in the"             + " implementation of its echo operation.";         MBeanInfo[] constructors = null;         MBeanNotificationInfo[] notifications = null;         MBeanInfo mbeanInfo = new MBeanInfo(classname,                                             description, attributes,                                             constructors, operations,                                             notifications);         // Log when this is called so we know when in the         lifecycle this is used             Throwable trace = new Throwable("getMBeanInfo trace");         log.info("Don't panic, just a stack                 trace", trace);         return mbeanInfo;     }     /**      * Returns the value of the attribute with the name matching the      * passed string.      *      * @param attribute the name of the attribute.      * @return the value of the attribute.      * @exception AttributeNotFoundException when there is no such      * attribute.      * @exception MBeanException wraps any error thrown by the      * resource when      * getting the attribute.      * @exception ReflectionException wraps any error invoking the      * resource.      */     public Object getAttribute(String attribute)         throws AttributeNotFoundException,                MBeanException,                ReflectionException      {         Object value = null;         if (attribute.equals("HelloPrefix")) {             value = getHelloPrefix();         } else if(attribute.equals("EjbJndiName")) {             value = getEjbJndiName();         } else {             throw new AttributeNotFoundException("Unknown                 attribute("+attribute+") requested");         }         return value;     }     /**      * Returns the values of the attributes with names matching the      * passed string array.      *      * @param attributes the names of the attribute.      * @return an {@link AttributeList AttributeList} of name      * and value pairs.      */     public AttributeList getAttributes(String[] attributes)     {         AttributeList values = new AttributeList();         for (int a = 0; a < attributes.length; a++) {             String name = attributes[a];             try {                 Object value = getAttribute(name);                 Attribute attr = new Attribute(name, value);                 values.add(attr);             } catch(Exception e) {                 log.error("Failed to find attribute: "+name, e);             }         }         return values;     }     /**      * Sets the value of an attribute. The attribute and new value      * are passed in the name value pair {@link Attribute      * Attribute}.       *      * @see javax.management.Attribute       *      * @param attribute the name and new value of the attribute.      * @exception AttributeNotFoundException when there is no such      * attribute.      * @exception InvalidAttributeValueException when the new value      * cannot be converted to the type of the attribute.      * @exception MBeanException wraps any error thrown by the      * resource when setting the new value.      * @exception ReflectionException wraps any error invoking the      * resource.      */     public void setAttribute(Attribute attribute)         throws AttributeNotFoundException,                InvalidAttributeValueException,                MBeanException,                ReflectionException     {         String name = attribute.getName();         if (name.equals("HelloPrefix")) {             String value = attribute.getValue().toString();             setHelloPrefix(value);         } else if(ename.equals("EjbJndiName")) {             String value = attribute.getValue().toString();             setEjbJndiName(value);         } else {             throw new AttributeNotFoundException ("Unknown attribute("+name+") requested");         }     }     /**      * Sets the values of the attributes passed as an      * {@link AttributeList AttributeList} of name and new      * value pairs.      *      * @param attributes the name and new value pairs.      * @return an {@link AttributeList AttributeList} of name and      * value pairs that were actually set.      */     public AttributeList setAttributes(AttributeList attributes)     {         AttributeList setAttributes = new AttributeList();         for(int a = 0; a < attributes.size(); a++) {             Attribute attr = (Attribute) attributes.get(a);             try {                 setAttribute(attr);                 setAttributes.add(attr);             } catch(Exception ignore) {             }         }         return setAttributes;     }     /**      * Invokes a resource operation.      *      * @param actionName the name of the operation to perform.      * @param params the parameters to pass to the operation.      * @param signature the signatures of the parameters.      * @return the result of the operation.      * @exception MBeanException wraps any error thrown by the      * resource when performing the operation.      * @exception ReflectionException wraps any error invoking the      * resource.      */     public Object invoke(String actionName, Object[] params,                          String[] signature)         throws MBeanException,                ReflectionException     {         Object rtnValue = null;         log.debug("Begin invoke, actionName="+actionName);         try {             if (actionName.equals("echo")) {                 String arg = (String) params[0];                 rtnValue = echo(arg);                 log.debug("Result: "+rtnValue);             } else if (actionName.equals("create")) {                 super.create();             } else if (actionName.equals("start")) {                 super.start();             } else if (actionName.equals("stop")) {                 super.stop();             } else if (actionName.equals("destroy")) {                 super.destroy();             } else {                 throw new JMRuntimeException("Invalid state,                 don't know about op="+actionName);             }         } catch(Exception e) {             throw new ReflectionException(e, "echo failed");         }         log.debug("End invoke, actionName="+actionName);         return rtnValue;     }     // --- End DynamicMBean interface methods } 

Believe it or not, this is a very trivial MBean. The vast majority of the code is here to provide the MBean metadata and handle the callbacks from the MBean server. This is required because a Dynamic MBean is free to expose whatever management interface it wants. A Dynamic MBean can in fact change its management interface at runtime, simply by returning different metadata from the getMBeanInfo method. Of course, some clients may not be happy with such a dynamic object, but the MBean server will do nothing to prevent a Dynamic MBean from changing its interface.

There are two points to this example. First, it demonstrates how an MBean can depend on an EJB for some of its functionality. Second, it shows how to create MBeans with dynamic management interfaces. If you were to write a standard MBean with a static interface for this example it would look like the following:

 public interface EjbMBeanAdaptorMBean {     public String getHelloPrefix();     public void setHelloPrefix(String prefix);     public String getEjbJndiName();     public void setEjbJndiName(String jndiName);     public String echo(String arg) throws CreateException, NamingException;     public void create() throws Exception;     public void start() throws Exception;     public void stop();     public void destroy(); } 

Lines 6783 are where the MBean operation metadata is constructed. The echo(String), create(), start(), stop(), and destroy() operations are defined by obtaining the corresponding java.lang.reflect.Method object and adding a description.

Let's go through the code and discuss where this interface implementation exists and how the MBean uses the EJB. Beginning with lines 4051, the two MBeanAttributeInfo instances created define the attributes of the MBean. These attributes correspond to the getHelloPrefix/setHelloPrefix and getEjbJndiName/setEjbJndiName of the static interface. One thing to note in terms of why you might want to use a Dynamic MBean is that you have the ability to associate descriptive text with the attribute metadata. This is not something you can do with a static interface.

Lines 88103 correspond to the JBoss service life cycle callbacks. Because you are subclassing the ServiceMBeanSupport utility class, you override the createService, startService, and stopService template callbacks rather than the create, start, and stop methods of the service interface. Note that you cannot attempt to look up the EchoLocalHome interface of the EJB you make use of until the startService method. Any attempt to access the home interface in an earlier life cycle method would result in the name not being found in JNDI because the EJB container had not gotten to the point of binding the home interfaces. Because of this dependency, you need to specify that the MBean service depends on the EchoLocal EJB container to ensure that the service is not started before the EJB container is started. You will see this dependency specification when we look at the service descriptor.

Lines 105121 are the HelloPrefix and EjbJndiName attribute accessors' implementations. These are invoked in response to getAttribute/setAttribute invocations made through the MBean server.

Lines 123130 correspond to the echo(String) operation implementation. This method invokes the EchoLocal.echo(String) EJB method. The local bean interface is created by using the EchoLocalHome that was obtained in the startService method.

The remainder of the class makes up the Dynamic MBean interface implementation. Lines 133152 correspond to the MBean metadata accessor callback. This method returns a description of the MBean management interface, in the form of the javax.management.MBeanInfo object. This is made up of a description, the MBeanAttributeInfo, and the MBeanOperationInfo metadata created earlier, as well as constructor and notification information. This MBean does not need any special constructors or notifications, so this information is null.

Lines 154258 handle the attribute access requests. This is rather tedious and error-prone code, so you should use a toolkit or an infrastructure that helps generate these methods. A model MBean framework based on XML called XBeans is currently being investigated in JBoss. Other than this, no other Dynamic MBean frameworks currently exist.

Lines 260310 correspond to the operation invocation dispatch entry point. Here, the request operation action name is checked against those the MBean handles, and the appropriate method is invoked.

The jboss-service.xml descriptor for the MBean is shown next. This is the format of the EJB container MBean ObjectName:

 <server>     <mbean code="org.jboss.chap2.ex3.EjbMBeanAdaptor"            name="jboss.book:service=EjbMBeanAdaptor">         <attribute name="HelloPrefix">AdaptorPrefix</attribute>         <attribute name="EjbJndiName">local/chap2.EchoBean</attribute>         <depends>jboss.j2ee:service=EJB,jndiName=local/chap2.EchoBean</depends>     </mbean> </server> 

You deploy the example EAR by running this:

 [examples]$ ant -Dchap=chap2 -Dex=3 run-example 

On the server console there should be messages similar to the following:

[View full width]

14:57:12,906 INFO [EARDeployer] Init J2EE application: file:/private/tmp/jboss-4.0.1 /server/default/deploy/chap2-ex3.ear 14:57:13,044 INFO [EjbMBeanAdaptor] Don't panic, just a stack trace java.lang.Throwable: getMBeanInfo trace at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo (EjbMBeanAdaptor.java:153) at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo (RawDynamicInvoker.java:172) at org.jboss.mx.server.RawDynamicInvoker.preRegister (RawDynamicInvoker.java:187) ... 14:57:13,088 INFO [EjbMBeanAdaptor] preRegister notification seen 14:57:13,093 INFO [EjbMBeanAdaptor] Don't panic, just a stack trace java.lang.Throwable: getMBeanInfo trace at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo (EjbMBeanAdaptor.java:153) at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo (RawDynamicInvoker.java:172) at org.jboss.mx.server.registry.BasicMBeanRegistry.registerMBean (BasicMBeanRegistry.java:207) ... 14:57:13,117 INFO [EjbMBeanAdaptor] Don't panic, just a stack trace java.lang.Throwable: getMBeanInfo trace at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo (EjbMBeanAdaptor.java:153) at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo (RawDynamicInvoker.java:172) at org.jboss.mx.server.registry.BasicMBeanRegistry.registerMBean (BasicMBeanRegistry.java:235) ... 14:57:13,140 WARN [EjbMBeanAdaptor] Unexcepted error accessing MBeanInfo for null java.lang.NullPointerException at org.jboss.system.ServiceMBeanSupport.postRegister (ServiceMBeanSupport.java:418) at org.jboss.mx.server.RawDynamicInvoker.postRegister (RawDynamicInvoker.java:226) at org.jboss.mx.server.registry.BasicMBeanRegistry.registerMBean (BasicMBeanRegistry.java:312) ... 14:57:13,203 INFO [EjbMBeanAdaptor] Don't panic, just a stack trace java.lang.Throwable: getMBeanInfo trace at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo (EjbMBeanAdaptor.java:153) at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo (RawDynamicInvoker.java:172) at org.jboss.mx.server.MBeanServerImpl.getMBeanInfo (MBeanServerImpl.java:481) ... 14:57:13,232 INFO [EjbMBeanAdaptor] Don't panic, just a stack trace java.lang.Throwable: getMBeanInfo trace at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo (EjbMBeanAdaptor.java:153) at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo (RawDynamicInvoker.java:172) at org.jboss.mx.server.MBeanServerImpl.getMBeanInfo (MBeanServerImpl.java:481) ... 14:57:13,420 INFO [EjbModule] Deploying Chap2EchoInfoBean 14:57:13,443 INFO [EjbModule] Deploying chap2.EchoBean 14:57:13,488 INFO [EjbMBeanAdaptor] Don't panic, just a stack trace java.lang.Throwable: getMBeanInfo trace at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo (EjbMBeanAdaptor.java:153) at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo (RawDynamicInvoker.java:172) at org.jboss.mx.server.MBeanServerImpl.getMBeanInfo (MBeanServerImpl.java:481) ... 14:57:13,542 INFO [EjbMBeanAdaptor] Don't panic, just a stack trace java.lang.Throwable: getMBeanInfo trace at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo (EjbMBeanAdaptor.java:153) at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo (RawDynamicInvoker.java:172) at org.jboss.mx.server.MBeanServerImpl.getMBeanInfo (MBeanServerImpl.java:481) ... 14:57:13,558 INFO [EjbMBeanAdaptor] Begin invoke, actionName=create 14:57:13,560 INFO [EjbMBeanAdaptor] Notified of create state 14:57:13,562 INFO [EjbMBeanAdaptor] End invoke, actionName=create 14:57:13,604 INFO [EjbMBeanAdaptor] Don't panic, just a stack trace java.lang.Throwable: getMBeanInfo trace at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo (EjbMBeanAdaptor.java:153) at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo (RawDynamicInvoker.java:172) at org.jboss.mx.server.MBeanServerImpl.getMBeanInfo (MBeanServerImpl.java:481) at org.jboss.mx.server.MBeanServerImpl.isInstanceOf (MBeanServerImpl.java:639) ... 14:57:13,621 INFO [EjbMBeanAdaptor] Don't panic, just a stack trace java.lang.Throwable: getMBeanInfo trace at org.jboss.chap2.ex3.EjbMBeanAdaptor.getMBeanInfo (EjbMBeanAdaptor.java:153) at org.jboss.mx.server.RawDynamicInvoker.getMBeanInfo (RawDynamicInvoker.java:172) at org.jboss.mx.server.MBeanServerImpl.getMBeanInfo (MBeanServerImpl.java:481) at org.jboss.mx.util.JMXInvocationHandler.<init> (JMXInvocationHandler.java:110) at org.jboss.mx.util.MBeanProxy.get(MBeanProxy.java:76) at org.jboss.mx.util.MBeanProxy.get(MBeanProxy.java:64) 14:57:13,641 INFO [EjbMBeanAdaptor] Begin invoke, actionName=getState 14:57:13,942 INFO [EjbMBeanAdaptor] Begin invoke, actionName=start 14:57:13,944 INFO [EjbMBeanAdaptor] Notified of start state 14:57:13,951 INFO [EjbMBeanAdaptor] Testing Echo 14:57:13,983 INFO [EchoBean] echo, info=echo info, arg=, arg=startService 14:57:13,986 INFO [EjbMBeanAdaptor] echo(startService) = startService 14:57:13,988 INFO [EjbMBeanAdaptor] End invoke, actionName=start 14:57:13,991 INFO [EJBDeployer] Deployed: file:/private/tmp/jboss-4.0.1/server/default /tmp/deploy/tmp1418chap2-ex3.ear-contents/chap2-ex3.jar 14:57:14,075 INFO [EARDeployer] Started J2EE application: file:/private/tmp/jboss-4.0.1 /server/default/deploy/chap2-ex3.ear

The stack traces are not exceptions. They are traces that come from line 150 of the EjbMBeanAdaptor code to demonstrate that clients ask for the MBean interface when they want to discover the MBean's capabilities. Notice that the EJB container (lines with EjbModule) is started before the example MBean (lines with EjbMBeanAdaptor).

Now, let's invoke the echo method, using the JMX Console web application. Go to the JMX Console (http://localhost:8080/jmx-console) and find service=EjbMBeanAdaptor in the jboss.book domain. Click the link and scroll down to the echo operation section. The view should be like that shown in Figure 2.19.

Figure 2.19. The EjbMBeanAdaptor MBean operations JMX Console view.


As shown, you have already entered an argument string of -echo-arg into the ParamValue text field. If you click the Invoke button, the result string AdaptorPrefix-echo-arg is displayed on the results page. The server console will show several stack traces from the various metadata queries issued by the JMX Console and the MBean invoke method debugging lines:

 10:51:48,671 INFO [EjbMBeanAdaptor] Begin invoke, actionName=echo 10:51:48,671 INFO [EjbMBeanAdaptor] Lookup EchoLocalHome@local/chap2.EchoBean 10:51:48,687 INFO [EchoBean] echo, info=echo info, arg=, arg=-echo-arg 10:51:48,687 INFO [EjbMBeanAdaptor] Result: AdaptorPrefix-echo-arg 10:51:48,687 INFO [EjbMBeanAdaptor] End invoke, actionName=echo 



JBoss 4. 0(c) The Official Guide
JBoss 4.0 - The Official Guide
ISBN: B003D7JU58
EAN: N/A
Year: 2006
Pages: 137

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net