Chapter 2. The JBoss JMX Microkernel

Modularly developed from the ground up, the JBoss server and container are completely implemented using component-based plug-ins. The modularization effort is supported by the use of JMX, the Java Management Extension API. Using JMX, industry-standard interfaces help manage both JBoss/Server components and the applications deployed on it. Ease of use is still the number one priority, and the JBoss Server version 3.x architecture sets a new standard for modular, plug-in design as well as ease of server and application management.

This high degree of modularity benefits the application developer in several ways. The already tight code can be further trimmed down to support applications that must have a small footprint. For example, if EJB passivation is unnecessary in your application, simply take the feature out of the server. If you later decide to deploy the same application under an Application Service Provider (ASP) model, simply enable the server's passivation feature for that web-based deployment. Another example is the freedom you have to drop your favorite object to relational database (O-R) mapping tool, such as TOPLink, directly into the container.

This chapter will introduce you to JMX and its role as the JBoss server component bus. You will also be introduced to the JBoss MBean service notion that adds life cycle operations to the basic JMX management component.

2.1. JMX

The success of the full Open Source J2EE stack lies with the use of JMX (Java Management Extension). JMX is the best tool for integration of software. It provides a common spine that allows the user to integrate modules, containers, and plug-ins. Figure 2.1, “The JBoss JMX integration bus and the standard JBoss components” shows the role of JMX as an integration spine or bus into which components plug. Components are declared as MBean services that are then loaded into JBoss. The components may subsequently be administered using JMX.

The JBoss JMX integration bus and the standard JBoss components

Figure 2.1. The JBoss JMX integration bus and the standard JBoss components

2.1.1. An Introduction to JMX

Before looking at how JBoss uses JMX as its component bus, it would help to get a basic overview what JMX is by touching on some of its key aspects.

JMX components are defined by the Java Management Extensions Instrumentation and Agent Specification, v1.0, which is available from the JSR003 Web page at http://jcp.org/aboutJava/communityprocess/final/jsr003/. The material in this JMX overview section is derived from the JMX instrumentation specification, with a focus on the aspects most used by JBoss. A more comprehensive discussion of JMX and its application can be found in JMX: Managing J2EE with Java Management Extensions written by Juha Lindfors (Sams, 0672322889, 2002).

JMX is about providing a standard for managing and monitoring all varieties of software and hardware components from Java. Further, JMX aims to provide integration with the large number of existing management standards. Figure 2.2, “The Relationship between the components of the JMX architecture” shows examples of components found in a JMX environment, and illustrates the relationship between them as well as how they relate to the three levels of the JMX model. The three levels are:

  • Instrumentation, which are the resources to manage
  • Agents, which are the controllers of the instrumentation level objects
  • Distributed services, the mechanism by which administration applications interact with agents and their managed objects
The Relationship between the components of the JMX architecture

Figure 2.2. The Relationship between the components of the JMX architecture

2.1.1.1. Instrumentation Level

The instrumentation level defines the requirements for implementing JMX manageable resources. A JMX manageable resource can be virtually anything, including applications, service components, devices, and so on. The manageable resource exposes a Java object or wrapper that describes its manageable features, which makes the resource instrumented so that it can be managed by JMX-compliant applications.

The user provides the instrumentation of a given resource using one or more managed beans, or MBeans. There are four varieties of MBean implementations: standard, dynamic, model, and open. The differences between the various MBean types is discussed in Managed Beans or MBeans.

The instrumentation level also specifies a notification mechanism. The purpose of the notification mechanism is to allow MBeans to communicate changes with their environment. This is similar to the JavaBean property change notification mechanism, and can be used for attribute change notifications, state change notifications, and so on.

2.1.1.2. Agent Level

The agent level defines the requirements for implementing agents. Agents are responsible for controlling and exposing the managed resources that are registered with the agent. By default, management agents are located on the same hosts as their resources. This collocation is not a requirement.

The agent requirements make use of the instrumentation level to define a standard MBeanServer management agent, supporting services, and a communications connector. JBoss provides both an html adaptor as well as an RMI adaptor.

The JMX agent can be located in the hardware that hosts the JMX manageable resources when a Java Virtual Machine (JVM) is available. This is how the JBoss server uses the MBeanServer. A JMX agent does not need to know which resources it will serve. JMX manageable resources may use any JMX agent that offers the services it requires.

Managers interact with an agent's MBeans through a protocol adaptor or connector, as described in the Section 2.1.1.3, “Distributed Services Level” in the next section. The agent does not need to know anything about the connectors or management applications that interact with the agent and its MBeans.

2.1.1.3. Distributed Services Level

The JMX specification notes that a complete definition of the distributed services level is beyond the scope of the initial version of the JMX specification. This was indicated by the component boxes with the horizontal lines in Figure 2.2, “The Relationship between the components of the JMX architecture”. The general purpose of this level is to define the interfaces required for implementing JMX management applications or managers. The following points highlight the intended functionality of the distributed services level as discussed in the current JMX specification.

  • Provide an interface for management applications to interact transparently with an agent and its JMX manageable resources through a connector
  • Exposes a management view of a JMX agent and its MBeans by mapping their semantic meaning into the constructs of a data-rich protocol (for example HTML or SNMP)
  • Distributes management information from high-level management platforms to numerous JMX agents
  • Consolidates management information coming from numerous JMX agents into logical views that are relevant to the end user's business operations
  • Provides security

It is intended that the distributed services level components will allow for cooperative management of networks of agents and their resources. These components can be expanded to provide a complete management application.

2.1.1.4. JMX Component Overview

This section offers an overview of the instrumentation and agent level components. The instrumentation level components include the following:

  • MBeans (standard, dynamic, open, and model MBeans)
  • Notification model elements
  • MBean metadata classes

The agent level components include:

  • MBean server
  • Agent services
2.1.1.4.1. Managed Beans or MBeans

An MBean is a Java object that implements one of the standard MBean interfaces and follows the associated design patterns. The MBean for a resource exposes all necessary information and operations that a management application needs to control the resource.

The scope of the management interface of an MBean includes the following:

  • Attribute values that may be accessed by name
  • Operations or functions that may be invoked
  • Notifications or events that may be emitted
  • The constructors for the MBean's Java class

JMX defines four types of MBeans to support different instrumentation needs:

  • Standard MBeans: These use a simple JavaBean style naming convention and a statically defined management interface. This is the most common type of MBean used by JBoss.
  • Dynamic MBeans: These must implement the javax.management.DynamicMBean interface, and they expose their management interface at runtime when the component is instantiated for the greatest flexibility. JBoss makes use of Dynamic MBeans in circumstances where the components to be managed are not known until runtime.
  • Open MBeans: These are an extension of dynamic MBeans. Open MBeans rely on basic, self-describing, user-friendly data types for universal manageability.
  • Model MBeans: These are also an extension of dynamic MBeans. Model MBeans must implement the javax.management.modelmbean.ModelMBean interface. Model MBeans simplify the instrumentation of resources by providing default behavior. Although JBoss does not use any Model MBeans for its core services as of the 3.2.0 release, there is a Model MBean implementation known as an XMBean.

We will present an example of a Standard and a Model MBean in the section that discusses extending JBoss with your own custom services.

2.1.1.4.2. Notification Model

JMX Notifications are an extension of the Java event model. Both the MBean server and MBeans can send notifications to provide information. The JMX specification defines the javax.management package Notification event object, NotificationBroadcaster event sender, and NotificationListener event receiver interfaces. The specification also defines the operations on the MBean server that allow for the registration of notification listeners.

2.1.1.4.3. MBean Metadata Classes

There is a collection of metadata classes that describe the management interface of an MBean. Users can obtain a common metadata view of any of the four MBean types by querying the MBean server with which the MBeans are registered. The metadata classes cover an MBean's attributes, operations, notifications, and constructors. For each of these, the metadata includes a name, a description, and its particular characteristics. For example, one characteristic of an attribute is whether it is readable, writable, or both. The metadata for an operation contains the signature of its parameter and return types.

The different types of MBeans extend the metadata classes to be able to provide additional information as required. This common inheritance makes the standard information available regardless of the type of MBean. A management application that knows how to access the extended information of a particular type of MBean is able to do so.

2.1.1.4.4. MBean Server

A key component of the agent level is the managed bean server. Its functionality is exposed through an instance of the javax.management.MBeanServer. An MBean server is a registry for MBeans that makes the MBean management interface available for use by management applications. The MBean never directly exposes the MBean object itself; rather, its management interface is exposed through metadata and operations available in the MBean server interface. This provides a loose coupling between management applications and the MBeans they manage.

MBeans can be instantiated and registered with the MBeanServer by the following:

  • Another MBean
  • The agent itself
  • A remote management application (through the distributed services)

When you register an MBean, you must assign it a unique object name. The object name then becomes the unique handle by which management applications identify the object on which to perform management operations. The operations available on MBeans through the MBean server include the following:

  • Discovering the management interface of MBeans
  • Reading and writing attribute values
  • Invoking operations defined by MBeans
  • Registering for notifications events
  • Querying MBeans based on their object name or their attribute values

Protocol adaptors and connectors are required to access the MBeanServer from outside the agent's JVM. Each adaptor provides a view via its protocol of all MBeans registered in the MBean server the adaptor connects to. An example adaptor is an HTML adaptor that allows for the inspection and editing of MBeans using a Web browser. As was indicated in Figure 2.2, “The Relationship between the components of the JMX architecture”, there are no protocol adaptors defined by the current JMX specification. Later versions of the specification will address the need for remote access protocols in standard ways.

A connector is an interface used by management applications to provide a common API for accessing the MBean server in a manner that is independent of the underlying communication protocol. Each connector type provides the same remote interface over a different protocol. This allows a remote management application to connect to an agent transparently through the network, regardless of the protocol. The specification of the remote management interface will be addressed in a future version of the JMX specification.

Adaptors and connectors make all MBean server operations available to a remote management application. For an agent to be manageable from outside of its JVM, it must include at least one protocol adaptor or connector. JBoss currently includes a custom HTML adaptor implementation and a custom JBoss RMI adaptor.

2.1.1.4.5. Agent Services

The JMX agent services are objects that support standard operations on the MBeans registered in the MBean server. The inclusion of supporting management services helps you build more powerful management solutions. Agent services are often themselves MBeans, which allow the agent and their functionality to be controlled through the MBean server. The JMX specification defines the following agent services:

  • A dynamic class loading MLet (management applet) service: This allows for the retrieval and instantiation of new classes and native libraries from an arbitrary network location.
  • Monitor services: These observe an MBean attribute's numerical or string value, and can notify other objects of several types of changes in the target.
  • Timer services: These provide a scheduling mechanism based on a one-time alarm-clock notification or on a repeated, periodic notification.
  • The relation service: This service defines associations between MBeans and enforces consistency on the relationships.

Any JMX-compliant implementation will provide all of these agent services. However, JBoss does not rely on any of these standard agent services.

2.2. JBoss JMX Implementation Architecture

2.2.1. The JBoss ClassLoader Architecture

JBoss 3.x employs a class loading architecture that facilitates sharing of classes across deployment units and hot deployment of services and applications. Before discussing the JBoss specific class loading model, we need to understand the nature of Java's type system and how class loaders fit in.

2.2.2. Class Loading and Types in Java

Class loading is a fundamental part of all server architectures. Arbitrary services and their supporting classes must be loaded into the server framework. This can be problematic due to the strongly typed nature of Java. Most developers know that the type of a class in Java is a function of the fully qualified name of the class. As of Java 1.2, the type is also a function of the java.lang.ClassLoader that is used to define that class. This additional qualification of type was added to ensure that environments in which classes may be loaded from arbitrary locations would be type-safe. A paper entitled Java is not type-safe by Vijay Saraswat in 1997 demonstrated that Java was not type-safe as intended. This could allow one to gain access to method and members of a class to which they should not have had access by fooling the Java VM into using an alternate implementation of a previously loaded class. Such circumvention of the type system was based on introducing class loaders that by-pass the normal delegation model. A class loader uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader that is either explicitly set when it is created, or assigned by the VM if no parent was specified. When called upon to find a class, a class loader will typically delegate the search for the class to its parent class loader before attempting to find the class or resource itself. The VM has a root class loader, called the bootstrap class loader, does not have a parent but may serve as the parent of a ClassLoader instance.

To address the type-safety issue, the type system was strengthened to include a class's defining ClassLoader in addition to the name of the class to fully define the type. The original paper in which the solution was described is Dynamic Class Loading in the Java Virtual Machine, by Sheng Liang and Gilad Bracha, and can be obtained from http://java.sun.com/people/sl/papers/oopsla98.ps.gz. The ramifications of this change in a dynamic environment like an application server, and especially JBoss with its support for hot deployment are that class cast exectiontions, linkage errors and illegal access errors can show up in ways not seen in more static class loading contexts. Let's take a look at the meaning of each of these exceptions and how they can happen.

2.2.2.1. ClassCastExceptions - I'm Not Your Type

A java.lang.ClassCastException results whenever an attempt is made to cast an instance to an incompatible type. A simple example is trying to obtain a String from a List into which a URL was placed:

ArrayList array = new ArrayList();
array.add(new URL("file:/tmp"));
String url = (String) array.get(0);

java.lang.ClassCastException: java.net.URL
at org.jboss.chap2.ex0.ExCCEa.main(Ex1CCE.java:16)

The ClassCastException tells you that the attempt to cast the array element to a String failed because the actual type was URL. This trivial case is not what we are interested in however. Consider the case of a JAR being loaded by differendt class loaders. Although the classes loaded through each class loader are identical in terms of the bytecode, they are completely different types as viewed by the Java type system. An example of this is illustrated by the code shown in Example 2.1, “The ExCCEc class used to demonstrate ClassCastException due to duplicate class loaders”.

Example 2.1. The ExCCEc class used to demonstrate ClassCastException due to duplicate class loaders

package org.jboss.chap2.ex0;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.Method;

import org.apache.log4j.Logger;

import org.jboss.util.ChapterExRepository;
import org.jboss.util.Debug;

/**
 * An example of a ClassCastException that
 * results from classes loaded through
 * different class loaders.
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
public class ExCCEc
{
    public static void main(String[] args) throws Exception
    {
        ChapterExRepository.init(ExCCEc.class);

        String chapDir = System.getProperty("chapter.dir");
        Logger ucl0Log = Logger.getLogger("UCL0");
        File jar0 = new File(chapDir+"/j0.jar");
        ucl0Log.info("jar0 path: "+jar0.toString());
        URL[] cp0 = {jar0.toURL()};
        URLClassLoader ucl0 = new URLClassLoader(cp0);
        Thread.currentThread().setContextClassLoader(ucl0);
        Class objClass = ucl0.loadClass("org.jboss.chap2.ex0.ExObj");
        StringBuffer buffer = new
            StringBuffer("ExObj Info");
        Debug.displayClassInfo(objClass, buffer, false);
        ucl0Log.info(buffer.toString());
        Object value = objClass.newInstance();
        
        File jar1 = new File(chapDir+"/j0.jar");
        Logger ucl1Log = Logger.getLogger("UCL1");
        ucl1Log.info("jar1 path: "+jar1.toString());
        URL[] cp1 = {jar1.toURL()};
        URLClassLoader ucl1 = new URLClassLoader(cp1);
        Thread.currentThread().setContextClassLoader(ucl1);
        Class ctxClass2 = ucl1.loadClass("org.jboss.chap2.ex0.ExCtx");
        buffer.setLength(0);
        buffer.append("ExCtx Info");
        Debug.displayClassInfo(ctxClass2, buffer, false);
        ucl1Log.info(buffer.toString());
        Object ctx2 = ctxClass2.newInstance();
        
        try {
            Class[] types = {Object.class};
            Method useValue =
                ctxClass2.getMethod("useValue", types);
            Object[] margs = {value};
            useValue.invoke(ctx2, margs);
        } catch(Exception e) {
            ucl1Log.error("Failed to invoke ExCtx.useValue", e);
            throw e;
        }
    }
}

Example 2.2. The ExCtx, ExObj, and ExObj2 classes used by the examples

package org.jboss.chap2.ex0;

import java.io.IOException;
import org.apache.log4j.Logger;
import org.jboss.util.Debug;

/**
 * A classes used to demonstrate various class
 * loading issues
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
public class ExCtx
{
    ExObj value;
    
    public ExCtx() 
        throws IOException
    {
        value = new ExObj();
        Logger log = Logger.getLogger(ExCtx.class);
        StringBuffer buffer = new StringBuffer("ctor.ExObj");
        Debug.displayClassInfo(value.getClass(), buffer, false);
        log.info(buffer.toString());
        ExObj2 obj2 = value.ivar;
        buffer.setLength(0);
        buffer = new StringBuffer("ctor.ExObj.ivar");
        Debug.displayClassInfo(obj2.getClass(), buffer, false);
        log.info(buffer.toString());
    }

    public Object getValue()
    {
        return value;
    }

    public void useValue(Object obj) 
        throws Exception
    {
        Logger log = Logger.getLogger(ExCtx.class);
        StringBuffer buffer = new
            StringBuffer("useValue2.arg class");
        Debug.displayClassInfo(obj.getClass(), buffer, false);
        log.info(buffer.toString());
        buffer.setLength(0);
        buffer.append("useValue2.ExObj class");
        Debug.displayClassInfo(ExObj.class, buffer, false);
        log.info(buffer.toString());
        ExObj ex = (ExObj) obj;
    }

    void pkgUseValue(Object obj) 
        throws Exception
    {
        Logger log = Logger.getLogger(ExCtx.class);
        log.info("In pkgUseValue");
    }
}

Example 2.3. The ExObj and ExObj2 classes used in the examples

package org.jboss.chap2.ex0;

import java.io.Serializable;

/**
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
public class ExObj
    implements Serializable
{
    public ExObj2 ivar = new ExObj2();
}


--------------------------------------------------------
package org.jboss.chap2.ex0;

import java.io.Serializable;

/**
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
public class ExObj2 
    implements Serializable
{
}
                    

The ExCCEc.main method uses reflection to isolate the classes that are being loaded by the class loaders ucl0 and ucl1 from the application class loader. Both are setup to load classes from the output/chap2/j0.jar, the contents of which are:

[nr@toki examples]$ jar -tf output/chap2/j0.jar

org/jboss/chap2/ex0/ExCtx.class
org/jboss/chap2/ex0/ExObj.class
org/jboss/chap2/ex0/ExObj2.class

We will run an example that demonstrates how a class cast exection can occur and then look at the specific issue with the example. See Appendix B, Book Example Installation for instructions on installing the examples accompanying the book, and then run the example from within the examples directory using the following command:

[nr@toki examples]$ ant -Dchap=chap2 -Dex=0c run-example
...
     [java] [ERROR,UCL1] Failed to invoke ExCtx.useValue
     [java] java.lang.reflect.InvocationTargetException
     [java] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
     [java] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
     [java] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl
.java:25)
     [java] at java.lang.reflect.Method.invoke(Method.java:324)
     [java] at org.jboss.chap2.ex0.ExCCEc.main(ExCCEc.java:58)
     [java] Caused by: java.lang.ClassCastException
     [java] at org.jboss.chap2.ex0.ExCtx.useValue(ExCtx.java:44)
     [java] ... 5 more

Only the exception is shown here. The full output can be found in the logs/chap2-ex0c.log file. At line 55 of ExCCEc.java we are invoking ExcCCECtx.useValue(Object) on the instance loaded and created in lines 37-48 using ucl1. The ExObj passed in is the one loaded and created in lines 25-35 via ucl0. The exception results when the ExCtx.useValue code attempts to cast the argument passed in to a ExObj. To understand why this fails consider the debugging output from the chap2-ex0c.log file shown in Example 2.4, “The chap2-ex0c.log debugging output for the ExObj classes seen”.

Example 2.4. The chap2-ex0c.log debugging output for the ExObj classes seen

[INFO,UCL0] ExObj Info
org.jboss.chap2.ex0.ExObj(113fe2).ClassLoader=java.net.URLClassLoader@6e3914
..java.net.URLClassLoader@6e3914
....file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/
  chap2/j0.jar
++++CodeSource:
    (file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/
  chap2/j0.jar <no certificates>)
Implemented Interfaces:
++interface java.io.Serializable(7934ad)
++++ClassLoader: null
++++Null CodeSource
 
[INFO,ExCtx] useValue2.ExObj class
org.jboss.chap2.ex0.ExObj(415de6).ClassLoader=java.net.URLClassLoader@30e280
..java.net.URLClassLoader@30e280
....file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/
  chap2/j0.jar
++++CodeSource:
    (file:/C:/Scott/JBoss/Books/AdminDevel/education/books/admin-devel/examples/output/
  chap2/j0.jar <no certificates>)
Implemented Interfaces:
++interface java.io.Serializable(7934ad)
++++ClassLoader: null
++++Null CodeSource

The first output prefixed with [INFO,UCL0] shows that the ExObj class loaded at line ExCCEc.java:31 has a hash code of 113fe2 and an associated URLClassLoader instance with a hash code of 6e3914, which corresponds to ucl0. This is the class used to create the instance passed to the ExCtx.useValue method. The second output prefixed with [INFO,ExCtx] shows that the ExObj class as seen in the context of the ExCtx.useValue method has a hash code of 415de6 and a URLClassLoader instance with an associated hash code of 30e280, which corresponds to ucl1. So even though the ExObj classes are the same in terms of actual bytecode since it comes from the same j0.jar, the classes are different as seen by both the ExObj class hash codes, and the associated URLClassLoader instances. Hence, attempting to cast an instance of ExObj from one scope to the other results in the ClassCastException.

This type of error is common when one redeploys an application to which other applications are holding references to classes from the redeployed application. For example, a standalone WAR accessing an EJB. If you are redeploying an application, all dependent applications must flush their class references. Typically this requires that the dependent applications themselves be redeployed.

An alternate means of allowing independent deployments to interact in the presence of redeployment would be to isolate the deployments by configuring the EJB layer to use the standard call-by-value semantics rather than the call-by-reference JBoss will default to for components collocated in the same VM. An example of how to enable call-by-value semantics is presented in Chapter 5, EJBs on JBoss

2.2.2.2. IllegalAccessException - Doing what you should not

A java.lang.IllegalAccessException is thrown when one attempts to access a method or member that visibility qualifiers do not allow. Typical examples are attempting to access private or protected methods or instance variables. Another common example is accessing package protected methods or members from a class that appears to be in the correct package, but is really not due to caller and callee classes being loaded by different class loaders. An example of this is illustrated by the code shown in Example 2.6, “Classes demonstrating the need for loading constraints”.

Example 2.5. The ExIAEd class used to demonstrate IllegalAccessException due to duplicate class loaders

package org.jboss.chap2.ex0;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.lang.reflect.Method;

import org.apache.log4j.Logger;

import org.jboss.util.ChapterExRepository;
import org.jboss.util.Debug;

/**
 * An example of IllegalAccessExceptions due to
 * classes loaded by two class loaders.
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
public class ExIAEd
{
    public static void main(String[] args) throws Exception
    {
	ChapterExRepository.init(ExIAEd.class);

	String chapDir = System.getProperty("chapter.dir");
	Logger ucl0Log = Logger.getLogger("UCL0");
	File jar0 = new File(chapDir+"/j0.jar");
	ucl0Log.info("jar0 path: "+jar0.toString());
	URL[] cp0 = {jar0.toURL()};
	URLClassLoader ucl0 = new URLClassLoader(cp0);
	Thread.currentThread().setContextClassLoader(ucl0);
	
	StringBuffer buffer = new
	    StringBuffer("ExIAEd Info");
	Debug.displayClassInfo(ExIAEd.class, buffer, false);
	ucl0Log.info(buffer.toString());
	
	Class ctxClass1 = ucl0.loadClass("org.jboss.chap2.ex0.ExCtx");
	buffer.setLength(0);
	buffer.append("ExCtx Info");
	Debug.displayClassInfo(ctxClass1, buffer, false);
	ucl0Log.info(buffer.toString());
	Object ctx0 = ctxClass1.newInstance();

	try {
	    Class[] types = {Object.class};
	    Method useValue =
		ctxClass1.getDeclaredMethod("pkgUseValue", types);
	    Object[] margs = {null};
	    useValue.invoke(ctx0, margs);
	} catch(Exception e) {
	    ucl0Log.error("Failed to invoke ExCtx.pkgUseValue", e);
	}
    }
}

The ExIAEd.main method uses reflection to load the ExCtx class via the ucl0 class loader while the ExIEAd class was loaded by the application class loader. We will run this example to demonstrate how the IllegalAccessException can occur and then look at the specific issue with the example. Run the example using the following command:

[orb@toki examples]$ ant -Dchap=chap2 -Dex=0d run-example
Buildfile: build.xml
...
[java] [ERROR,UCL0] Failed to invoke ExCtx.pkgUseValue
[java] java.lang.IllegalAccessException: Class org.jboss.chap2.ex0.ExIAEd 
  can not access a member of class org.jboss.chap2.ex0.ExCtx with modifiers ""
[java] at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:57)
[java] at java.lang.reflect.Method.invoke(Method.java:317)
[java] at org.jboss.chap2.ex0.ExIAEd.main(ExIAEd.java:48)

The truncated output shown here illustrates the IllegalAccessException. The full output can be found in the logs/chap2-ex0d.log file. At line 48 of ExIAEd.java the ExCtx.pkgUseValue(Object) method is invoked via reflection. The pkgUseValue method has package protected access and even though both the invoking class ExIAEd and the ExCtx class whose method is being invoked reside in the org.jboss.chap2.ex0 package, the invocation is seen to be invalid due to the fact that the two classes are loaded by different class loaders. This can be seen by looking at the debugging output from the chap2-ex0d.log file.

[INFO,UCL0] ExIAEd Info
org.jboss.chap2.ex0.ExIAEd(65855a).ClassLoader=sun.misc.Launcher$AppClassLoader@3f52a5
..sun.misc.Launcher$AppClassLoader@3f52a5
...
[INFO,UCL0] ExCtx Info
org.jboss.chap2.ex0.ExCtx(70eed6).ClassLoader=java.net.URLClassLoader@113fe2
..java.net.URLClassLoader@113fe2
...

The ExIAEd class is seen to have been loaded via the default application class loader instance sun.misc.Launcher$AppClassLoader@3f52a5, while the ExCtx class was loaded by the java.net.URLClassLoader@113fe2 instance. Because the classes are loaded by different class loaders, access to the package protected method is seen to be a security violation. So, not only is type a function of both the fully qualified class name and class loader, the package scope is as well.

An example of how this can happen in practise is to include the same classes in two different SAR deployments. If classes in the deployment have a package protected relationship, users of the SAR service may end up loading one class from SAR class loading at one point, and then load another class from the second SAR at a later time. If the two classes in question have a protected access relationship an IllegalAccessError will result. The solution is to either include the classes in a separate jar that is referenced by the SARs, or to combine the SARs into a single deployment. This can either be a single SAR, or an EAR the includes both SARs.

2.2.2.3. LinkageErrors - Making Sure You Are Who You Say You Are

To address the type-safety problems of the early Java VMs, the notion of loading constraints were added to the 1.2 Java language spec. Loading constraints validate type expectations in the context of class loader scopes to ensure that a class X is consistently the same class when multiple class loaders are involved. This is important because Java allows for user defined class loaders. Linkage errors are essentially an extension of the class cast exception that is enforced by the VM when classes are loaded and used.

To understand what loading constraints are and how they ensure type-safety we will first introduce the nomenclature of the Liang and Bracha paper along with an example from this paper. There are two type of class loaders, initiating and defining. An initiating class loader is one that a ClassLoader.loadClass method has been invoked on to initiate the loading of the named class. A defining class loader is the loader that calls one of the ClassLoader.defineClass methods to convert the class byte code into a Class instance. The most complete expression of a class is given by <C,Ld>Li , where C is the fully qualified class name, Ld is the defining class loader, and Li is the initiating class loader. In a context where the initiating class loader is not important the type may be represented by <C,Ld>, while when the defining class loader is not important, the type may be represented by CLi . In the latter case, there is still a defining class loader, its just not important what the identity of the defining class loader is. Also, a type is completely defined by <C,Ld>. The only time the initiating loader is relevant is when a loading constraint is being validated. Now consider the classes shown in Example 2.6, “Classes demonstrating the need for loading constraints”.

Example 2.6. Classes demonstrating the need for loading constraints

class <C,L1> {
    void f() {
        <Spoofed, L1>L1x = <Delegated, L2>L2
        x.secret_value = 1; // Should not be allowed
    }
}
class <Delegated,L2> {
    static <Spoofed, L2>L3 g() {...}
    }
}
class <Spoofed, L1> {
    public int secret_value;
}
class <Spoofed, L2> {
    private int secret_value;
}

The class C is defined by L1 and so L1 is used to initiate loading of the classes Spoofed and Delegated referenced in the C.f() method. The Spoofed class is defined by L1, but Delegated is defined by L2 because L1 delegates to L2. Since Delegated is defined by L2, L2 will be used to initiate loading of Spoofed in the context of the Delegated.g() method. In this example both L1 and L2 define different versions of Spoofed as indicated by the two versions shown at the end of Example 2.6, “Classes demonstrating the need for loading constraints”. Since C.f() believes x is an instance of <Spoofed,L1> it is able to access the private field secret_value of <Spoofed,L2> returned by Delegated.g() due to the 1.1 and earlier Java VM's failure to take into account that a class type is determined by both the fully qualified name of the class and the defining class loader.

Java 1.2 and beyond addresses this problem by generating loader constraints to validate type consistency when the types being used are coming from different defining class loaders. For the Example 2.6, “Classes demonstrating the need for loading constraints” example, the VM generates a constraint SpoofedL1=SpoofedL2 when the first line of method C.f() is verified to indicate that the type Spoofed must be the same regardless of whether the load of Spoofed is initiated by L1 or L2. It does not matter if L1 or L2, or even some other class loader defines Spoofed. All that matters is that there is only one Spoofed class defined regardless of whether L1 or L2 was used to initiate the loading. If L1 or L2 have already defined separate versions of Spoofed when this check is made a LinkageError will be generated immediately. Otherwise, the constraint will be recorded and when Delegated.g() is executed, any attempt to load a duplicate version of Spoofed will result in a LinkageError.

Now let's take a look at how a LinkageError can occur with a concrete example. Example 2.7, “A concrete example of a LinkageError” gives the example main class along with the custom class loader used.

Example 2.7. A concrete example of a LinkageError

  1 package org.jboss.chap2.ex0;
    import java.io.File;
    import java.net.URL;
    
  5 import org.apache.log4j.Logger;
    import org.jboss.util.ChapterExRepository;
    import org.jboss.util.Debug;
    
    /** 
 10  * An example of a LinkageError due to classes being defined by more
     * than one class loader in a non-standard class loading environment.
     *
     * @author Scott.Stark@jboss.org
     * @version $Revision: 1.11 $
 15  */
    public class ExLE
    {
        public static void main(String[] args) 
    	throws Exception
 20     {
            ChapterExRepository.init(ExLE.class);
    
            String chapDir = System.getProperty("chapter.dir");
            Logger ucl0Log = Logger.getLogger("UCL0");
 25         File jar0 = new File(chapDir+"/j0.jar");
            ucl0Log.info("jar0 path: "+jar0.toString());
            URL[] cp0 = {jar0.toURL()};
            Ex0URLClassLoader ucl0 = new Ex0URLClassLoader(cp0);
            Thread.currentThread().setContextClassLoader(ucl0);
 30         Class ctxClass1  = ucl0.loadClass("org.jboss.chap2.ex0.ExCtx");
            Class obj2Class1 = ucl0.loadClass("org.jboss.chap2.ex0.ExObj2");
            StringBuffer buffer = new StringBuffer("ExCtx Info");
            Debug.displayClassInfo(ctxClass1, buffer, false);
            ucl0Log.info(buffer.toString());
 35         buffer.setLength(0);
            buffer.append("ExObj2 Info, UCL0");
            Debug.displayClassInfo(obj2Class1, buffer, false);
            ucl0Log.info(buffer.toString());
            
 40         File jar1 = new File(chapDir+"/j1.jar");
            Logger ucl1Log = Logger.getLogger("UCL1");
            ucl1Log.info("jar1 path: "+jar1.toString());
            URL[] cp1 = {jar1.toURL()};
            Ex0URLClassLoader ucl1 = new Ex0URLClassLoader(cp1);
 45         Class obj2Class2 = ucl1.loadClass("org.jboss.chap2.ex0.ExObj2");
            buffer.setLength(0);
            buffer.append("ExObj2 Info, UCL1");
            Debug.displayClassInfo(obj2Class2, buffer, false);
            ucl1Log.info(buffer.toString());
 50         
            ucl0.setDelegate(ucl1);
            try {
                ucl0Log.info("Try ExCtx.newInstance()");
                Object ctx0 = ctxClass1.newInstance();
 55             ucl0Log.info("ExCtx.ctor succeeded, ctx0: "+ctx0);
            } catch(Throwable e) {
                ucl0Log.error("ExCtx.ctor failed", e);
            }
        }
 60 }
package org.jboss.chap2.ex0;

import java.net.URLClassLoader;
import java.net.URL;

import org.apache.log4j.Logger;

/** 
 * A custom class loader that overrides the standard parent delegation
 * model
 *
 * @author Scott.Stark@jboss.org
 * @version $Revision: 1.11 $
 */
public class Ex0URLClassLoader extends URLClassLoader
{
    private static Logger log = Logger.getLogger(Ex0URLClassLoader.class);
    private Ex0URLClassLoader delegate;

    public Ex0URLClassLoader(URL[] urls)
    {
        super(urls);
    }
    
    void setDelegate(Ex0URLClassLoader delegate)
    {
        this.delegate = delegate;
    }
    
    protected synchronized Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        Class clazz = null;
        if (delegate != null) {
            log.debug(Integer.toHexString(hashCode()) +
		      "; Asking delegate to loadClass: " + name);
            clazz = delegate.loadClass(name, resolve);
            log.debug(Integer.toHexString(hashCode()) +
		      "; Delegate returned: "+clazz);
        } else {
            log.debug(Integer.toHexString(hashCode()) + 
		      "; Asking super to loadClass: "+name);
            clazz = super.loadClass(name, resolve);
            log.debug(Integer.toHexString(hashCode()) + 
		      "; Super returned: "+clazz);
        }
        return clazz;
    }

    protected Class findClass(String name)
        throws ClassNotFoundException
    {
        Class clazz = null;
        log.debug(Integer.toHexString(hashCode()) + 
		  "; Asking super to findClass: "+name);
        clazz = super.findClass(name);
        log.debug(Integer.toHexString(hashCode()) + 
		  "; Super returned: "+clazz);
        return clazz;
    }
}

The key component in this example is the URLClassLoader subclass Ex0URLClassLoader. This class loader implementation overrides the default parent delegation model to allow the ucl0 and ucl1 instances to both load the ExObj2 class and then setup a delegation relationship from ucl0 to ucl1. At lines 30 and 31. the ucl0 Ex0URLClassLoader is used to load the ExCtx and ExObj2 classes. At line 45 of ExLE.main the ucl1 Ex0URLClassLoader is used to load the ExObj2 class again. At this point both the ucl0 and ucl1 class loaders have defined the ExObj2 class. A delegation relationship from ucl0 to ucl1 is then setup at line 51 via the ucl0.setDelegate(ucl1) method call. Finally, at line 54 of ExLE.main an instance of ExCtx is created using the class loaded via ucl0. The ExCtx class is the same as presented in Example 2.2, “The ExCtx, ExObj, and ExObj2 classes used by the examples”, and the constructor was:

public ExCtx() 
    throws IOException
{
    value = new ExObj();
    Logger log = Logger.getLogger(ExCtx.class);
    StringBuffer buffer = new StringBuffer("ctor.ExObj");
    Debug.displayClassInfo(value.getClass(), buffer, false);
    log.info(buffer.toString());
    ExObj2 obj2 = value.ivar;
    buffer.setLength(0);
    buffer = new StringBuffer("ctor.ExObj.ivar");
    Debug.displayClassInfo(obj2.getClass(), buffer, false);
    log.info(buffer.toString());
}    

Now, since the ExCtx class was defined by the ucl0 class loader, and at the time the ExCtx constructor is executed, ucl0 delegates to ucl1, line 24 of the ExCtx constructor involves the following expression which has been rewritten in terms of the complete type expressions:

<ExObj2,ucl0>ucl0 obj2 = <ExObj,ucl1>ucl0 value * ivar

This generates a loading constraint of ExObj2ucl0 = ExObj2ucl1 since the ExObj2 type must be consistent across the ucl0 and ucl1 class loader instances. Because we have loaded ExObj2 using both ucl0 and ucl1 prior to setting up the delegation relationship, the constraint will be violated and should generate a LinkageError when run. Run the example using the following command:

[nr@toki examples]$ ant -Dchap=chap2 -Dex=0e run-example
Buildfile: build.xml
...
[java] java.lang.LinkageError: loader constraints violated when linking org/jboss/chap2/ex0/ExObj2 class
[java] at org.jboss.chap2.ex0.ExCtx.<init>(ExCtx.java:24)
[java] at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
[java] at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
[java] at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
[java] at java.lang.reflect.Constructor.newInstance(Constructor.java:274)
[java] at java.lang.Class.newInstance0(Class.java:308)
[java] at java.lang.Class.newInstance(Class.java:261)
[java] at org.jboss.chap2.ex0.ExLE.main(ExLE.java:53)

As expected, a LinkageError is thrown while validating the loader constraints required by line 24 of the ExCtx constructor.

2.2.2.3.1. Debugging Class Loading Issues

Debugging class loading issues comes down to finding out where a class was loaded from. A useful tool for this is the code snippet shown in Example 2.8, “Obtaining debugging information for a Class” taken from the org.jboss.util.Debug class of the book examples.

Example 2.8. Obtaining debugging information for a Class

Class clazz =...;
StringBuffer results = new StringBuffer();

ClassLoader cl = clazz.getClassLoader();
results.append("\n" + clazz.getName() + "(" + 
               Integer.toHexString(clazz.hashCode()) + ").ClassLoader=" + cl);
ClassLoader parent = cl;

while (parent != null) {
    results.append("\n.."+parent);
    URL[] urls = getClassLoaderURLs(parent);

    int length = urls != null ? urls.length : 0;
    for(int u = 0; u < length; u ++) {
	results.append("\n...."+urls[u]);
    }

    if (showParentClassLoaders == false) {
	break;
    }
    if (parent != null) {
	parent = parent.getParent();
    }
}

CodeSource clazzCS = clazz.getProtectionDomain().getCodeSource();
if (clazzCS != null) {
    results.append("\n++++CodeSource: "+clazzCS);
} else {
    results.append("\n++++Null CodeSource");
}

The key items are shown in bold. The first is that every Class object knows its defining ClassLoader and this is available via the getClassLoader() method. The defines the scope in which the Class type is known as we have just seen in the previous sections on class cast exceptions, illegal access exceptions and linkage errors. From the ClassLoader you can view the hierarchy of class loaders that make up the parent delegation chain. If the class loader is a URLClassLoader you can also see the URLs used for class and resource loading.

The defining ClassLoader of a Class cannot tell you from what location that Class was loaded. To determine this you must obtain the java.security.ProtectionDomain and then the java.security.CodeSource. It is the CodeSource that has the URL location from which the class originated. Note that not every Class has a CodeSource. If a class is loaded by the bootstrap class loader then its CodeSource will be null. This will be the case for all classes in the java.* and javax.* packages, for example.

Beyond that it may be useful to view the details of classes being loaded into the JBoss server. You can enable verbose logging of the JBoss class loading layer using a Log4j configuration fragment like that shown in Example 2.9, “An example log4j.xml configuration fragment for enabling verbose class loading logging”.

Example 2.9. An example log4j.xml configuration fragment for enabling verbose class loading logging

<appender name="UCL" class="org.apache.log4j.FileAppender">
    <param name="File" value="${jboss.server.home.dir}/log/ucl.log"/>
    <param name="Append" value="false"/>
    <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern" value="[%r,%c{1},%t] %m%n"/>
    </layout>
</appender>
<category name="org.jboss.mx.loading" additivity="false">
    <priority value="TRACE" class="org.jboss.logging.XLevel"/>
    <appender-ref ref="UCL"/>
</category>

This places the output from the classes in the org.jboss.mx.loading package into the ucl.log file of the server configurations log directory. Although it may not be meaningful if you have not looked at the class loading code, it is vital information needed for submitting bug reports or questions regarding class loading problems. If you have a class loading problem that appears to be a bug, submit it to the JBoss project on SourceForge and include this log file as an attachment. If the log file is too big, compress it and mail it to scott.stark@jboss.org.

2.2.2.4. Inside the JBoss Class Loading Architecture

Now that we have the role of class loaders in the Java type system defined, let's take a look at the JBoss class loading architecture. Figure 2.3, “The core JBoss class loading components”.

The core JBoss class loading components

Figure 2.3. The core JBoss class loading components

The central component is the org.jboss.mx.loading.UnifiedClassLoader3 (UCL) class loader. This is an extension of the standard java.net.URLClassLoader that overrides the standard parent delegation model to use a shared repository of classes and resources. This shared repository is the org.jboss.mx.loading.UnifiedLoaderRepository3. Every UCL is associated with a single UnifiedLoaderRepository3, and a UnifiedLoaderRepository3 typically has many UCLs. A UCL may have multiple URLs associated with it for class and resource loading. Deployers use the top-level deployment's UCL as a shared class loader and all deployment archives are assigned to this class loader. We will talk about the JBoss deployers and their interaction with the class loading system in more detail latter in Section 2.4.2, “JBoss MBean Services”.

When a UCL is asked to load a class, it first looks to the repository cache it is associated with to see if the class has already been loaded. Only if the class does not exist in the repository will it be loaded into the repository by the UCL. By default, there is a single UnifiedLoaderRepository3 shared across all UCL instances. This means the UCLs form a single flat class loader namespace. The complete sequence of steps that occur when a UnfiedClassLoader3.loadClass(String, boolean) method is called is:

  1. Check the UnifiedLoaderRepository3 classes cache associated with the UnifiedClassLoader3. If the class is found in the cache it is returned.
  2. Else, ask the UnfiedClassLoader3 if it can load the class. This is essentially a call to the superclass URLClassLoader.loadClass(String, boolean) method to see if the class is among the URLs associated with the class loader, or visible to the parent class loader. If the class is found it is placed into the repository classes cache and returned.
  3. Else, the repository is queried for all UCLs that are capable of providing the class based on the repository package name to UCL map. When a UCL is added to a repository an association between the package names available in the URLs associated with the UCL is made, and a mapping from package names to the UCLs with classes in the package is updated. This allows for a quick determination of which UCLs are capable of loading the class. The UCLs are then queried for the requested class in the order in which the UCLs were added to the repository. If a UCL is found that can load the class it is returned, else a java.lang.ClassNotFoundException is thrown.
2.2.2.4.1. Viewing Classes in the Loader Repository

Another useful source of information on classes is the UnifiedLoaderRepository itself. This is an MBean that contains operations to display class and package information. The default repository is located under a standard JMX name of JMImplementation:name=Default,service=LoaderRepository, and its MBean can be accessed via the JMX console by following its link from the front page. The JMX console view of this MBean is shown in Figure 2.4, “The default class LoaderRepository MBean view in the JMX console”.

The default class LoaderRepository MBean view in the JMX console

Figure 2.4. The default class LoaderRepository MBean view in the JMX console

Two useful operations you will find here are getPackageClassLoaders(String) and displayClassInfo(String). The getPackageClassLoaders operation returns a set of class loaders that have been indexed to contain classes or resources for the given package name. The package name must have a trailing period. If you type in the package name org.jboss.ejb., the following representation is displayed:

[org.jboss.mx.loading.UnifiedClassLoader3@7dac02{ url=file:/private/tmp/jboss-3.2.6/server/default/tmp/deploy/tmp9103jboss-service.xml ,addedOrder=2}]

This is the string representation of the set. It shows one UnifiedClassLoader3 instance with a primary URL pointing to the default/conf/jboss-service.xml descriptor. This is the second class loader added to the repository (shown by the addedOrder=2) and it is the class loader that owns all of the JARs in the lib directory of the server configuration (e.g., server/default/lib). If you enter the package name org.jboss.jmx.adaptor.html., then the following set will be displayed:

[org.jboss.mx.loading.UnifiedClassLoader3@7dac02{ url=file:/private/tmp/jboss-3.2.6/server/default/tmp/deploy/tmp9103jboss-service.xml ,addedOrder=2}]

This time there are two UnifiedClassLoader3 instances, one for the default/deploy/jmx-console.war and one for the default/deploy/jmx-console2.war.

The view the information for a given class, use the displayClassInfo operation, passing in the fully qualified name of the class to view. For example, if we use org.jboss.jmx.adaptor.html.HtmlAdaptorServlet which is from the package we just looked at, the following description is displayed:

org.jboss.jmx.adaptor.html.HtmlAdaptorServlet Information
Repository cache version:
org.jboss.jmx.adaptor.html.HtmlAdaptorServlet(26f678).ClassLoader=org.jboss.mx.loading.Uni
fiedClassLoader3@30cd4a{ url=file:/private/tmp/jboss-3.2.6/server/default/deploy/jmx-conso
le.war/ ,addedOrder=32}
..org.jboss.mx.loading.UnifiedClassLoader3@30cd4a{ url=file:/private/tmp/jboss-3.2.6/serve
r/default/deploy/jmx-console.war/ ,addedOrder=32}
....file:/private/tmp/jboss-3.2.6/server/default/deploy/jmx-console.war/
....file:/private/tmp/jboss-3.2.6/server/default/deploy/jmx-console.war/WEB-INF/classes/
..org.jboss.system.server.NoAnnotationURLClassLoader@e48e1b
..sun.misc.Launcher$AppClassLoader@33056f
....file:/private/tmp/jboss-3.2.6/bin/run.jar
....file:/System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Home/lib/tools.jar
..sun.misc.Launcher$ExtClassLoader@94af67
....file:/System/Library/Java/Extensions/CoreAudio.jar
....file:/System/Library/Java/Extensions/j3daudio.jar
....file:/System/Library/Java/Extensions/j3dcore.jar
....file:/System/Library/Java/Extensions/j3dsupport.jar
....file:/System/Library/Java/Extensions/j3dutils.jar
....file:/System/Library/Java/Extensions/jai_codec.jar
....file:/System/Library/Java/Extensions/jai_core.jar
....file:/System/Library/Java/Extensions/libJ3D.jnilib
....file:/System/Library/Java/Extensions/libJ3DAudio.jnilib
....file:/System/Library/Java/Extensions/libJ3DUtils.jnilib
....file:/System/Library/Java/Extensions/libmlib_jai.jnilib
....file:/System/Library/Java/Extensions/mlibwrapper_jai.jar
....file:/System/Library/Java/Extensions/MRJToolkit.jar
....file:/System/Library/Java/Extensions/QTJava.zip
....file:/System/Library/Java/Extensions/QTJSupport.jar
....file:/System/Library/Java/Extensions/vecmath.jar
....file:/System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Home/lib/ext/apple_pro
vider.jar
....file:/System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Home/lib/ext/ldapsec.j
ar
....file:/System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Home/lib/ext/localedat
a.jar
....file:/System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Home/lib/ext/sunjce_pr
ovider.jar
++++CodeSource: (file:/private/tmp/jboss-3.2.6/server/default/deploy/jmx-console.war/WEB-I
NF/classes/ )
Implemented Interfaces:

### Instance0 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@30cd4a{ url=file:/pri
vate/tmp/jboss-3.2.6/server/default/deploy/jmx-console.war/ ,addedOrder=32}


### Instance1 found in UCL: org.jboss.mx.loading.UnifiedClassLoader3@492aff{ url=file:/pri
vate/tmp/jboss-3.2.6/server/default/deploy/jmx-console2.war/ ,addedOrder=34}


### Instance2 via UCL: org.jboss.mx.loading.UnifiedClassLoader3@30cd4a{ url=file:/private/
tmp/jboss-3.2.6/server/default/deploy/jmx-console.war/ ,addedOrder=32}

The information is a dump of the information for the Class instance in the loader repository if one has been loaded, followed by the class loaders that are seen to have the class file available. If a class is seen to have more than one class loader associated with it, then there is the potential for class loading related errors.

2.2.2.4.2. Scoping Classes

If you need to deploy multiple versions of an application the default 3.x class loading model would require that each application be deployed in a separate JBoss server. Sometimes this is desirable as you have more control over security and resource monitoring, but it can be difficult to manage multiple server instances. An alternative mechanism exists that allows multiple versions of an application to be deployed using deployment based scoping.

With deployment based scoping, each deployment creates its own class loader repository in the form of a HeirarchicalLoaderRepository3 that looks first to the UnifiedClassLoader3 instances of the deployment units included in the EAR before delegating to the default UnifiedLoaderRepository3. To enable an EAR specific loader repository, you need to create a META-INF/jboss-app.xml descriptor as shown in Example 2.10, “An example jboss-app.xml descriptor for enabled scoped class loading at the EAR level.”.

Example 2.10. An example jboss-app.xml descriptor for enabled scoped class loading at the EAR level.

<jboss-app>
    <loader-repository>some.dot.com:loader=webtest.ear</loader-repository>
</jboss-app>

The value of the loader-repository element is the JMX object name to assign to the repository created for the EAR. This must be unique and valid JMX ObjectName, but the actual name is not important.

2.2.2.4.3. The Complete Class Loading Model

The previous discussion of the core class loading components introduced the custom UnifiedClassLoader3 and UnifiedLoaderRepository3 classes that form a shared class loading space. The complete class loading picture must also include the parent class loader used by UnifiedClassLoader3s as well as class loaders introduced for scoping and other speciality class loading purposes. Figure 2.5, “A complete class loader view” shows an outline of the class hierarchy that would exist for an EAR deployment containing EJBs and WARs.

A complete class loader view

Figure 2.5. A complete class loader view

The following points apply to this figure:

  • System ClassLoaders: The System ClassLoaders node refers to either the thread context class loader (TCL) of the VM main thread or of the thread of the application that is loading the JBoss server if it is embedded.
  • ServerLoader: The ServerLoader node refers to the a URLClassLoader that delegates to the System ClassLoaders and contains the following boot URLs:

    • All URLs referenced via the jboss.boot.library.list system property. These are path specifications relative to the libraryURL defined by the jboss.lib.url property. If there is no jboss.lib.url property specified, it defaults to jboss.home.url + /lib/. If there is no jboss.boot.library property specified, it defaults to jaxp.jar, log4j-boot.jar, jboss-common.jar, and jboss-system.jar.
    • The JAXP JAR which is either crimson.jar or xerces.jar depending on the -j option to the Main entry point. The default is crimson.jar.
    • The JBoss JMX jar and GNU regex jar, jboss-jmx.jar and gnu-regexp.jar.
    • Oswego concurrency classes JAR, concurrent.jar
    • Any JARs specified as libraries via -L command line options
    • Any other JARs or directories specified via -C command line options
  • Server: The Server node represent a collection of UCLs created by the org.jboss.system.server.Server interface implementation. The default implementation creates UCLs for the patchDir entries as well as the server conf directory. The last UCL created is set as the JBoss main thread context class loader. This will be combined into a single UCL now that multiple URLs per UCL are supported.
  • All UnifiedClassLoader3s: The All UnifiedClassLoader3 node represents the UCLs created by deployers. This covers EARs, jars, WARs, SARs and directories seen by the deployment scanner as well as JARs referenced by their manifests and any nested deployment units they may contain. This is a flat namespace and there should not be multiple instances of a class in different deployment JARs. If there are, only the first loaded will be used and the results may not be as expected. There is a mechanism for scoping visibility based on EAR deployment units that we discussed in Section 2.2.2.4.2, “Scoping Classes”. Use this mechanism if you need to deploy multiple versions of a class in a given JBoss server.
  • EJB DynClassLoader: The EJB DynClassLoader node is a subclass of URLClassLoader that is used to provide RMI dynamic class loading via the simple HTTP WebService. It specifies an empty URL[] and delegates to the TCL as its parent class loader. If the WebService is configured to allow system level classes to be loaded, all classes in the UnifiedLoaderRepository3 as well as the system classpath are available via HTTP.
  • EJB ENCLoader: The EJB ENCLoader node is a URLClassLoader that exists only to provide a unique context for an EJB deployment's java:comp JNDI context. It specifies an empty URL[] and delegates to the EJB DynClassLoader as its parent class loader.
  • Web ENCLoader: The Web ENCLoader node is a URLClassLoader that exists only to provide a unique context for a web deployment's java:comp JNDI context. It specifies an empty URL[] and delegates to the TCL as its parent class loader.
  • WAR Loader: The WAR Loader is a servlet container specific classloader that delegates to the Web ENCLoader as its parent class loader. The default behavior is to load from its parent class loader and then the WAR WEB-INFclasses and lib directories. If the servlet 2.3 class loading model is enabled it will first load from the its WEB-INF directories and then the parent class loader.

In its current form there are some advantages and disadvantages to the JBoss class loading architecture. Advantages include:

  • Classes do not need to be replicated across deployment units in order to have access to them.
  • Many future possibilities including novel partitioning of the repositories into domains, dependency and conflict detection, etc.

Disadvantages include:

  • Existing deployments may need to be repackaged to avoid duplicate classes. Duplication of classes in a loader repository can lead to class cast exceptions and linkage errors depending on how the classes are loaded.
  • Deployments that depend on different versions of a given class need to be isolated in separate EARs and a unique HeirarchicalLoaderRepository3 defined using a jboss-app.xml descriptor.

2.2.3. JBoss XMBeans

XMBeans are the JBoss JMX implementation version of the JMX model MBean. XMBeans have the richness of the dynamic MBean metadata without the tedious programming required by a direct implementation of the DynamicMBean interface. The JBoss model MBean implementation allows one to specify the management interface of a component through a XML descriptor, hence the X in XMBean. In addition to providing a simple mechanism for describing the metadata required for a dynamic MBean, XMBeans also allow for the specification of attribute persistence, caching behavior, and even advanced customizations like the MBean implementation interceptors. The high level elements of the jboss_xmbean_1_0.dtd for the XMBean descriptor is given in Figure 2.6, “The JBoss 1.0 XMBean DTD Overview (jboss_xmbean_1_0.dtd)”.

The JBoss 1.0 XMBean DTD Overview (jboss_xmbean_1_0.dtd)

Figure 2.6. The JBoss 1.0 XMBean DTD Overview (jboss_xmbean_1_0.dtd)

The mbean element is the root element of the document containing the required elements for describing the management interface of one MBean (constructors, attributes, operations and notifications). It also includes an optional description element, which can be used to describe the purpose of the MBean, as well as an optional descriptors element which allows for persistence policy specification, attribute caching, etc.

2.2.3.1. Descriptors

The descriptors element contains all the descriptors for a containing element, as subelements. The descriptors suggested in the JMX specification as well as those used by JBoss have predefined elements and attributes, whereas custom descriptors have a generic descriptor element with name and value attributes as show in Figure 2.7, “ The descriptors element content model”.

The descriptors element content model

Figure 2.7.  The descriptors element content model

The key descriptors child elements include:

  • interceptors: The interceptors element specifies a customized stack of interceptors that will be used in place of the default stack. Currently this is only used when specified at the MBean level, but it could define a custom attribute or operation level interceptor stack in the future. The content of the interceptors element specifies a custom interceptor stack. If no interceptors element is specified the standard ModelMBean interceptors will be used. The standard interceptors are:

    • org.jboss.mx.interceptor.PersistenceInterceptor
    • org.jboss.mx.interceptor.MBeanAttributeInterceptor
    • org.jboss.mx.interceptor.ObjectReferenceInterceptor

    When specifying a custom interceptor stack you would typically include the standard interceptors along with your own unless you are replacing the corresponding standard interceptor.

    Each interceptor element content value specifies the fully qualified class name of the interceptor implementation, and the class must implement the org.jboss.mx.interceptor.Interceptor interface. The interceptor class must also have either a no-arg constructor, or a constructor that accepts a (javax.management.MBeanInfo, org.jboss.mx.server.MBeanInvoker) pair.

    The interceptor elements may have any number of attributes that correspond to JavaBean style properties on the interceptor class implementation. For each interceptor element attribute specified, the interceptor class is queried for a matching setter method. The attribute value is converted to the true type of the interceptor class property using the java.beans.PropertyEditor associated with the type. It is an error to specify an attribute for which there is no setter or PropertyEditor.

  • persistence: The persistence element allows the specification of the persistPolicy, persistPeriod, persistLocation, and persistName persistence attributes suggested by the JMX specification. The persistence element attributes are:

    • persistPolicy: The persistPolicy attribute defines when attributes should be persisted and its value must be one of

      • Never: attribute values are transient values that are never persisted
      • OnUpdate: attribute values are persisted whenever they are updated
      • OnTimer: attribute values are persisted based on the time given by the persistPeriod.
      • NoMoreOftenThan: attribute values are persisted when updated unless but no more oten than the persistPeriod.
    • persistPeriod: The persistPeriod attribute gives the update frequency in milliseconds if the perisitPolicy attribute is NoMoreOftenThan or OnTimer.
    • persistLocation: The persistLocation attribute specifies the location of the persistence store. Its form depends on the JMX peristence implementation. Currently this should refer to a directory into which the attributes will be serialized if using the default JBoss persistence manager.
    • persistName: The persistName attribute can be used in conjunction with the persistLocation attribute to further qualify the persistent store location. For a directory persistLocation the persistName specifies the file to which the attributes are stored within the directory.
  • currencyTimeLimit: The currencyTimeLimit element specifies the time in seconds that a cached value of an attribute remains valid. Its value attribute gives the time in seconds. A 0 value indicates that an attribute value should always be retrieved from the MBean and never cached. A -1 value indicates that a cache value is always valid.
  • state-action-on-update: The state-action-on-update element specifies the what happens to an MBean when one of its attributes is updated. The action is given by the value attribute. Its value attribute defines what happens to the mbean lifecycle state when one of its attributes is update. It must be one of: keep-running, restart, reconfigure, reinstantiate. However, note that this descriptor is not currently used.
  • display-name: The display-name element specifies the human friendly name of an item.
  • default: The default element specifes a default value to use when a field has not been set. Note that this value is not written to the MBean on startup as is the case with the jboss-service.xml attribute element content value. Rather, the default value is used only if there is no attribute accessor defined, and there is no value element defined.
  • value: The value element specifies a management attribute's current value. Unlike the default element, the value element is written through to the MBean on startup provided there is a setter method available.
  • persistence-manager: The persistence-manager element gives the name of a class to use as the persistence manager. The value attribute specifies the class name that supplies the org.jboss.mx.persistence.PersistenceManager interface implementation. The only implementation currently supplied by JBoss is the org.jboss.mx.persistence.ObjectStreamPersistenceManager which serializes the ModelMBeanInfo content to a file using Java serialization.
  • descriptor: The descriptor element specifies an arbitrary descriptor not known to JBoss. Its name attribute specifies the type of the descriptor and its value attribute specifies the descriptor value. The descriptor element allows for the attachment of arbitrary management metadata.

Note that any of the constructor, attribute, operation or notification elements may have a descriptors element to specify the specification defined descriptors as well as arbitrary extension descriptor settings.

2.2.3.2. The Management Class

The class element specifies the fully qualified name of the managed object whose management interface is described by the XMBean descriptor.

2.2.3.3. The Constructors

The constructor element(s) specifies the constructors available for creating an instance of the managed object. The constructor element and its content model are shown in Figure 2.8, “The XMBean constructor element and its content model”.

The XMBean constructor element and its content model

Figure 2.8. The XMBean constructor element and its content model

The key child elements are:

  • description: A description of the constructor.
  • name: The name of the constructor, which must be the same as the implementation class.
  • parameter: The parameter element describes a constructor parameter. The parameter element has the following attributes:

    • description: An optional description of the parameter.
    • name: The required variable name of the parameter.
    • type: The required fully qualified class name of the parameter type.
  • descriptors: Any descriptors to associate with the constructor metadata.

2.2.3.4. The Attributes

The attribute element(s) specifies the management attributes exposed by the MBean. The attribute element and its content model are shown in Figure 2.9, “The XMBean attribute element and its content model”.

The XMBean attribute element and its content model

Figure 2.9. The XMBean attribute element and its content model

The attribute element supported attributes include:

  • access: The optional access attribute defines the read/write access modes of an attribute. It must be one of:

    • read-only: The attribute may only be read.
    • write-only: The attribute may only be written.
    • read-write: The attribute is both readable and writable. This is the implied default.
  • getMethod: The getMethod attribute defines the name of the method which reads the named attribute. This must be specified if the managed attribute should be obtained from the MBean instance.
  • setMethod: The setMethod attribute defines the name of the method which writes the named attribute. This must be specified if the managed attribute should be obtained from the MBean instance.

The key child elements of the attribute element include:

  • description: A description of the attribute.
  • name: The name of the attribute as would be used in the MBeanServer.getAttribute() operation.
  • type: The fully qualified class name of the attribute type.
  • descriptors: Any additional desciptors that affect the attribute persistence, caching, default value, etc.

2.2.3.5. The Operations

The management operations exposed by the XMBean are specified via one or more operation elements. The operation element and its content model are shown in Figure 2.10, “The XMBean operation element and its content model”.

The XMBean operation element and its content model

Figure 2.10. The XMBean operation element and its content model

The impact attribute defines the impact of executing the operation and must be one of:

  • ACTION: The operation changes the state of the MBean component (write operation)
  • INFO: The operation should not alter the state of the MBean component (read operation).
  • ACTION_INFO: The operation behaves like a read/write operation.

The child elements are:

  • description: This element specifies a human readable description of the operation.
  • name: This element contains the operation's name
  • parameter: This element describes the operation's signature.
  • return-type: This element contains a fully qualified class name of the return type from this operation. If not specified, it defaults to void.
  • descriptors: Any descriptors to associate with the operation metadata.

2.2.3.6. Notifications

The notification element(s) describes the management notifications that may be emitted by the XMBean. The notification element and its content model is shown in Figure 2.11, “The XMBean notification element and content model”.

The XMBean notification element and content model

Figure 2.11. The XMBean notification element and content model

The child elements are:

  • description: This element gives a human readable description of the notification.
  • name: This element contains the fully qualified name of the notification class.
  • notification-type: This element contains the dot-separated notification type string.
  • descriptors: Any descriptors to associate with the notification metadata.

For a reference of the complete DTD content model see the expanded view of the complete provided in Figure 2.12, “An expanded view of the jboss_xmbean_1_0 DTD”. We will work through examples of creating an XMBeans when we discuss the JBoss MBean services notion. See Section 2.4.3.2, “XMBean Examples” for these examples.

An expanded view of the jboss_xmbean_1_0 DTD

Figure 2.12. An expanded view of the jboss_xmbean_1_0 DTD

2.3. Connecting to the JMX Server

JBoss includes adaptors that allow access to the JMX MBeanServer from outside of the JBoss server VM. The current adaptors include HTML, an RMI interface, and an EJB.

2.3.1. Inspecting the Server - the JMX Console Web Application

JBoss comes with its own implementation of a JMX HTML adaptor that allows one to view the server's MBeans using a standard web browser. The default URL for the console web application is http://localhost:8080/jmx-console/. If you browse this location you will see something similar to that presented in Figure 2.13, “The JBoss JMX console web application agent view”.

The JBoss JMX console web application agent view

Figure 2.13. The JBoss JMX console web application agent view

The top view is called the agent view and it provides a listing of all MBeans registered with the MBeanServer sorted by the domain portion of the MBean's ObjectName. Under each domain are the MBeans under that domain. When you select one of the MBeans you will be taken to the MBean view. This allows one to view and edit an MBean's attributes as well as invoke operations. As an example, Figure 2.14, “The MBean view for the "jboss.system:type=Server" MBean” shows the MBean view for the jboss.system:type=Server MBean.

The MBean view for the "jboss.system:type=Server" MBean

Figure 2.14. The MBean view for the "jboss.system:type=Server" MBean

The source code for the JMX console web application is located in the varia module under the src/main/org/jboss/jmx directory. Its web pages are located under varia/src/resources/jmx. The application is a simple MVC servlet with JSP views that utilize the MBeanServer.

2.3.1.1. Securing the JMX Console

Since the JMX console web application is just a standard servlet, it may be secured using standard J2EE role based security. The jmx-console.war that is deployed as an unpacked WAR that includes template settings for quickly enabling simple username and password based access restrictions. If you look at the jmx-console.war in the server/default/deploy directory you will find the web.xml and jboss-web.xml descriptors in the WEB-INF directory and a jmx-console-roles.properties and jmx-console-users.properties file under WEB-INF/classes

By uncommenting the security sections of the web.xml and jboss-web.xml descriptors as shown in Example 2.11, “The jmx-console.war web.xml descriptors with the security elements uncommented.”, you enable HTTP basic authentication that restricts access to the jmx-console application to the user admin with password admin. The username and password are determined by the admin=admin line in the jmx-console-users.properties file.

Example 2.11. The jmx-console.war web.xml descriptors with the security elements uncommented.

<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC
          "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
          "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
    <!-- ... -->
    
    <!-- A security constraint that restricts access to the HTML JMX console
         to users with the role JBossAdmin. Edit the roles to what you want and
         uncomment the WEB-INF/jboss-web.xml/security-domain element to enable
         secured access to the HTML JMX console.
    -->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>HtmlAdaptor</web-resource-name>
            <description> An example security config that only allows users with
                the role JBossAdmin to access the HTML JMX console web
                application </description>
            <url-pattern>/*</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
        </web-resource-collection>
        <auth-constraint>
            <role-name>JBossAdmin</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>JBoss JMX Console</realm-name>
    </login-config>
    <security-role>
        <role-name>JBossAdmin</role-name>
    </security-role>
</web-app>

Example 2.12. The jmx-console.war jboss-web.xml descriptors with the security elements uncommented.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web
    PUBLIC "-//JBoss//DTD Web Application 2.3//EN"
    "http://www.jboss.org/j2ee/dtd/jboss-web_3_0.dtd">
<jboss-web>
    <!-- 
        Uncomment the security-domain to enable security. You will
        need to edit the htmladaptor login configuration to setup the
        login modules used to authentication users.      
    -->
    <security-domain>java:/jaas/jmx-console</security-domain>
</jboss-web>

Make these changes and then when you try to access the JMX Console URL. You will see a dialog similar to that shown in Figure 2.15, “The jmx-console basic HTTP login dialog.”.

The jmx-console basic HTTP login dialog.

Figure 2.15. The jmx-console basic HTTP login dialog.

Its generally a bad idea to use the properties files for securing access to the JMX console application. To see how to properly configure the security settings of web applications see Chapter 8, Security on JBoss.

2.3.2. Connecting to JMX Using RMI

JBoss supplies an RMI interface for connecting to the JMX MBeanServer. This interface is org.jboss.jmx.adaptor.rmi.RMIAdaptor, and it is shown in Example 2.13, “The RMIAdaptor interface”.

Example 2.13. The RMIAdaptor interface

/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.jmx.adaptor.rmi;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.ObjectInstance;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.MBeanInfo;

import javax.management.AttributeNotFoundException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.InvalidAttributeValueException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanRegistrationException;
import javax.management.NotCompliantMBeanException;
import javax.management.OperationsException;
import javax.management.ReflectionException;

public interface RMIAdaptor
    extends java.rmi.Remote
{
    
    public ObjectInstance createMBean(String pClassName,
                                      ObjectName pName)
        throws ReflectionException,
               InstanceAlreadyExistsException,
               MBeanRegistrationException,
               MBeanException,
               NotCompliantMBeanException,
               RemoteException;
    
    public ObjectInstance createMBean(String pClassName,
                                      ObjectName pName,
                                      ObjectName pLoaderName)
        throws ReflectionException,
               InstanceAlreadyExistsException,
               MBeanRegistrationException,
               MBeanException,
               NotCompliantMBeanException,
               InstanceNotFoundException,
               RemoteException;
    
    public ObjectInstance createMBean(String pClassName,
                                      ObjectName pName,
                                      Object[] pParams, 
                                      String[] pSignature)
        throws ReflectionException,
               InstanceAlreadyExistsException,
               MBeanRegistrationException,
               MBeanException,
               NotCompliantMBeanException,
               RemoteException;
    
    public ObjectInstance createMBean(String pClassName,
                                      ObjectName pName,
                                      ObjectName pLoaderName, 
                                      Object[] pParams, 
                                      String[] pSignature)
        throws ReflectionException,
               InstanceAlreadyExistsException,
               MBeanRegistrationException,
               MBeanException,
               NotCompliantMBeanException,
               InstanceNotFoundException,
               RemoteException;
    
    public void unregisterMBean(ObjectName pName)
        throws InstanceNotFoundException,
               MBeanRegistrationException,
               RemoteException;
    
    public ObjectInstance getObjectInstance(ObjectName pName)
        throws InstanceNotFoundException,
               RemoteException;
    
    public Set queryMBeans(ObjectName pName, QueryExp pQuery)
        throws RemoteException;
    
    public Set queryNames(ObjectName pName, QueryExp pQuery)
        throws RemoteException;
    
    public boolean isRegistered(ObjectName pName)
        throws RemoteException;
    
    public boolean isInstanceOf(ObjectName pName, String pClassName)
        throws InstanceNotFoundException,
               RemoteException;
                
    public Integer getMBeanCount()
        throws RemoteException;
    
    public Object getAttribute(ObjectName pName, String pAttribute)
        throws MBeanException,
               AttributeNotFoundException,
               InstanceNotFoundException,
               ReflectionException,
               RemoteException;
    
    public AttributeList getAttributes(ObjectName pName,
                                       String[] pAttributes)
        throws InstanceNotFoundException,
               ReflectionException,
               RemoteException;
    
    public void setAttribute(ObjectName pName, Attribute pAttribute)
        throws InstanceNotFoundException,
               AttributeNotFoundException,
               InvalidAttributeValueException,
               MBeanException,
               ReflectionException,
               RemoteException;
    
    public AttributeList setAttributes(ObjectName pName,
                                       AttributeList pAttributes)
        throws InstanceNotFoundException,
               ReflectionException,
               RemoteException;
    
    public Object invoke(ObjectName pName, String pActionName,
                         Object[] pParams, String[] pSignature)
        throws InstanceNotFoundException,
               MBeanException,
               ReflectionException,
               RemoteException;
    
    public String getDefaultDomain()
        throws RemoteException;
                
    public void addNotificationListener(ObjectName pName,
                                        ObjectName pListener,
                                        NotificationFilter pFilter, 
                                        Object pHandback)
        throws InstanceNotFoundException,
               RemoteException;
    
    public void removeNotificationListener(ObjectName pName,
                                           ObjectName pListener)
        throws InstanceNotFoundException,
               ListenerNotFoundException,
               RemoteException;
    
    public MBeanInfo getMBeanInfo(ObjectName pName)
        throws InstanceNotFoundException,
               IntrospectionException,
               ReflectionException,
               RemoteException;
    
}

The RMIAdaptor interface was bound into JNDI by the org.jboss.jmx.adaptor.rmi.RMIAdaptorService MBean, but as of 3.2.2 this service has been removed from the dist deploy directory by default. It can still be found in the docs/examples/jmx directory, but it has been deprecated in favor of the invoker adaptor service. This service also supports the RMIAdaptor interface and its configuration also provides a binding of this interface in the default location of jmx/rmi/RMIAdaptor for backwards compatibility with existing clients. The RMIAdaptorService still has utility for remote clients that need to receive JMX notifications. The invoker adaptor service does not yet suppor this capability so if this is required, the jmx-invoker-adaptor-server.sar must be replaced with the jmx-rmi-adaptor.sar from the examples directory.

The RMIAdaptorService is deployed as the jmx-rmi-adaptor.sar package, and supports the following attributes:

  • JndiName: The JNDI name under which the RMIAdaptor interface will be bound. The default name is jmx/rmi/RMIAdaptor.
  • RMIObjectPort: The server side listening port number for the exported RMI object. This defaults to 0 meaning choose an anonymous available port.
  • ServerAddress: The server interface name or IP address to bind the export RMI listening port to. This defaults to an empty value meaning to bind on all available interfaces.
  • Backlog: The RMI object server socket backlog of client connection requests that will be accepted before a connection error occurs.

Example 2.14, “ A JMX client that uses the RMIAdaptor” shows a client that makes use of the RMIAdaptor interface to query the MBeanInfo for the JNDIView MBean. It also invokes the MBean's list(boolean) method and displays the result.

Example 2.14.  A JMX client that uses the RMIAdaptor

public class JMXBrowser
{
    /**
     * @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 MBeanInfo for the JNDIView MBean
	ObjectName name = new ObjectName("jboss:service=JNDIView");
	MBeanInfo info = server.getMBeanInfo(name);
	System.out.println("JNDIView Class: "+info.getClassName());

	MBeanOperationInfo[] opInfo = info.getOperations();
	System.out.println("JNDIView Operations: ");

	for (int o = 0; o < opInfo.length; o ++) {
	    MBeanOperationInfo op = opInfo[o];

	    String returnType = op.getReturnType();
	    String opName = op.getName();

	    System.out.print(" +"+returnType+" "+opName+"(");

	    MBeanParameterInfo[] params = op.getSignature();
	    for (int p = 0; p < params.length; p++) {
		MBeanParameterInfo paramInfo = params[p];

		String pname = paramInfo.getName();
		String type = paramInfo.getType();

		if (pname.equals(type)) {
		    System.out.print(type);
		} else {
		    System.out.print(type+" "+name);
		}

		if (p < params.length-1) {
		    System.out.print(',');
		}
	    }
	    System.out.println(")");
	}
                
	// Invoke the list(boolean) op
	String[] sig = {"boolean"};
	Object[] opArgs = {Boolean.TRUE};
	Object result = server.invoke(name,
				      "list", opArgs, sig);
	System.out.println("JNDIView.list(true) output:\n"+result);
    }
}

To test the client access using the RMIAdaptor, run the following:

[orb@toki examples]$ ant -Dchap=chap2 -Dex=4 run-example
Buildfile: build.xml
 
...
                 
run-example4:
     [java] JNDIView Class: org.jboss.mx.modelmbean.XMBean
     [java] JNDIView Operations: 
     [java]  + java.lang.String list(boolean jboss:service=JNDIView)
     [java]  + java.lang.String listXML()
     [java]  + void create()
     [java]  + void start()
     [java]  + void stop()
     [java]  + void destroy()
     [java]  + void jbossInternalLifecycle(java.lang.String jboss:service=JNDIView)
     [java]  + java.lang.String getName()
     [java]  + int getState()
     [java]  + java.lang.String getStateString()
     [java] JNDIView.list(true) output:
     [java] <h1>java: Namespace</h1>
     [java] <pre>
     [java]   +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
     [java]   +- DefaultDS (class: org.jboss.resource.adapter.jdbc.WrapperDataSource)
     [java]   +- SecurityProxyFactory (class: org.jboss.security.SubjectSecurityProxyFacto
ry)
     [java]   +- DefaultJMSProvider (class: org.jboss.jms.jndi.JNDIProviderAdapter)
     [java]   +- comp (class: javax.naming.Context)
     [java]   +- JmsXA (class: org.jboss.resource.adapter.jms.JmsConnectionFactoryImpl)
     [java]   +- ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
     [java]   +- jaas (class: javax.naming.Context)
     [java]   |   +- JmsXARealm (class: org.jboss.security.plugins.SecurityDomainContext)
     [java]   |   +- jbossmq (class: org.jboss.security.plugins.SecurityDomainContext)
     [java]   |   +- HsqlDbRealm (class: org.jboss.security.plugins.SecurityDomainContext)
     [java]   +- timedCacheFactory (class: javax.naming.Context)
     [java] Failed to lookup: timedCacheFactory, errmsg=null
     [java]   +- TransactionPropagationContextExporter (class: org.jboss.tm.TransactionPro
pagationContextFactory)
     [java]   +- Mail (class: javax.mail.Session)
     [java]   +- StdJMSPool (class: org.jboss.jms.asf.StdServerSessionPoolFactory)
     [java]   +- TransactionPropagationContextImporter (class: org.jboss.tm.TransactionPro
pagationContextImporter)
     [java]   +- TransactionManager (class: org.jboss.tm.TxManager)
     [java] </pre>
     [java] <h1>Global JNDI Namespace</h1>
     [java] <pre>
     [java]   +- HAILConnectionFactory[link -> ConnectionFactory] (class: javax.naming.
                LinkRef)
     [java]   +- jmx (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- invoker (class: org.jnp.interfaces.NamingContext)
     [java]   |   |   +- RMIAdaptor (proxy: $Proxy26 implements interface org.jboss.jmx.ad
                aptor.rmi.RMIAdaptor,interface org.jboss.jmx.adaptor.rmi.RMIAdaptorExt)
     [java]   |   +- rmi (class: org.jnp.interfaces.NamingContext)
     [java]   |   |   +- RMIAdaptor[link -> jmx/invoker/RMIAdaptor] (class: javax.namin
g.LinkRef)
     [java]   +- HTTPXAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
     [java]   +- ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
     [java]   +- UserTransactionSessionFactory (proxy: $Proxy13 implements interface org.j
                boss.tm.usertx.interfaces.UserTransactionSessionFactory)
     [java]   +- HTTPConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
     [java]   +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
     [java]   +- invokers (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- 0.0.0.0 (class: org.jnp.interfaces.NamingContext)
     [java]   |   |   +- pooled (class: org.jboss.invocation.pooled.interfaces.PooledInvok
erProxy)
     [java]   +- UserTransaction (class: org.jboss.tm.usertx.client.ClientUserTransaction)
     [java]   +- UILXAConnectionFactory[link -> XAConnectionFactory] (class: javax.nami
ng.LinkRef)
     [java]   +- HAILXAConnectionFactory[link -> XAConnectionFactory] (class: javax.nam
ing.LinkRef)
     [java]   +- UIL2XAConnectionFactory[link -> XAConnectionFactory] (class: javax.nam
ing.LinkRef)
     [java]   +- queue (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- A (class: org.jboss.mq.SpyQueue)
     [java]   |   +- testQueue (class: org.jboss.mq.SpyQueue)
     [java]   |   +- ex (class: org.jboss.mq.SpyQueue)
     [java]   |   +- DLQ (class: org.jboss.mq.SpyQueue)
     [java]   |   +- D (class: org.jboss.mq.SpyQueue)
     [java]   |   +- C (class: org.jboss.mq.SpyQueue)
     [java]   |   +- B (class: org.jboss.mq.SpyQueue)
     [java]   +- topic (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- testDurableTopic (class: org.jboss.mq.SpyTopic)
     [java]   |   +- testTopic (class: org.jboss.mq.SpyTopic)
     [java]   |   +- securedTopic (class: org.jboss.mq.SpyTopic)
     [java]   +- console (class: org.jnp.interfaces.NamingContext)
     [java]   |   +- PluginManager (proxy: $Proxy27 implements interface org.jboss.console
.manager.PluginManagerMBean)
     [java]   +- UIL2ConnectionFactory[link -> ConnectionFactory] (class: javax.naming.
LinkRef)
     [java]   +- UILConnectionFactory[link -> ConnectionFactory] (class: javax.naming.L
inkRef)
     [java]   +- UUIDKeyGeneratorFactory (class: org.jboss.ejb.plugins.keygenerator.uuid.U
UIDKeyGeneratorFactory)
     [java] </pre>

2.3.3. Command Line Access to JMX

JBoss provides a simple command line tool that allows for interaction with a remote JMX server instance. This tool is called twiddle (for twiddling bits via JMX) and is located in the bin directory of the distribution. Twiddle is a command execution tool, not a general command shell. It is run using either the twiddle.sh or twiddle.bat scripts, and passing in a -h(--help) argument provides the basic syntax, and --help-commands shows what you can do with the tool:

[nr@toki bin]$ ./twiddle.sh -h
A JMX client to 'twiddle' with a remote JBoss server.
 
usage: twiddle.sh [options] <command> [command_arguments]
 
options:
-h, --help Show this help message
--help-commands Show a list of commands
-H=<command> Show command specific help
-c=command.properties Specify the command.properties file to use
-D<name>[=<value>] Set a system property
-- Stop processing options
-s, --server=<url> The JNDI URL of the remote server
-a, --adapter=<name> The JNDI name of the RMI adapter to use
[nr@toki bin]$ ./twiddle.sh --help-commands
twiddle.sh commands:
get Get the values of one or more MBean attributes
invoke Invoke an operation on an MBean
unregister Unregister one or more MBeans
create Create an MBean
serverinfo Get information about the MBean server
query Query the server for a list of matching MBeans
info Get the metadata for an MBean

2.3.3.1. Connecting twiddle to a Remote Server

By default the twiddle command will connect to the localhost at port 1099 to lookup the default jmx/rmi/RMIAdaptor binding of the RMIAdaptor service as the connector for communicating with the JMX server. To connect to a different server/port combination you can use the -s (--server) option:

[nr@rubik bin]$ ./twiddle.sh -s toki serverinfo -d jboss
[nr@rubik bin]$ ./twiddle.sh -s toki:1099 serverinfo -d jboss

To connect using a different RMIAdaptor binding use the -a (--adapter) option:

[nr@rubik bin]$ ./twiddle.sh -s toki -a jmx/rmi/RMIAdaptor serverinfo -d jboss
[nr@rubik bin]$ ./twiddle.sh -s toki --adapter=jmx/rmi/RMIAdaptor serverinfo -d jboss

2.3.3.2. Sample twiddle Command Usage

To access basic information about a server, use the serverinfo command. This currently supports:

[nr@toki bin]$ ./twiddle.sh -H serverinfo
Get information about the MBean server
 
usage: serverinfo [options]
 
options:
-d, --domain Get the default domain
-c, --count Get the MBean count
-l, --list List the MBeans
-- Stop processing options
 
[nr@rubik bin]$ ./twiddle.sh --server=toki serverinfo --count
385
[nr@rubik bin]$ ./twiddle.sh --server=toki serverinfo --domain
jboss

To query the server for the name of MBeans matching a pattern, use the query command. This currently supports:

[nr@rubik bin]$ ./twiddle.sh -H query
Query the server for a list of matching MBeans
 
usage: query [options] <query>
options:
    -c, --count    Display the matching MBean count
    --             Stop processing options
Examples:
 query all mbeans: query '*:*'
 query all mbeans in the jboss.j2ee domain: query 'jboss.j2ee:*'
[nr@rubik bin]$ ./twiddle.sh -s toki query 'jboss:service=invoker,*'
jboss:readonly=true,service=invoker,target=Naming,type=http
jboss:service=invoker,type=jrmp
jboss:service=invoker,type=httpHA
jboss:service=invoker,type=local
jboss:service=invoker,socketType=SSL,type=jrmp
jboss:service=invoker,type=pooled
jboss:service=invoker,type=http
jboss:service=invoker,target=Naming,type=http

To get the attributes of an MBean, use the get command:

[nr@toki bin]$ ./twiddle.sh -H get
Get the values of one or more MBean attributes
 
usage: get [options] <name> [<attr>+]
  If no attribute names are given all readable attributes are retrieved
options:
    --noprefix    Do not display attribute name prefixes
    --            Stop processing options
[orb@toki bin]$ ./twiddle.sh get jboss:service=invoker,type=jrmp RMIObjectPort StateString
RMIObjectPort=4444
StateString=Started
[nr@toki bin]$ ./twiddle.sh get jboss:service=invoker,type=jrmp
ServerAddress=0.0.0.0
StateString=Started
State=3
EnableClassCaching=false
SecurityDomain=null
RMIServerSocketFactory=null
Backlog=200
RMIObjectPort=4444
Name=JRMPInvoker
RMIClientSocketFactory=null

To query the MBeanInfo for an MBean, use the info command:

[nr@toki bin]$ ./twiddle.sh -H info
Get the metadata for an MBean
 
usage: info <mbean-name>
  Use '*' to query for all attributes
[nr@toki bin]$ ./twiddle.sh info jboss:service=invoker,type=jrmp
Description: Management Bean.
+++ Attributes:
 Name: ServerAddress
 Type: java.lang.String
 Access: rw
 Name: StateString
 Type: java.lang.String
 Access: r-
 Name: State
 Type: int
 Access: r-
 Name: EnableClassCaching
 Type: boolean
 Access: rw
 Name: SecurityDomain
 Type: java.lang.String
 Access: rw
 Name: RMIServerSocketFactory
 Type: java.lang.String
 Access: rw
 Name: Backlog
 Type: int
 Access: rw
 Name: RMIObjectPort
 Type: int
 Access: rw
 Name: Name
 Type: java.lang.String
 Access: r-
 Name: RMIClientSocketFactory
 Type: java.lang.String
 Access: rw
+++ Operations:
 void start()
 void jbossInternalLifecycle(java.lang.String java.lang.String)
 void destroy()
 void create()
 void stop()

To invoke an operation on an MBean, use the invoker command:

[nr@toki bin]$ ./twiddle.sh -H invoke
Invoke an operation on an MBean
 
usage: invoke [options] <query> <operation> (<arg>)*

options:
    -q, --query-type[=<type>]    Treat object name as a query
    --                           Stop processing options

query type:
    f[irst]    Only invoke on the first matching name [default]
    a[ll]      Invoke on all matching names
[nr@toki bin]$ 
<h1>java: Namespace</h1>
<pre>
  +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
  +- DefaultDS (class: org.jboss.resource.adapter.jdbc.WrapperDataSource)
  +- SecurityProxyFactory (class: org.jboss.security.SubjectSecurityProxyFactory)
  +- DefaultJMSProvider (class: org.jboss.jms.jndi.JNDIProviderAdapter)
  +- comp (class: javax.naming.Context)
  +- JmsXA (class: org.jboss.resource.adapter.jms.JmsConnectionFactoryImpl)
  +- ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
  +- jaas (class: javax.naming.Context)
  |   +- JmsXARealm (class: org.jboss.security.plugins.SecurityDomainContext)
  |   +- jbossmq (class: org.jboss.security.plugins.SecurityDomainContext)
  |   +- HsqlDbRealm (class: org.jboss.security.plugins.SecurityDomainContext)
  +- timedCacheFactory (class: javax.naming.Context)
Failed to lookup: timedCacheFactory, errmsg=null
  +- TransactionPropagationContextExporter (class: org.jboss.tm.TransactionPropagationCont
extFactory)
  +- Mail (class: javax.mail.Session)
  +- StdJMSPool (class: org.jboss.jms.asf.StdServerSessionPoolFactory)
  +- TransactionPropagationContextImporter (class: org.jboss.tm.TransactionPropagationCont
extImporter)
  +- TransactionManager (class: org.jboss.tm.TxManager)
</pre>
<h1>Global JNDI Namespace</h1>
<pre>
  +- HAILConnectionFactory[link -> ConnectionFactory] (class: javax.naming.LinkRef)
  +- jmx (class: org.jnp.interfaces.NamingContext)
  |   +- invoker (class: org.jnp.interfaces.NamingContext)
  |   |   +- RMIAdaptor (proxy: $Proxy26 implements interface org.jboss.jmx.adaptor.rmi.RM
IAdaptor,interface org.jboss.jmx.adaptor.rmi.RMIAdaptorExt)
  |   +- rmi (class: org.jnp.interfaces.NamingContext)
  |   |   +- RMIAdaptor[link -> jmx/invoker/RMIAdaptor] (class: javax.naming.LinkRef)
  +- HTTPXAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
  +- ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
  +- UserTransactionSessionFactory (proxy: $Proxy13 implements interface org.jboss.tm.user
tx.interfaces.UserTransactionSessionFactory)
  +- HTTPConnectionFactory (class: org.jboss.mq.SpyConnectionFactory)
  +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory)
  +- invokers (class: org.jnp.interfaces.NamingContext)
  |   +- 0.0.0.0 (class: org.jnp.interfaces.NamingContext)
  |   |   +- pooled (class: org.jboss.invocation.pooled.interfaces.PooledInvokerProxy)
  +- UserTransaction (class: org.jboss.tm.usertx.client.ClientUserTransaction)
  +- UILXAConnectionFactory[link -> XAConnectionFactory] (class: javax.naming.LinkRef)
  +- HAILXAConnectionFactory[link -> XAConnectionFactory] (class: javax.naming.LinkRef)
  +- UIL2XAConnectionFactory[link -> XAConnectionFactory] (class: javax.naming.LinkRef)
  +- queue (class: org.jnp.interfaces.NamingContext)
  |   +- A (class: org.jboss.mq.SpyQueue)
  |   +- testQueue (class: org.jboss.mq.SpyQueue)
  |   +- ex (class: org.jboss.mq.SpyQueue)
  |   +- DLQ (class: org.jboss.mq.SpyQueue)
  |   +- D (class: org.jboss.mq.SpyQueue)
  |   +- C (class: org.jboss.mq.SpyQueue)
  |   +- B (class: org.jboss.mq.SpyQueue)
  +- topic (class: org.jnp.interfaces.NamingContext)
  |   +- testDurableTopic (class: org.jboss.mq.SpyTopic)
  |   +- testTopic (class: org.jboss.mq.SpyTopic)
  |   +- securedTopic (class: org.jboss.mq.SpyTopic)
  +- console (class: org.jnp.interfaces.NamingContext)
  |   +- PluginManager (proxy: $Proxy27 implements interface org.jboss.console.manager.Plu
ginManagerMBean)
  +- UIL2ConnectionFactory[link -> ConnectionFactory] (class: javax.naming.LinkRef)
  +- UILConnectionFactory[link -> ConnectionFactory] (class: javax.naming.LinkRef)
  +- UUIDKeyGeneratorFactory (class: org.jboss.ejb.plugins.keygenerator.uuid.UUIDKeyGenera
torFactory)
</pre>

2.3.4. Connecting to JMX Using Any Protocol

With the detached invokers and a somewhat generalized proxy factory capability, you can really talk to the JMX server using the InvokerAdaptorService and a proxy factory service to expose an RMIAdaptor or similar interface over your protocol of choice. We will introduce the detached invoker notion along with proxy factories in Section 2.7, “Remote Access to Services, Detached Invokers”. See Section 2.7.1, “A Detached Invoker Example, the MBeanServer Invoker Adaptor Service” for an example of an invoker service that allows one to access the MBean server using to the RMIAdaptor interface over any protocol for which a proxy factory service exists.

2.4. 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. The kernel in that sense is only an 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.

2.4.1. The Startup Process

In this section we will describe the JBoss server startup process. A summary of the steps that occur during the JBoss server startup sequence is:

  1. The run start 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 belonging 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:

    • Creating a java.net.URLClassLoader 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 class name 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 using the URLClassLoader created in step 6 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 steps:

    • Set the thread context class loader to the class loader used to load the ServerImpl class.
    • Create an MBeanServer under the jboss domain using the MBeanServerFactory.createMBeanServer(String) method.
    • Register the ServerImpl and ServerConfigImpl MBeans with the MBean server.
    • Initialize 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 UnifiedClassLoader 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. The ServiceController manages the JBoss MBean services life cycle. We will discuss the JBoss MBean services notion in detail in Section 2.4.2, “JBoss MBean Services”.
    • The org.jboss.deployment.MainDeployer is created and started. The MainDeployer manages deployment dependencies and directing deployments to the correct deployer.
    • The org.jboss.deployment.JARDeployer is created and started. The JARDeployer handles the deployment of JARs that are simple library JARs.
    • The org.jboss.deployment.SARDeployer is created and started. The 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.
    • Restore the thread context class loader.

The JBoss server starts out as nothing more than a container for the JMX MBean server, and then 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 using MBeans. This is the topic of the next section.

2.4.2. JBoss MBean Services

As we have seen, JBoss relies on JMX to load in the MBean services that make up a given server instance's personality. All of 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. They can be written per the JMX specification and added 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.

2.4.2.1. 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.

The 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 given in Figure 2.16, “The DTD for the MBean service descriptor parsed by the SARDeployer”.

The DTD for the MBean service descriptor parsed by the SARDeployer

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

The elements of the DTD are:

  • server/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.
  • server/loader-repository/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.
  • server/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.
  • server/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 * wild card to signify that all jars should be loaded. The wild card only works 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 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 element is used as the classpath ordering of the JARs. Therefore, if you have patches or inconsistent versions of classes that require a certain ordering, use this feature to ensure the correct ordering. Both the codebase and archives attributes values may reference a system property using a pattern ${x} to refer to replacement of the x system property.
  • server/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.
  • server/mbean/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 using the JavaBean 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 elements. Section 2.4.2.4, “Specifying Service Dependencies”. Note that the dependency value can be another mbean element which defines a nested mbean.

When the SARDeployer is asked to deploy a service performs several steps. Figure 2.17, “A sequence diagram highlighting the main activities performed by the SARDeployer to start a JBoss MBean service” is a sequence diagram that shows the init through start phases of a service.

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

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

In Figure 2.17, “A sequence diagram highlighting the main activities performed by the SARDeployer to start a JBoss MBean service” the following is illustrated:

  • Methods prefixed with 1.1 correspond to the load and parse of the XML service descriptor.
  • Methods prefixed with 1.2 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 correspond to processing each local-directory element in the service descriptor. This does a copy of the SAR elements specified in the path attribute to the server/<config>/db directory.
  • Method 1.4. Process each deployable unit nested in the service a child deployment is created and added to the service deployment info subdeployment list.
  • Method 2.1. The UnifiedClassLoader of the SAR deployment unit is registered with the MBean Server so that is can be used for loading of the SAR MBeans.
  • Method 2.2. 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 2.4.1. For each MBean instance created, obtain its JMX ObjectName and ask the ServiceController to handle the create 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 create method invoked.
  • Methods prefixed with 3.1 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.

2.4.2.2. 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 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 listing 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. We'll discuss the methods in more detail in the ServiceController section.

Note that there is a J2EE management specification request (JSR 77, http://jcp.org/jsr/detail/77.jsp) that introduces a state management notion that includes a start/stop lifecycle notion. When this standard is finalized JBoss will likely support an extension of the JSR 77 based service lifecycle implementation. As of the 3.2.0 release we do support JSR77 management objects and most of the statistics, but the lifecycle operations are not supported.

2.4.2.3. 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.18, “The interaction between the SARDeployer and ServiceController to start a service” shows a sequence diagram that highlights interaction between the SARDeployer and ServiceController.

The interaction between the SARDeployer and ServiceController to start a service

Figure 2.18. 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.

2.4.2.3.1. The create(ObjectName) method

The create(ObjectName) method is called whenever an event occurs that affects the named services 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.

2.4.2.3.2. The start(ObjectName) method

The start(ObjectName) method is called whenever an event occurs that affects the named services 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 since all services upon which the service depends have been created and started.

2.4.2.3.3. The stop(ObjectName) method

The stop(ObjectName) method is called whenever an event occurs that affects the named services 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.

2.4.2.3.4. The destroy(ObjectName) method

The destroy(ObjectName) method is called whenever an event occurs that affects the named services 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 method, or neither stop nor destroy if the service has no state or resources that need cleanup.

2.4.2.4. 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 the two elements relates to the optional-attribute-name attribute usage. If you track the ObjectNames of dependencies using single valued attributes you should use the depends element. If you track the ObjectNames of dependencies 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 use whatever element is easiest. The following listing shows example 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. Example 2.15, “An example of using the depends element to specify the complete configuration of a depended on service.” 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 ManagedConnectionFactoryName attribute of the LocalTxConnectionManager class.

Example 2.15. 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>

2.4.2.5. Identitifying Unsatisfied Dependencies

The ServiceController MBean supports two operations that help with debugging what 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.

2.4.2.6. 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:

  • URLs: 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 then the file is deployed and watched for subsequent updates or removal. If a URL ends in / to represent a directory, then the contents of the directory are treated as a collection of deployables and scanned for content that are to be watched for updates or removal. The requirement that a URL end in a / to identify a directory follows the RFC2518 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, etc. dropped into the server/<name>/deploy directory will be automatically deployed and watched for updates.

    Example URLs include:

    • "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"deploy 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 only work if the remote http server supports the WebDAV PROPFIND command.
  • ScanPeriod: The time in milliseconds between runs of the scanner thread. The default is 5000 (5 seconds).
  • URLComparator: The class name 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: "sar", "service.xml", "rar", "jar", "war", "wsr", "ear", "zip".

    An 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 zeroes), 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: The class name 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", "*,v", "*~", ".make.state", ".nse_depinfo", "CVS", "CVS.admin", "RCS", "RCSLOG", "SCCS", "TAGS", "core", "tags"

  • RecursiveSearch: This property indicates whether or not deploy subdirectories are seen to be holding deployable content. If this is false, deploy subdirectories that do not contain `.' in their name are seen to be unpackaged jars with nested subdeployments. If true, then deploy subdirectories are just groupings of deployable content. The difference between the two views shows 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 deployables and calculates the order based on the previous filter logic. The default is true. However, note that the jboss-3.2.1 release shipped with a default configuration with this set to false.
  • Deployer: 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.

2.4.3. 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 since JMX has no dependency notion. Instead, you must manage dependency state using the Service interface create and/or start methods. You can do this 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 String getName(), int getState(), and String getStateString() methods.

Which approach you choose depends on if you want to be associated with JBoss specific code. If you don't, then you would 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 createService, startService, stopService, and destroyService methods respectively. When subclassing ServiceMBeanSupport, you would override one or more of the createService, startService, stopService, and destroyService methods as required

2.4.3.1. 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.

The MBean you develop is called JNDIMap. Version one of the JNDIMapMBean interface and JNDIMap implementation class, which is based on the service interface method pattern, is given in Example 2.16, “JNDIMapMBean interface and implementation based on the service interface method pattern”. This version of the interface makes use of the first approach in that it incorporates the Service interface methods needed to start up correctly, but does not do so by using a JBoss-specific interface. The interface includes the Service.start method, which will be informed when all required services have been started, and the stop method, which will clean up the service.

Example 2.16. 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();
        }
    }
}
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("Failed to 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 two of the JNDIMapMBean interface and JNDIMap implementation class, which is based on the ServiceMBean interface and ServiceMBeanSupport class, is given in Example 2.16, “JNDIMapMBean interface and implementation based on the service interface method pattern”. In this version, the implementation class extends the ServiceMBeanSupport class and overrides the startService method and the stopService method. JNDIMapMBean also implements the abstract getName 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 2.4.2, “JBoss MBean Services”. The implementation differences between Example 2.16, “JNDIMapMBean interface and implementation based on the service interface method pattern” and Example 2.17, “JNDIMap MBean interface and implementation based on the ServiceMBean interface and ServiceMBeanSupport class” are highlighted in bold in Example 2.17, “JNDIMap MBean interface and implementation based on the ServiceMBean interface and ServiceMBeanSupport class”.

Example 2.17. 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 example 1 service descriptor is shown below along with a sample client usage code fragment. 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 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>
                
// Sample lookup code
InitialContext ctx = new InitialContext();
HashMap map = (HashMap) ctx.lookup("inmemory/maps/MapTest");

2.4.3.2. XMBean Examples

In this section we will develop a variation of the JNDIMap MBean introduced in the preceding section that exposes its management metadata using the JBoss XMBean framework. Our core managed component will be exactly the same core code from the JNDIMap class, but this will not implement any specific management related interface. We will illustrate the following capabilities not possible with a Standard MBean:

  • The ability to add rich descriptions to attribute 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
2.4.3.2.1. Version 1, The Annotated JNDIMap XMBean

Let's start with a simple XMBean variation of the standard MBean version of the JNDIMap that adds the descriptive information about the attributes and operations and their arguments. The following listing 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 is get 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>

As noted previously, the 3.2.2 release replaced the binding of the RMIAdaptor interface with the invoker adaptor service and this service does not yet support remoting of JMX notifications. Therefore, we need to create a config that uses the RMIAdaptorService . There is a config target that sets up a rmi-adaptor configuration with the jmx-rmi-adaptor.sar installed. Build this setup using:

[nr@toki]$ ant -Dchap=chap2 config
...
 
     [echo] Preparing rmi-adaptor configuration fileset
     [copy] Copying 214 files to /tmp/jboss-3.2.6/server/rmi-adaptor
     [copy] Copied 2 empty directories to /tmp/jboss-3.2.6/server/rmi-adaptor
     [copy] Copying 2 files to /tmp/jboss-3.2.6/server/rmi-adaptor/deploy/jmx-rmi-adaptor.s
                        ar
   [delete] Deleting directory /tmp/jboss-3.2.6/server/rmi-adaptor/deploy/jmx-invoker-adapt
                        or-server.sar
   [delete] Deleting directory /tmp/jboss-3.2.6/server/rmi-adaptor/deploy/management

Now, run the rmi-adaptor configuration, and then build, deploy and test the XMBean as follows:

[nr@toki examples]$ ant -Dchap=chap2 -Dex=xmbean1 -Djboss.deploy.conf=rmi-adaptor run-example
...
run-examplexmbean1:
     [copy] Copying 1 file to /tmp/jboss-3.2.6/server/rmi-adaptor/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 is largely the same as 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 using notification elements as is 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 an jmx.attribute.change notification emitted when the InitialValues attribute was changed. This is a standard feature of ModelMBeans owning to the fact that 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.19, “The Version 1 JNDIMapXMBean jmx-console view”.

The Version 1 JNDIMapXMBean jmx-console view

Figure 2.19. 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 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.

2.4.3.2.2. Version 2, Adding Persistence to the JNDIMap XMBean

In version 2 of the XMBean we add support for persistence of the XMBean attributes. The updated XMBean deployment descriptor is given below. The changes with respect to the version 1 descriptor of Lxxx1 are shown in bold.

<?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 nap</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 is get 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>

Build, deploy and test the version 2 XMBean as follows:

[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 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=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 we have done nothing to test that changes to attribute value are actually persisted. Perform this test by running example xmbean2a serveral times:

[nr@toki examples] ant -Dchap=chap2 -Dex=xmbean2a -Djboss.deploy.conf=rmi-adaptor \
run-example
...
     [java] InitialValues.length=2
     [java] key=key10, value=value10
[nr@toki examples] ant -Dchap=chap2 -Dex=xmbean2a -Djboss.deploy.conf=rmi-adaptor \
run-example
...
     [java] InitialValues.length=4
     [java] key=key10, value=value10
     [java] key=key2, value=value2
 
[nr@toki examples] ant -Dchap=chap2 -Dex=xmbean2a -Djboss.deploy.conf=rmi-adaptor \
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 below.

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 it run 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.11 $
 */
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 may even shutdown the JBoss server, restart it and then rerun the initial example 2 to see if the changes are persisted across server restarts:

[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 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=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:s
ervice=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:s
ervice=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:s
ervice=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:s
ervice=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:s
ervice=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:s
ervice=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:s
ervice=JNDIMap,type=org.jboss.chap2.xmbean.JNDIMap.put,sequenceNumber=9,timeStamp=10986336
65614,message=null,userData=null]   

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

2.4.3.2.3. Version 3, Adding Security and Remote Access to the JNDIMap XMBean

The last example version of the JNDIMap XMBean will demonstrate customization of the server interceptor stack as well as exposing a subset of the XMBean management interface via a typed proxy to a remote client using RMI/JRMP. On the server side we will add a simple security interceptor that only allows access to attributes or operations by a user specified in the interceptor configuration. We will also use another custom interceptor to implement the MBean detached invoker pattern described in Section 2.7, “Remote Access to Services, Detached Invokers”. By implementing this pattern in an invoker rather than the XMBean, we demonstrate how to introduce a remote access aspect without having to modify the existing JNDIMap implementation.

We will 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);
} 

Our 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.11 $
 */
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 below:

<?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 nap</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>
</mbean>

The addition over the previous versions of the JNDIMap XMBean is the interceptors element shown in bold in the listing. This 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 example custom interceptors. The remaining three interceptors are the standard ModelMBean interceptors. Because we have a persistence policy of Never, we 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 below.

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.11 $
 *
 */
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 the ServerSecurityInterceptor and the InvokerInterceptor. The ServerSecurityInterceptor intercepts invoke operations and validates that the Invocation context include 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.11 $
 */
 
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 in 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.11 $
 */
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.xxml -->
        <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>
[nr@toki 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
[nr@toki 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
 
[nr@toki 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

2.4.4. Deployment Ordering and Dependencies

We have seen how to manage dependencies 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-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 mange deployment ordering.

Typically you will need to explicitly state your MBean dependencies because your packaging structure does not happen to resolve the dependencies. Let's consider an example component deployment that consists of an MBean that uses an EJB. Here is the structure of the example 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 a chap2-ex3.jar and chap2-ex3.sar. The chap2-ex3.jar is the EJB archive and the chap2-ex3.sar is the MBean service archive. We have implemented the service as a Dynamic MBean to provide an illustration of their use. .

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.11 $
 */
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 setup 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.";
        MBeanConstructorInfo[] 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 an 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 signartures 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 there 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 a different metadata value from the getMBeanInfo method. Of course, clients may not be too 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, demonstrate how an MBean can depend on an EJB for some of its functionality and second, how to create MBeans with dynamic management interfaces. If we 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();
}
            

Moving to lines 67-83, this is 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 40-51, 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 one 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 88-103 correspond to the JBoss service life cycle callbacks. Since we are subclassing the ServiceMBeanSupport utility class, we override the createService, startService, and stopService template callbacks rather than the create, start, and stop methods of the service interface. Note that we cannot attempt to lookup the EchoLocalHome interface of the EJB we 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 we will 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. We will see this dependency specification when we look at the service descriptor.

Lines 105-121 are the HelloPrefix and EjbJndiName attribute accessors implementations. These are invoked in response to getAttribute/setAttribute invocations made through the MBean Server.

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

The remainder of the class makes up the Dynamic MBean interface implementation. Lines 133-152 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 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 154-258 handle the attribute access requests. This is rather tedious and error prone code so a toolkit or infrastructure that helps generate these methods should be used. 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 260-310 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 given below. The dependency on the EJB container MBean is highlighted in bold. The format of the EJB container MBean ObjectName is:

"jboss.j2ee:service=EJB,jndiName=" + <home-jndi-name>

where the <home-jndi-name> is the EJB home interface JNDI name.

<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>    

Deploy the example ear by running:

[starksm@banshee examples]$ ant -Dchap=chap2 -Dex=3 run-example
...
run-example3:
     [copy] Copying 1 file to /tmp/jboss-3.2.6/server/default/deploy

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

14:57:12,906 INFO  [EARDeployer] Init J2EE application: file:/private/tmp/jboss-3.2.6/serv
er/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(BasicMBeanRegistr
y.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(BasicMBeanRegistr
y.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(BasicMBeanRegistr
y.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-3.2.6/server/default/tm
p/deploy/tmp1418chap2-ex3.ear-contents/chap2-ex3.jar
14:57:14,075 INFO  [EARDeployer] Started J2EE application: file:/private/tmp/jboss-3.2.6/s
erver/default/deploy/chap2-ex3.ear

The stack traces are not exceptions. They are traces coming 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. Browse to http://localhost:8080/jmx-console/HtmlAdaptor?action=inspectMBean&name=jboss.book%3Aservice%3DEjbMBeanAdaptor and scroll down to the echo operation section. The view should be like that shown in Figure 2.20, “The EjbMBeanAdaptor MBean operations JMX console view”.

The EjbMBeanAdaptor MBean operations JMX console view

Figure 2.20. The EjbMBeanAdaptor MBean operations JMX console view

As shown, we have already entered an argument string of "-echo-arg" into the ParamValue text field. Press the Invoke button and a result string of "AdaptorPrefix-echo-arg" is displayed on the results page. The server console will show several stack traces from the various metadata queries issues 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

2.5. JBoss Deployer Architecture

JBoss has an extensible deployment architecture that allows one to incorporate components into the bare JBoss JMX microkernel. Figure 2.21, “The deployment layer classes”shows the classes in the deployment layer.

The deployment layer classes

Figure 2.21. The deployment layer classes

The MainDeployer is the deployment entry point. Requests to deploy a component are sent to the MainDeployer and it determines if there is a subdeployer capable of handling the deployment, and if there is, it delegates the deployment to the subdeployer. We saw an example of this when we looked at how the MainDeployer used the SARDeployer to deploy MBean services. The current deployers included with JBoss are:

  • AbstractWebContainer: This subdeployer handles web application archives (WARs). It accepts deployment archives and directories whose name ends with a "war" suffix. WARs must have a WEB-INF/web.xml descriptor and may have a WEB-INF/jboss-web.xml descriptor.
  • EARDeployer: This subdeployer handles enterprise application archives (EARs). It accepts deployment archives and directories whose name ends with an "ear" suffix. EARs must have a META-INF/application.xml descriptor and may have a META-INF/jboss-app.xml descriptor.
  • EJBDeployer: This subdeployer handles enterprise bean jars. It accepts deployment archives and directories whose name ends with a "jar" suffix. EJB jars must have a META-INF/ejb-jar.xml descriptor and may have a META-INF/jboss.xml descriptor.
  • JARDeployer: This subdeployer handles library jar archives. The only restriction it places on an archive is that it cannot contain a WEB-INF directory.
  • RARDeployer: This subdeployer handles JCA resource archives (RARs). It accepts deployment archives and directories whose name ends with a "rar" suffix. RARs must have a META-INF/ra.xml descriptor.
  • SARDeployer: This subdeployer handles JBoss MBean service archives (SARs). It accepts deployment archives and directories whose name ends with a "sar" suffix, as well as standalone XML files that end with "service.xml". SARs that are jars must have a META-INF/jboss-service.xml descriptor.

The MainDeployer, JARDeployer and SARDeployer are hard coded deployers in the JBoss server core. The AbstractWebContainer, EARDeployer, EJBDeployer, and RARDeployer are MBean services that register themselves as deployers with the MainDeployer using the addDeployer(SubDeployer) operation. The SubDeployer interface is shown below.

public interface SubDeployer
{
    /**
     * The <code>accepts</code> method
     is called by MainDeployer to
     * determine which deployer is suitable for a DeploymentInfo.
     *
     * @param sdi a <code>DeploymentInfo</code> value
     * @return a <code>boolean</code> value
     *
     * @jmx:managed-operation
     */
    boolean accepts(DeploymentInfo sdi);
    
    /**
     * The <code>init</code> method lets the deployer set
     * a few properties of the DeploymentInfo, such as the watch url.
     *
     * @param sdi a <code>DeploymentInfo</code> value
     * @throws DeploymentException if an error occurs
     *
     * @jmx:managed-operation
     */
    void init(DeploymentInfo sdi) throws DeploymentException;
    
    /**
     * Set up the components of the deployment that do not
     * refer to other components
     *
     * @param sdi a <code>DeploymentInfo</code> value
     * @throws DeploymentException Failed to deploy
     *
     * @jmx:managed-operation
     */
    void create(DeploymentInfo sdi) throws DeploymentException;
    
    /**
     * The <code>start</code> method sets up relationships
     * with other components.
     *
     * @param sdi a <code>DeploymentInfo</code> value
     * @throws DeploymentException if an error occurs
     *
     * @jmx:managed-operation
     */
    void start(DeploymentInfo sdi) throws DeploymentException;
    
    /**
     * The <code>stop</code> method removes relationships
     * between components.
     *
     * @param sdi a <code>DeploymentInfo</code> value
     * @throws DeploymentException if an error occurs
     *
     * @jmx:managed-operation
     */
    void stop(DeploymentInfo sdi) throws DeploymentException;
    
    /**
     * The <code>destroy</code> method
     removes individual components
     *
     * @param sdi a <code>DeploymentInfo</code> value
     * @throws DeploymentException if an error occurs
     *
     * @jmx:managed-operation
     */
    void destroy(DeploymentInfo sdi) throws DeploymentException;
} 

The DeploymentInfo object is basically a data structure that encapsulates the complete state of a deployable component. When the MainDeployer receives a deployment request, it iterates through its registered subdeployers and invokes the accepts(DeploymentInfo) method on the subdeployer. The first subdeployer to return true is chosen and the deployment deployer and the MainDeployer will delegate the init, create, start, stop and destroy deployment life cycle operations to the subdeployer.

2.5.1. Deployers and ClassLoaders

Deployers are the mechanism by which components are brought into a JBoss server. Deployers are also the creators of the majority of UCL instances, and the primary creator is the MainDeployer. The MainDeployer creates the UCL for a deployment early on during its init method. The UCL is created by calling the DeploymentInfo.createClassLoaders() method. As of the 3.0.5RC1 release, only the topmost DeploymentInfo will actually create a UCL. All subdeployments will add their class paths to their parent DeploymentInfo UCL. Previously every subdeployment created a UCL for its deployment, and a seperate UCL for every manifest or classpath reference. This could cause problems because classes ended up being loaded by more than one UCL and IllegalAccessError s and LinkageError s would result. Every deployment does have a standalone URLClassLoader that uses the deployment URL as its path. This is used to localize the loading of resources such as deployment descriptors. Figure 2.22, “An illustration of the class loaders involved with an EAR deployment” provides an illustration of the interaction between Deployers, DeploymentInfos and class loaders.

An illustration of the class loaders involved with an EAR deployment

Figure 2.22. An illustration of the class loaders involved with an EAR deployment

The figure illustrates an EAR deployment with EJB and WAR subdeployments. The EJB deployment references the lib/util.jar utility jar via its manifest. The WAR includes classes in its WEB-INF/classes directory as well as the WEB-INF/lib/jbosstest-web-util.jar. Each deployment has a DeploymentInfo instance that has a URLClassLoader pointing to the deployment archive. The DeploymentInfo associated with some.ear is the only one to have a UCL created. The ejbs.jar and web.war DeploymentInfo s add their deployment archive to the some.ear UCL classpath, and share this UCL as their deployemnt UCL. The EJBDeployer also adds any manifest jars to the EAR UCL.

The WARDeployer behaves differently than other deployers in that it only adds its WAR archive to the DeploymentInfo UCL classpath. The loading of classes from the WAR WEB-INF/classes and WEB-INF/lib locations is handled by the servlet container class loader. The servlet container class loaders delegate to the WAR DeploymentInfo UCL as their parent class loader, but the server container class loader is not part of the JBoss class loader repository. Therefore, classes inside of a WAR are not visible to other components. Classes that need to be shared between web application components and other components such as EJBs, and MBeans need to be loaded into the shared class loader repository either by including the classes into a SAR or EJB deployment, or by referencing a jar containing the shared classes through a manifest Class-Path entry. In the case of a SAR, the SAR classpath element in the service deployment serves the same purpose as a jar manifest Class-Path.

2.6.  Exposing MBean Events via SNMP

3.2.2 added an snmp-adaptor service that can be used to intercept JMX notifications emitted by MBeans, convert them to traps and send them to SNMP managers. In this respect the snmp-adaptor acts as a SNMP agent. Future versions may offer support for full agent get/set functionality that maps onto MBean attributes or operations.

It can be used to integrate JBoss with higher order system/network management platforms (e.g., HP OpenView), thus making the MBeans visible to those systems. The MBean developer can instrument the MBeans by producing notifications for any significant event (e.g. server coldstart). The adaptor can then be configured to intercept and map those notifications to SNMP traps. The adaptor uses the JoeSNMP package from OpenNMS as the SNMP engine.

SnmpAgentService is the main MBean that implements the SNMP agent. It is configured by means of three different configuration files:

  • managers.xml: configures where to send traps
  • mbeans.xml: configures the monitored MBeans/notifications types
  • notifications.xml: specifies the exact mapping of each notification type to a corresponding SNMP trap

2.6.1. The SNMP Adaptor Service

The org.jboss.jmx.adaptor.snmp.agent.SnmpAgentService allows one to send V1 or V2 SNMP traps to one or more SNMP managers defined by their IP address, listening port number and expected SNMP version.

  • HeartBeatPeriod: The period in seconds at which hearteat notifications are generated.
  • ManagersResName: Specifies the resource name of the file containing SNMP manager specifications. The content model for this file is shown in Figure 2.23, “The schema for the SNMP managers file”.
  • MonitoredObjectsResName: Specifies the resource name of the file that configures which JMX objects to monitor for events. The content model for this file is shown in Figure 2.25, “The schema for the monitored objects file”.
  • NotificationMapResName: Specifies the resource name of the file containing the JMX notification to SNMP trap mappings. The content model for this file is shown in Figure 2.24, “The schema for the notification to trap mapping file”.
  • TrapFactoryClassName: The org.jboss.jmx.adaptor.snmp.agent.TrapFactory implementation class that takes care of translation of JMX Notifications into SNMP V1 and V2 traps.
  • TimerName: Specifies the JMX ObjectName of the JMX timer serivce to use for heartbeat notifications.
The schema for the SNMP managers file

Figure 2.23. The schema for the SNMP managers file

The schema for the notification to trap mapping file

Figure 2.24. The schema for the notification to trap mapping file

The schema for the monitored objects file

Figure 2.25. The schema for the monitored objects file

2.6.2. The Event to Trap Service

org.jboss.jmx.adaptor.snmp.trapd.TrapdService is a simple MBean that acts as an SNMP Manager. It listens to a configurable port for incoming traps and logs them as DEBUG messages using the system logger. You can modify the log4j configuration to redirect the log output to a file. SnmpAgentService and TrapdService are not dependent with each other.

2.7. Remote Access to Services, Detached Invokers

In addition to the MBean services notion that allows for the ability to integrate arbitrary functionality, JBoss also has a detached invoker concept that allows MBean services to expose functional interfaces via arbitrary protocols for remote access by clients. This notion first showed up in 3.0 for the EJB container and it has been further generalized to any MBean service in 3.2. The notion of a detached invoker is that remoting and the protocol by which a service is accessed is a functional aspect or service from independent of the component. Thus, one can make a naming service available for use via RMI/JRMP, RMI/HTTP, RMI/SOAP, or any arbitrary custom transport.

Let's begin our discussion of the detached invoker architecture with an overview of the components involved. The main components in the detached invoker architecture are shown in Figure 2.26, “The main components in the detached invoker architecture”.

The main components in the detached invoker architecture

Figure 2.26. The main components in the detached invoker architecture

On the client side, there exist a client proxy which exposes the interface(s) of the MBean service. This is the same smart, compile-less dynamic proxy that we use for EJB home and remote interfaces. The only difference between the proxy for an arbitrary service and the EJB is the set of interfaces exposed as well as the client side interceptors found inside the proxy. The client interceptors are represented by the rectangles found inside of the client proxy. An interceptor is an assembly line type of pattern that allows for transformation of a method invocation and/or return values. A client obtains a proxy through some lookup mechanism, typically JNDI. Although RMI is indicated in Figure 2.26, “The main components in the detached invoker architecture”, the only real requirement on the exposed interface and its types is that they are serializable between the client server over JNDI as well as the transport layer.

The choice of the transport layer is determined by the last interceptor in the client proxy, which is referred to as the "Invoker Interceptor" in Figure 2.26, “The main components in the detached invoker architecture”. The invoker interceptor contains a reference to the transport specific stub of the server side "Detached Invoker" MBean service. The invoker interceptor also handles the optimization of calls that occur within the same VM as the target MBean. When the invoker interceptor detects that this is the case the call is passed to a call-by-reference invoker that simply passes the invocation along to the target MBean.

The detached invoker service is responsible for making a generic invoke operation available via the transport the detached invoker handles. The Invoker interface illustrates the generic invoke operation.

package org.jboss.invocation;
             
import java.rmi.Remote;
import org.jboss.proxy.Interceptor;
import org.jboss.util.id.GUID;
             
             
public interface Invoker
    extends Remote
{
    GUID ID = new GUID();
        
    Object invoke(Invocation invocation) throws Exception;
}

The Invoker interface extends Remote to be compatible with RMI, but this does not mean that an invoker must expose an RMI service stub. The detached invoker service simply acts as a transport gateway that accepts invocations represented as the org.jboss.invocation.Invocation object over its specific transport, unmarshalls the invocation, forwards the invocation onto the destination MBean service, represented by the "Target MBean" in Figure 2.26, “The main components in the detached invoker architecture”, and marshalls the return value or exception resulting from the forwarded call back to the client.

The Invocation object is just a representation of a method invocation context. This includes the target MBean name, the method, the method arguments, a context of information associated with the proxy by the proxy factory, and an arbitrary map of data associated with the invocation by the client proxy interceptors. The following listing shows the key methods of the Invocation class.

package org.jboss.invocation;

import java.lang.reflect.Method;
import java.security.Principal;
import java.util.Map;
import java.util.HashMap;
import javax.transaction.Transaction;

public class Invocation
{
    /** The signature of the invoke() method */
    public static final String[] INVOKE_SIGNATURE = 
        {"org.jboss.invocation.Invocation"};

    /**
     *  Contextual information to the invocation that is not part of
     *  the payload.
     */
    public Map transient_payload;

    /**
     * as_is classes that will not be marshalled by the invocation
     * (java.* and javax.* or anything in system classpath is OK)
     */
    public Map as_is_payload;
    
    /** 
     * Payload will be marshalled for type hiding at the RMI layers.
     */
    public Map payload;
    
    protected InvocationContext invocationContext;
    protected Object[] args;
    protected Object objectName;
    protected Method method;
    
    public Invocation()
    {
	payload = new HashMap();
	as_is_payload = new HashMap();
	transient_payload = new HashMap();
    }
    
    public Invocation(Object id, Method m, Object[] args,
		      Transaction tx,
		      Principal identity, Object credential )
    {
	this.payload = new HashMap();
	this.as_is_payload = new HashMap();
	this.transient_payload = new HashMap();
	
	setId(id);
	setMethod(m);
	setArguments(args);
	setTransaction(tx);
	setPrincipal(identity);
	setCredential(credential);
    }

    public void setValue(Object key, Object value)
    {
	setValue(key, value, PayloadKey.PAYLOAD);
    }

    public void setValue(Object key, Object value, PayloadKey type)
    {
	if(type == PayloadKey.TRANSIENT) {
	    transient_payload.put(key,value);
	} else if(type == PayloadKey.AS_IS) {
	    as_is_payload.put(key,value);
	} else if(type == PayloadKey.PAYLOAD) {
	    payload.put(key,value);
	} else {
	    throw new IllegalArgumentException("Unknown
                PayloadKey: " + type);
	}
    }

    public Object getValue(Object key)
    {
	// find where it is
	Object rtn = payload.get(key);
	if (rtn != null) return rtn;

	rtn = as_is_payload.get(key);
	if (rtn != null) return rtn;
	
	rtn = transient_payload.get(key);
	return rtn;
    }

    public Object getPayloadValue(Object key)
    {
	return payload.get(key);
    }

    // ... Convience accessor methods deleted...
}            

The configuration of the client proxy is done by the server side proxy factory MBean service, indicated by the "Proxy Factory" component in Figure 2.26, “The main components in the detached invoker architecture”. The proxy factory preforms the following tasks:

  • Create a dynamic proxy that implements the interface the target MBean wishes to expose.
  • Associate the client proxy interceptors with the dynamic proxy handler.
  • Associate the invocation context with the dynamic proxy. This includes the target MBean, detached invoker stub and the proxy JNDI name.
  • Make the proxy available to clients by binding the proxy into JNDI.

The last component in \Figure 2.26, “The main components in the detached invoker architecture” is the "Target MBean" service that wishes to expose an interface for invocations to remote clients. The steps required for an MBean service to be accessible through a given interface are:

  • Define a JMX operation matching the signature: public Object invoke(org.jboss.invocation.Invocation) throws Exception
  • Create a HashMap<Long, Method> mapping from the exposed interface java.lang.reflect.Method s to the long hash representation using the org.jboss.invocation.MarshalledInvocation.calculateHash method.
  • Implement the invoke(Invocation) JMX operation and use the interface method hash mapping to transform from the long hash representation of the invoked method to the java.lang.reflect.Method of the exposed interface. Reflection is used to perform the actual invocation on the object associated with the MBean service that actually implements the exposed interface.

2.7.1. A Detached Invoker Example, the MBeanServer Invoker Adaptor Service

In the section on connecting to the JMX server we mentioned that there was a service that allows one to access the javax.management.MBeanServer via any protocol using an invoker service. In this section we present the org.jboss.jmx.connector.invoker.InvokerAdaptorService and its configuration for access via RMI/JRMP as an example of the steps required to provide remote access to an MBean service.

The InvokerAdaptorService is a simple MBean service that only exists to fulfill the target MBean role in the detached invoker pattern.

Example 2.18. The InvokerAdaptorService MBean

package org.jboss.jmx.connector.invoker;
public interface InvokerAdaptorServiceMBean
    extends org.jboss.system.ServiceMBean
{
    Class getExportedInterface();
    void setExportedInterface(Class exportedInterface);

    Object invoke(org.jboss.invocation.Invocation invocation)
        throws Exception;
}

package org.jboss.jmx.connector.invoker;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.jboss.invocation.Invocation;
import org.jboss.invocation.MarshalledInvocation;
import org.jboss.mx.server.ServerConstants;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.system.Registry;

public class InvokerAdaptorService
    extends ServiceMBeanSupport
    implements InvokerAdaptorServiceMBean, ServerConstants
{
    private static ObjectName mbeanRegistry;
    
    static {
        try {
            mbeanRegistry = new ObjectName(MBEAN_REGISTRY);
        } catch (Exception e) {
            throw new RuntimeException(e.toString());
        }
    }

    private Map marshalledInvocationMapping = new HashMap();
    private Class exportedInterface;

    public Class getExportedInterface()
    {
        return exportedInterface;
    }

    public void setExportedInterface(Class exportedInterface)
    {
        this.exportedInterface = exportedInterface;
    }

    protected void startService()
        throws Exception
    {
        // Build the interface method map
        Method[] methods = exportedInterface.getMethods();
        HashMap tmpMap = new HashMap(methods.length);
        for (int m = 0; m < methods.length; m ++) {
            Method method = methods[m];
            Long hash = new Long(MarshalledInvocation.calculateHash(method));
            tmpMap.put(hash, method);
        }

        marshalledInvocationMapping = Collections.unmodifiableMap(tmpMap);
        // Place our ObjectName hash into the Registry so invokers can
        // resolve it
        Registry.bind(new Integer(serviceName.hashCode()), serviceName);
    }

    protected void stopService()
        throws Exception
    {
        Registry.unbind(new Integer(serviceName.hashCode()));
    }


    public Object invoke(Invocation invocation)
        throws Exception
    {
        // Make sure we have the correct classloader before unmarshalling
        Thread thread = Thread.currentThread();
        ClassLoader oldCL = thread.getContextClassLoader();

        // Get the MBean this operation applies to
        ClassLoader newCL = null;
        ObjectName objectName = (ObjectName) 
            invocation.getValue("JMX_OBJECT_NAME");
        if (objectName != null) {
            // Obtain the ClassLoader associated with the MBean deployment
            newCL = (ClassLoader) 
                server.invoke(mbeanRegistry, "getValue",
                              new Object[] { objectName, CLASSLOADER },
                              new String[] { ObjectName.class.getName(),
                                             "java.lang.String" });
        }
        
        if (newCL != null && newCL != oldCL) {
            thread.setContextClassLoader(newCL);
        }

        try {
            // Set the method hash to Method mapping
            if (invocation instanceof MarshalledInvocation) {
                MarshalledInvocation mi = (MarshalledInvocation) invocation;
                mi.setMethodMap(marshalledInvocationMapping);
            }

            // Invoke the MBeanServer method via reflection
            Method method = invocation.getMethod();
            Object[] args = invocation.getArguments();
            Object value = null;
            try {
                String name = method.getName();
                Class[] sig = method.getParameterTypes();
                Method mbeanServerMethod =
                    MBeanServer.class.getMethod(name, sig);
                value = mbeanServerMethod.invoke(server, args);
            } catch(InvocationTargetException e) {
                Throwable t = e.getTargetException();
                if (t instanceof Exception) {
                    throw (Exception) t;
                } else {
                    throw new UndeclaredThrowableException(t, method.toString());
                }
            }

            return value;
        } finally {
            if (newCL != null && newCL != oldCL) {
                thread.setContextClassLoader(oldCL);
            }
        }
    }
}    

Let's go through the key details of this service. The InvokerAdaptorServiceMBean Standard MBean interface of the InvokerAdaptorService has a single ExportedInterface attribute and a single invoke(Invocation) operation. The ExportedInterface attribute allows customization of the type of interface the service exposes to clients. This has to be "compatible" with the MBeanServer class in terms of method name and signature as we will see. The invoke(Invocation) operation is the required entry point that target MBean services must expose to participate in the detached invoker pattern. This operation is invoked by the detached invoker services that have been configured to provide access to the InvokerAdaptorService.

Lines 54-64 of the InvokerAdaptorService build the HashMap<Long, Method> of the ExportedInterface Class using the org.jboss.invocation.MarshalledInvocation.calculateHash(Method) utility method. Because java.lang.reflect.Method instances are not serializable, a MarshalledInvocation version of the non-serializable Invocation class is used to marshall the invocation between the client and server. The MarshalledInvocation replaces the Method instances with their corresponding hash representation. On the server side, the MarshalledInvocation must be told what the hash to Method mapping is.

Line 64 creates a mapping between the InvokerAdaptorService service name and its hashCode representation. This is used by detached invokers to determine what the target MBean ObjectName of an Invocation is. When the target MBean name is store in the Invocation, its store as its hashCode because ObjectName s are relatively expensive objects to create. The org.jboss.system.Registry is a global map like construct that invokers use to store the hashCode to ObjectName mappings in.

Lines 77-93 obtain the name of the MBean on which the MBeanServer operation is being performed and lookup the ClassLoader associated with the MBean's SAR deployment. This information is available via the org.jboss.mx.server.registry.BasicMBeanRegistry, a JBoss JMX implementation specific class. It is generally necessary for an MBean to establish the correct class loading context because the detached invoker protocol layer may not have access to the class loaders needed to unmarshall the types associated with an invocation.

Lines 101-105 install the ExposedInterface class method hash to method mapping if the invocation argument is of type MarshalledInvocation. The method mapping calculated previously at lines 54-62 is used here.

Lines 107-114 perform a second mapping from the ExposedInterface Method to the matching method of the MBeanServer class. The InvokerServiceAdaptor decouples the ExposedInterface from the MBeanServer class in that it allows an arbitrary interface. This is needed on one hand because the standard java.lang.reflect.Proxy class can only proxy interfaces. It also allows one to only expose a subset of the MBeanServer methods and add transport specific exceptions like java.rmi.RemoteException to the ExposedInterface method signatures.

Line 115 dispatches the MBeanServer method invocation to the MBeanServer instance to which the InvokerAdaptorService was deployed. The server instance variable is inherited from the ServiceMBeanSupport superclass.

Lines 117-124 handle any exceptions coming from the reflective invocation including the unwrapping of any declared exception thrown by the invocation.

Line 126 is the return of the successful MBeanServer method invocation result.

Note that the InvokerAdaptorService MBean does not deal directly with any transport specific details. There is the calculation of the method hash to Method mapping, but this is a transport independent detail.

Now let's take a look at how the InvokerAdaptorService may be used to expose the same org.jboss.jmx.adaptor.rmi.RMIAdaptor interface via RMI/JRMP as seen in Connecting to JMX Using RMI. We will start by presenting the proxy factory and InvokerAdaptorService configurations found in the default setup in the jmx-invoker-adaptor-service.sar deployment. Example 2.19, “The default jmx-invoker-adaptor-server.sar jboss-service.xml deployment descriptor” shows the jboss-service.xml descriptor for this deployment.

Example 2.19. The default jmx-invoker-adaptor-server.sar jboss-service.xml deployment descriptor

<server>
    <!-- The JRMP invoker proxy configuration for the InvokerAdaptorService -->
    <mbean code="org.jboss.invocation.jrmp.server.JRMPProxyFactory"
           name="jboss.jmx:type=adaptor,name=Invoker,protocol=jrmp,service=proxyFactory">
        <!-- Use the standard JRMPInvoker from conf/jboss-service.xxml -->
        <attribute name="InvokerName">jboss:service=invoker,type=jrmp</attribute>
        <!-- The target MBean is the InvokerAdaptorService configured below -->
        <attribute name="TargetName">jboss.jmx:type=adaptor,name=Invoker</attribute>
        <!-- Where to bind the RMIAdaptor proxy -->
        <attribute name="JndiName">jmx/invoker/RMIAdaptor</attribute>
        <!-- The RMI compabitle MBeanServer interface -->
        <attribute name="ExportedInterface">org.jboss.jmx.adaptor.rmi.RMIAdaptor</attribute>
        <attribute name="ClientInterceptors">
            <iterceptors>
                <interceptor>org.jboss.proxy.ClientMethodInterceptor</interceptor>
                <interceptor>
                    org.jboss.jmx.connector.invoker.client.InvokerAdaptorClientInterceptor </interceptor>
                <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor>
            </iterceptors>
        </attribute>
        <depends>jboss:service=invoker,type=jrmp</depends>
    </mbean>  
    <!-- This is the service that handles the RMIAdaptor invocations by routing
         them to the MBeanServer the service is deployed under. -->
    <mbean code="org.jboss.jmx.connector.invoker.InvokerAdaptorService" 
           name="jboss.jmx:type=adaptor,name=Invoker">
        <attribute name="ExportedInterface">org.jboss.jmx.adaptor.rmi.RMIAdaptor </attribute>
    </mbean>
</server>

The first MBean, org.jboss.invocation.jrmp.server.JRMPProxyFactory, is the proxy factory MBean service that creates proxies for the RMI/JRMP protocol. The complete reference information on the JRMPProxyFactory may be found in section The JRMPProxyFactory Service - Building Dynamic JRMP Proxies. The configuration of this service as shown in Example 2.19, “The default jmx-invoker-adaptor-server.sar jboss-service.xml deployment descriptor” states that the JRMPInvoker will be used as the detached invoker, the InvokerAdaptorService is the target mbean to which requests will be forwarded, that the proxy will expose the RMIAdaptor interface, the proxy will be bound into JNDI under the name "jmx/invoker/RMIAdaptor", and the proxy will contain 3 interceptors: ClientMethodInterceptor, InvokerAdaptorClientInterceptor, InvokerInterceptor. The configuration of the InvokerAdaptorService simply sets the RMIAdaptor interface that the service is exposing.

The last piece of the configuration for exposing the InvokerAdaptorService via RMI/JRMP is the detached invoker. The detached invoker we will use is the standard RMI/JRMP invoker used by the EJB containers for home and remote invocations, and this is the org.jboss.invocation.jrmp.server.JRMPInvoker MBean service configured in the conf/jboss-service.xml descriptor. That we can use the same service instance emphasizes the detached nature of the invokers. The JRMPInvoker simply acts as the RMI/JRMP endpoint for all RMI/JRMP proxies regardless of the interface(s) the proxies expose or the service the proxies utilize.

2.7.2. Detached Invoker Reference

2.7.2.1. The JRMPInvoker - RMI/JRMP Transport

The org.jboss.invocation.jrmp.server.JRMPInvoker class is an MBean service that provides the RMI/JRMP implementation of the Invoker interface. The JRMPInvoker exports itself as an RMI server so that when it is used as the Invoker in a remote client, the JRMPInvoker stub is sent to the client instead and invocations use the RMI/JRMP protocol.

The JRMPInvoker MBean supports a number of attribute to configure the RMI/JRMP transport layer. Its configurable attributes are:

  • RMIObjectPort: sets the RMI server socket listening port number. This is the port RMI clients will connect to when communicating through the proxy interface. The default setting in the jboss-service.xml descriptor is 4444, and if not specified, the attribute defaults to 0 to indicate an anonymous port should be used.
  • RMIClientSocketFactory: specifies a fully qualified class name for the java.rmi.server.RMIClientSocketFactory interface to use during export of the proxy interface.
  • RMIServerSocketFactory: specifies a fully qualified class name for the java.rmi.server.RMIServerSocketFactory interface to use during export of the proxy interface.
  • ServerAddress: specifies the interface address that will be used for the RMI server socket listening port. This can be either a DNS hostname or a dot-decimal Internet address. Since the RMIServerSocketFactory does not support a method that accepts an InetAddress object, this value is passed to the RMIServerSocketFactory implementation class using reflection. A check for the existence of a: public void setBindAddress(java.net.InetAddress addr) method is made, and if one exists, the RMIServerSocketAddr value is passed to the RMIServerSocketFactory implementation. If the RMIServerSocketFactory implementation does not support such a method, the ServerAddress value will be ignored.
  • SecurityDomain: specifies the JNDI name of an org.jboss.security.SecurityDomain interface implementation to associate with the RMIServerSocketFactory implementation. The value will be passed to the RMIServerSocketFactory using reflection to locate a method with a signature of: public void setSecurityDomain(org.jboss.security.SecurityDomain d) If no such method exists the SecurityDomain value will be ignored.

2.7.2.2. The PooledInvoker - RMI/Socket Transport

The org.jboss.invocation.pooled.server.PooledInvoker is an MBean service that provides RMI over a custom socket transport implementation of the Invoker interface. The PooledInvoker exports itself as an RMI server so that when it is used as the Invoker in a remote client, the PooledInvoker stub is sent to the client instead and invocations use the a custom socket protocol.

The PooledInvoker MBean supports a number of attribute to configure the socket transport layer. Its configurable attributes are:

  • NumAcceptThreads: The number of threads that exist for accepting client connections. The default is 1.
  • MaxPoolSize: The number of server threads for processing client. The default is 300.
  • SocketTimeout: The socket timeout value passed to the Socket.setSoTimeout() cmethod. The default is 60000.
  • ServerBindPort: The port used for the server socket. A value of 0 indicates that an anonymous port should be chosen.
  • ClientConnectAddress: The address that the client passes to the Socket(addr, port) constructor. This defaults to the server InetAddress.getLocalHost() value.
  • ClientConnectPort: The port that the client passes to the Socket(addr, port) constructor. The default is the port of the server listening socket.
  • ClientMaxPoolSize: The client side maximum number of threads. The default is 300.
  • Backlog: The backlog associated with the server accept socket. The default is 200.
  • EnableTcpNoDelay: A boolean flag indicating if client sockets will enable the TcpNoDelay flag on the socket. The default is false.
  • ServerBindAddress: The address on which the server binds its listening socket. The default is an empty value which indicates the server should be bound on all interfaces.
  • TransactionManagerService: The JMX ObjectName of the JTA transaction manager service.

2.7.2.3. The IIOPInvoker - RMI/IIOP Transport

The org.jboss.invocation.iiop.IIOPInvoker class is an MBean service that provides the RMI/IIOP implementation of the Invoker interface. The IIOPInvoker IIOP invoker that routes IIOP requests to CORBA servants are used by the This used by the org.jboss.proxy.ejb.IORFactory proxy factory to create RMI/IIOP proxies. However, rather than creating Java proxies (as the JRMP proxy factory does), this factory creates CORBA IORs. An <code>IORFactory</code> is associated to a given enterprise bean. It registers with the IIOP invoker two CORBA servants: anEjbHomeCorbaServant for the bean's EJBHome and an EjbObjectCorbaServant for the bean's EJBObjects.

The IIOPInvoker MBean has no configurable proprties, since all properties are configured from the conf/jacorb.properties property file used by the JacORB CORBA service.

2.7.2.4. The JRMPProxyFactory Service - Building Dynamic JRMP Proxies

The org.jboss.invocation.jrmp.server.JRMPProxyFactory MBean service is a proxy factory that can expose any interface with RMI compatible semantics for access to remote clients using JRMP as the transport.

The JRMPProxyFactory supports the following attributes:

  • InvokerName: The server side JRMPInvoker MBean service JMX ObjectName string that will handle the RMI/JRMP transport.
  • TargetName: The server side MBean that exposes the invoke(Invocation) JMX operation for the exported interface. This is used as the destination service for any invocations done through the proxy.
  • JndiName: The JNDI name under which the proxy will be bound.
  • ExportedInterface: The fully qualified class name of the interface that the proxy implements. This is the typed view of the proxy that the client uses for invocations.
  • ClientInterceptors: An XML fragment of interceptors/interceptor elements with each interceptor element body specifying the fully qualified class name of an org.jboss.proxy.Interceptor implementation to include in the proxy interceptor stack. The ordering of the interceptors/interceptor elements defines the order of the interceptors.

2.7.2.5. The HttpInvoker - RMI/HTTP Transport

The org.jboss.invocation.http.server.HttpInvoker MBean service provides the provides support for making invocations into the JMX bus over HTTP. Unlike the JRMPInvoker, the HttpInvoker is not an implementation of Invoker, but it does implement the Invoker.invoke method. The HttpInvoker is accessed indirectly by issuing an HTTP POST against the org.jboss.invocation.http.servlet.InvokerServlet. The HttpInvoker exports a client side proxy in the form of the org.jboss.invocation.http.interfaces.HttpInvokerProxy class, which is an implementation of Invoker, and is serializable. The HttpInvoker is a drop in replacement for the JRMPInvoker as the target of the bean-invoker and home-invoker EJB configuration elements. The HttpInvoker and InvokerServlet are deployed in the http-inovker.sar discussed in the JNDI chapter in the section entitled Accessing JNDI over HTTP

The HttpInvoker supports the following attributes:

  • InvokerURL: This is either the http URL to the InvokerServlet mapping, or the name of a system property that will be resolved inside the client VM to obtain the http URL to the InvokerServlet. This value can itself be a reference to a system property resolved in the server if the value is of the form ${x} where x is the name of the system property. This allows the URL or client side system property to be set in one place and reused in the HttpInvoker config as well as the InvokerServlet config.
  • InvokerURLPrefix: If there is no invokerURL set, then one will be constructed via the concatenation of invokerURLPrefix + the local host + invokerURLSuffix. An example prefix is "http://", and this is the default.
  • InvokerURLSuffix: If there is no invokerURL set, then one will be constructed via the concatenation of invokerURLPrefix + the local host + invokerURLSuffix. An example suffix is ":8080/invoker/JMXInvokerServlet" and this is the default.
  • UseHostName: A boolean flag if the InetAddress.getHostName() or getHostAddress() method should be used as the host component of invokerURLPrefix + host + invokerURLSuffix. If true getHostName() is used, false getHostAddress().

2.7.2.6. The HA JRMPInvoker - Clustered RMI/JRMP Transport

The org.jboss.proxy.generic.ProxyFactoryHA service is an extension of the ProxyFactoryHA that is a cluster aware factory. The ProxyFactoryHA fully supports all of the attributes of the JRMPProxyFactory. This means that customized bindings of the port, interface and socket transport are available to clustered RMI/JRMP as well. In addition, the following cluster specific attributes are supported:

  • PartitionObjectName: The JMX ObjectName of the cluster service to which the proxy is to be associated with.
  • LoadBalancePolicy: The class name of the org.jboss.ha.framework.interfaces.LoadBalancePolicy interface implementation to associate with the proxy.

2.7.2.7. The HA HttpInvoker - Clustered RMI/HTTP Transport

The RMI/HTTP layer added in JBoss-3.0.2 has been extended to allow for software load balancing of the invocations in a clustered environment in JBoss-3.0.3. An HA capable extension of the HTTP invoker has been added that borrows much of its functionality from the HA-RMI/JRMP clustering.

To enable HA-RMI/HTTP you need to configure the invokers for the EJB container. This is done through either a jboss.xml descriptor, or the standardjboss.xml descriptor.

2.7.2.8. HttpProxyFactory - Building Dynamic HTTP Proxies

The org.jboss.invocation.http.server.HttpProxyFactory MBean service is a proxy factory that can expose any interface with RMI compatible semantics for access to remote clients using HTTP as the transport.

The HttpProxyFactory supports the following attributes:

  • InvokerName: The server side MBean that exposes the invoke operation for the exported interface. The name is embedded into the HttpInvokerProxy context as the target to which the invocation should be forwarded by the HttpInvoker.
  • JndiName: The JNDI name under which the HttpInvokerProxy will be bound. This is the name clients lookup to obtain the dynamic proxy that exposes the service interfaces and marshalls invocations over HTTP. This may be specified as an empty value to indicate that the proxy should not be bound into JNDI.
  • InvokerURL: This is either the http URL to the InvokerServlet mapping, or the name of a system property that will be resolved inside the client VM to obtain the http URL to the InvokerServlet. This value can itself be a reference to a system property resolved in the server if the value is of the form ${x} where x is the name of the system property.
  • InvokerURLPrefix: If there is no invokerURL set, then one will be constructed via the concatenation of invokerURLPrefix + the local host + invokerURLSuffix. An example prefix is "http://", and this is the default.
  • InvokerURLSuffix: If there is no invokerURL set, then one will be constructed via the concatenation of invokerURLPrefix + the local host + invokerURLSuffix. An example suffix is ":8080/invoker/JMXInvokerServlet" and this is the default.
  • UseHostName: A boolean flag indicating if the InetAddress.getHostName() or getHostAddress() method should be used as the host component of invokerURLPrefix + host + invokerURLSuffix. If true getHostName() is used, false getHostAddress().
  • ExportedInterface: The name of the RMI compatible interface that the HttpInvokerProxy implements.

2.7.2.9. Steps to Expose Any RMI Interface via HTTP

Using the HttpProxyFactory MBean and JMX, you can expose any interface for access using HTTP as the transport. The interface to expose does not have to be an RMI interface, but it does have to be compatible with RMI in that all method parameters and return values are serializable. There is also no support for converting RMI interfaces used as method parameters or return values into their stubs.

The three steps to making your object invocable via HTTP are:

import java.lang.reflect.Method;
import java.util.HashMap;
import org.jboss.invocation.MarshalledInvocation;
 
HashMap marshalledInvocationMapping = new HashMap();
 
// Build the Naming interface method map
Method[] methods = SRPRemoteServerInterface.class.getMethods();
for(int m = 0; m < methods.length; m ++) {
    Method method = methods[m];
    Long hash = new Long(MarshalledInvocation.calculateHash(method));
    marshalledInvocationMapping.put(hash, method);
}
import org.jboss.invocation.Invocation;
import org.jboss.invocation.MarshalledInvocation;
 
public Object invoke(Invocation invocation)
    throws Exception
{
    SRPRemoteServerInterface theServer = <the_actual_rmi_server_object>;
    // Set the method hash to Method mapping
    if (invocation instanceof MarshalledInvocation) {
        MarshalledInvocation mi = (MarshalledInvocation) invocation;
        mi.setMethodMap(marshalledInvocationMapping);
    }

    // Invoke the Naming method via reflection
    Method method = invocation.getMethod();
    Object[] args = invocation.getArguments();
    Object value = null;
    try {
        value = method.invoke(theServer, args);
    } catch(InvocationTargetException e) {
        Throwable t = e.getTargetException();    
        if (t instanceof Exception) {
            throw (Exception) e;
        } else {
            throw new UndeclaredThrowableException(t, method.toString());
        }
    }
 
    return value;
}
<!-- Expose the SRP service interface via HTTP -->
<mbean code="org.jboss.invocation.http.server.HttpProxyFactory"
       name="jboss.security.tests:service=SRP/HTTP">
    <attribute name="InvokerURL">http://localhost:8080/invoker/JMXInvokerServlet</attribute>
    <attribute name="InvokerName">jboss.security.tests:service=SRPService</attribute>
    <attribute name="ExportedInterface">org.jboss.security.srp.SRPRemoteServerInterface
    </attribute><attribute name="JndiName">srp-test-http/SRPServerInterface</attribute>
</mbean>
  • Create a mapping of longs to the RMI interface methods using the MarshalledInvocation.calculateHash method. Here for example, is the procedure for an RMI SRPRemoteServerInterface interface:
  • Either create or extend an existing MBean to support an invoke operation. Its signature is Object invoke(Invocation invocation) throws Exception, and the steps it performs are as shown here for the SRPRemoteServerInterface interface. Note that this uses the marshalledInvocationMapping from step 1 to map from the Long method hashes in the MarshalledInvocation to the Method for the interface.
  • Create a configuration of the HttpProxyFactory MBean to make the RMI/HTTP proxy available through JNDI. For example:

Any client may now lookup the RMI interface from JNDI using the name specified in the HttpProxyFactory (e.g., srp-test-http/SRPServerInterface) and use the obtain proxy in exactly the same manner as the RMI/JRMP version.