Java Dynamic Management Kit 5.0 Tutorial

Chapter 14 M-Let Class Loader

The “dynamic” in Java Dynamic Management Kit (DMK) not only stands for dynamic loading, it also stands for dynamic downloading. The agent service that provides this functionality is the m-let class loader. M-let stands for management applet, an HTML-style tag that tells the class loader how to retrieve the desired class. Using this information, the m-let loader can retrieve an MBean class from a remote location given as a URL and create it in the agent.

The m-let resides in a separate text file that acts as a loading manifest. The contents of the m-let file let you specify any number of classes to load, possibly a different source for each, arguments for the class constructor, and the object name for the instantiated MBean. Because this mechanism is sometimes too heavy, the m-let loader can also be used to load classes directly and create MBeans in the agent.

The m-let loader is a service implemented as an MBean, so it can be called either directly by the agent or remotely by a management application. It can also be managed remotely, which allows a manager to effectively “push” MBeans to an agent: the manager instantiates the m-let loader in an agent and instructs it to load classes from a predetermined location.

This chapter covers the following topics:

M-Let Loader

In Java DMK 5.0, the m-let loader is a class loader object that extends the URLClassLoader class of the java.net package to simplify the downloading service it provides.

The m-let loader service is an instance of the MLet class in the javax.management.loading package. It is also an MBean that can be accessed remotely. It provides m-let file loading and a shortcut method. In addition, can be used directly as a class loader, without requiring an m-let file.

We will start by demonstrating the usage of the m-let service as it would be used in an agent or in an MBean. In our example, the agent application creates an MBean server and then the m-let loader.


Example 14–1 Instantiating the MLet Class

// Parse debug properties and command line arguments.
[...]

// Instantiate the MBean server
MBeanServer server = MBeanServerFactory.createMBeanServer();
String domain = server.getDefaultDomain();

// Create a new MLet MBean and add it to the MBeanServer.
String mletClass = "javax.management.loading.MLet";
ObjectName mletName = new ObjectName(domain + ":name=" + mletClass);
server.createMBean(mletClass, mletName);

There is no special initialization that needs to be done before loading classes through an m-let file.

Loading MBeans from a URL

In order to download an MBean, we must first have its corresponding m-let definition in an HTML file. In our example, we define the following file with two MLET tags:


Example 14–2 The M-Let File

<HTML>
<MLET
  CODE=EquilateralTriangle.class
  ARCHIVE=EquilateralTriangle.jar
  NAME=MLetExample:name=EquilateralTriangle,id=1
>
<ARG TYPE=java.lang.Integer VALUE=8>
</MLET>
<MLET
  CODE=EquilateralTriangle.class
  ARCHIVE=EquilateralTriangle.jar
  NAME=MLetExample:name=EquilateralTriangle,id=2
>
<ARG TYPE=java.lang.Integer VALUE=15>
</MLET>
</HTML>

This file tells the m-let loader to create two MBeans with the given object names, using the given classes in the JAR files. The JAR files must be located in the same directory as this file, regardless of whether the directory is on a local or remote host. The MLET tag can also specify a CODEBASE, which is an alternate location for the JAR file. The MLET tag is fully defined in the JMX specification.

To download the MBeans specified in the m-let file we call the getMBeansFromURL method and analyze the result:


Example 14–3 Calling the getMBeansFromURL Method

// the url_2 string is read from the command line
echo("\tURL = " + url_2);
Object mletParams_2[] = {url_2};
String mletSignature_2[] = {"java.lang.String"};
Set mbeanSet = (Set) server.invoke(mletName, "getMBeansFromURL",
     mletParams_2, mletSignature_2);

for (Iterator i = mbeanSet.iterator(); i.hasNext(); ) {
    Object element = i.next();
    if (element instanceof ObjectInstance) {
        // Success, we display the new MBean's name
        echo("\tOBJECT NAME = 
			" + ((ObjectInstance)element).getObjectName());
    } else {
        // Failure, we display why
        echo("\tEXCEPTION = " + ((Throwable)element).getMessage());
    }
}

The m-let loader is the class loader, and it handles just a list of code-bases that it has accessed directly. You can view this list by calling the getURLs method of the m-let loader MBean.

This behavior means that the getMBeansFromURL method does not need to return the object names of class loaders it has used. Instead it just returns either the object instance of the downloaded and registered MBean or a Throwable object in case of an error or an exception. These are returned in a Set object containing as many elements as there are MLET tags in the target m-let file.

Shortcut for Loading MBeans

This behavior also simplifies any repeated loading of the classes after they have been loaded from an m-let file. Because the m-let loader has already used the code-base of the MBean, it is available to be used again. All you need to do is specify the object name of the m-let loader as the class loader when creating the MBean.

You can also load other MBeans in the same code-base, once the code-base has been accessed by a call to the getMBeansFromURL method. In our example we will just download another MBean of the EquilateralTriangle class.


Example 14–4 Reloading Classes in the M-Let Class Loader

// Create another EquilateralTriangle MBean from its class
// in the EquilateralTriangle.jar file.
String triangleClass = "EquilateralTriangle";
ObjectName triangleName = new ObjectName(
    "MLetExample:name=" + triangleClass + ",id=3");
Object triangleParams[] = {new Integer(20)};
String triangleSignature[] = {"java.lang.Integer"};

server.createMBean(triangleClass, triangleName, mletName,
                   triangleParams, triangleSignature);

Again, loading classes from known code-bases or reloading a class directly from its JAR file implies that the agent or MBean programmer has some knowledge of the code-bases and JAR file contents at runtime.

Loading MBeans Directly

Because the m-let loader object is a class loader, you can use it to load classes directly, without needing to define an m-let file.

Before you can load an MBean directly, you need to add the URL of its code-base to the m-let loader's internal list. Then we just use the m-let loader's object name as the class loader name when creating the MBean. Here is the code to do this in the agent example:


Example 14–5 Using the M-Let MBean as a Class Loader

// Add a new URL to the MLet class loader
// The url_1 string is read from the command line
Object mletParams_1[] = {url_1};
String mletSignature_1[] = {"java.lang.String"};
server.invoke(mletName, "addURL", mletParams_1, mletSignature_1);

// Create a Square MBean from its class in the Square.jar file.
String squareClass = "Square";
ObjectName squareName = new ObjectName(
    "MLetExample:name=" + squareClass);
Object squareParams[] = {new Integer(10)};
String squareSignature[] = {"java.lang.Integer"};
server.createMBean(squareClass, squareName, mletName,
                   squareParams, squareSignature);

You only need to add the URL to the m-let loader the first time you download a class. Once it is added, you can load it as many times as necessary by calling createMBean directly.

Because this loading mechanism does not use the MLET tag, the programmer must ensure that either the downloaded class provides its own object name or, as in Example 14–5, the agent provides one.

The fact that the m-let loader is also a class loader into which you can load multiple URLs raises the issue of name spaces. If there are two classes with the same name within the code-bases defined by the set of all URLs, the m-let loader will load one of them non-deterministically. To specify one of them precisely, do not add the URL of the second code-base to the m-let loader. Instead, create a second m-let loader MBean to which you can add the URL for the second version of the class. In this case, you will have one m-let MBean that can load one version of the class and another m-let MBean that can load the other.

Running the M-Let Agent Example

To run the m-let agent example, you must have installed the Java DMK, and set your classpath accordingly. This example is located in the examplesDir/MLetAgent/ directory. See “Directories and Classpath” in the Preface for details.

In our example, we have two MBeans representing geometrical shapes. Before running the example, we compile them and create a JAR file for each. We also compile the agent application at the same time.


$ cd examplesDir/MLetAgent/
$ javac -classpath classpath *.java

$ jar cf Square.jar Square.class SquareMBean.class
$ rm Square.class SquareMBean.class

$ jar cf EquilateralTriangle.jar EquilateralTriangle.class \
EquilateralTriangleMBean.class
$ rm EquilateralTriangle.class EquilateralTriangleMBean.class

The agent command line requires you to specify first the URL of a JAR file for directly loading the Square class, then the URL of the m-let file. We have left these files in the examples directory, but you could place them on a remote host. With the Korn shell on the Solaris platform, type the following command:


$ java -classpath classpath Agent \ 
file:${PWD}/Square.jar file:${PWD}/GeometricShapes.html

In the output of the agent, you can see it create the m-let loader MBean, and then download classes to create MBeans. It starts with the direct loading of the Square class, and then loads from the HTML file that specifies two EquilateralTriangle MBeans to be loaded. Once these have been loaded, we can see the third one that is loaded through the class loader shortcut.

This agent uses the tracing mechanism, and you can select to receive the messages from the m-let loader by specifying the -DINFO_MLET property on the command line. The tracing mechanism is covered in the Java Dynamic Management Kit 5.0 Tools Reference and in the Javadoc API of the com.sun.jdmk.TraceManager class (for receiving traces) and the com.sun.jdmk.trace.Trace class (for producing traces).

The agent then starts an HTML adaptor so that we can easily view the new MBeans. In them we can see that the values contained in the ARG tags of the m-let file were used to initialize the MBeans. Point your web browser to the following URL and click on the MBeans in the MLetExample domain:

http://localhost:8082/

When you have finished, press Control-C in the window where you started the agent.

M-Let Loading From a Manager

Because the MLet class is an MBean, the m-let loader service is fully manageable from a remote manager. This lets a manager create an m-let loader in an agent, add URLs to its list of code-bases, and create MBeans whose classes must be downloaded first.

Using the m-let class loader, we can again implement a “push” mechanism originating from a management application. In fact, it is even easier due to the direct class loading that the MLet MBean allows.

In the example, our manager will create an m-let loader in an agent and have it load new MBean classes. Started with only an RMI connector and an HTML adaptor, we can see at the end that the agent contains all of the new MBeans loaded from JAR files, along with the m-let loader MBean. The initialization of the manager is very simple:


Example 14–6 main Method of the Manager Application

public Client() {
    
    // Enable the trace mechanism
    [...]

    // Connect a new RMI connector client to the agent
    connectorClient = new RmiConnectorClient();

    // Use the default address (localhost)
    RmiConnectorAddress address = new RmiConnectorAddress();
    try {
        connectorClient.connect(address);
    } catch (Exception e) {
        echo("Could not connect to the agent!");
        e.printStackTrace();
        System.exit(1);
    }
}

public static void main(String[] args) {

    // Parse command line arguments.
    [...]

    // Call the constructor to establish the connection
    Client client = new Client();

    // Run the MLet example (see below)
    client.runMLetExample();

    // Disconnect connector client from the connector server.
    client.connectorClient.disconnect();

    System.exit(0);
}

Asking the Agent to Load Classes

Now that the manager is connected to the client, we can ask it to load classes. First we have it create an m-let loader MBean that we can use to download classes. Then we demonstrate the various ways of loading classes:

The following code is from the manager's runMLetExample method. The code is identical to the code of the agent example except that we now go through the RemoteMBeanServer interface of the connector client instead of directly through the MBean server.


Example 14–7 Calling the getMBeansFromURL Method Remotely

// Get the domain name from the MBeanServer.
String domain = connectorClient.getDefaultDomain();

// Create a new MLet MBean and add it to the MBeanServer
String mletClass = "javax.management.loading.MLet";
ObjectName mletName = new ObjectName(domain + ":name=" + mletClass);
connectorClient.createMBean(mletClass, mletName);

[...]

// Create new EquilateralTriangle MBeans through MLET tags
// The url_2 string is read from the command line
Object mletParams_2[] = {url_2};
String mletSignature_2[] = {"java.lang.String"};
Set mbeanSet = (Set) connectorClient.invoke(
    mletName, "getMBeansFromURL", mletParams_2, mletSignature_2);

for (Iterator i = mbeanSet.iterator(); i.hasNext(); ) {
    Object element = i.next();
    if (element instanceof ObjectInstance) {
        // Success
        echo("OBJECT NAME = " + 
			((ObjectInstance)element).getObjectName());
    } else {
        // Failure
        echo("EXCEPTION = " + ((Throwable)element).getMessage());
    }
}

Now that the class loader has used the code-base of the JAR file, we can create more of the MBeans from the same JAR file. We invoke the createMBean method of the server with the object name of the class loader.


Example 14–8 Reloading MBeans from an Existing Code-Base

// Create another EquilateralTriangle MBean from the same jar file
// used in the MLET file
String triangleClass = "EquilateralTriangle";
ObjectName triangleName = new ObjectName(
    "MLetExample:name=" + triangleClass + ",id=3");
Object triangleParams[] = {new Integer(20)};
String triangleSignature[] = {"java.lang.Integer"};
connectorClient.createMBean(triangleClass, triangleName, mletName,
                            triangleParams, triangleSignature);

Finally, if we have a different code-base not associated with an m-let file, we can give its URL directly to the loader. This enables us to ask the agent to create almost any MBean, imitating a class “push” mechanism.


Example 14–9 Implementing a “Push” Operation

// Add a new URL to the MLet MBean to look for classes
// The url_1 string is read from the command line
Object mletParams_1[] = {url_1};
String mletSignature_1[] = {"java.lang.String"};
connectorClient.invoke(mletName, "addURL",
                       mletParams_1, mletSignature_1);

// Create a new Square MBean from its class in the Square.jar file
String squareClass = "Square";
ObjectName squareName = new ObjectName(
    "MLetExample:name=" + squareClass);
Object squareParams[] = {new Integer(10)};
String squareSignature[] = {"java.lang.Integer"};
connectorClient.createMBean(squareClass, squareName, mletName,
                            squareParams, squareSignature);

In this way, the manager can make code available on the network, and it can direct its agents to load the classes to create new MBeans ready for management. This mechanism can be used to distribute new resources, provide new services, or update applications, all under the control of the manager.

Running the M-Let Manager Example

The MBeans in the agent and manager (client) examples are identical, and we will set up the example in exactly the same manner.


$ cd examplesDir/MLetClient/
$ javac -classpath classpath *.java

$ jar cf Square.jar Square.class SquareMBean.class
$ rm Square.class SquareMBean.class

$ jar cf EquilateralTriangle.jar EquilateralTriangle.class \
EquilateralTriangleMBean.class
$ rm EquilateralTriangle.class EquilateralTriangleMBean.class

The manager is written to be run on the same host as the agent application. To run it on a different host, modify the code for the Client class constructor where the agent address is specified (see Example 14–6). You can place the JAR files and the m-let file on a remote host and specify its new URL as the parameter to the manager application. We run the example with this file in the current directory.

Before starting the manager, you must start the agent. Here we give commands for starting the applications from the same terminal window running the Korn shell. On the Windows 2000 platform, you will have to start each application in a separate window.


$ java -classpath classpath Agent &
$ java -classpath classpath Client \ 
file:${PWD}/Square.jar file:${PWD}/GeometricShapes.html

In the output of the manager, you can see it create the m-let service MBean, and then load all of the MBeans from different sources. Connect to the agent in a web browser at the following URL:

http://localhost:8082/

Reload its agent view every time the manager pauses, to see the MBeans as they are created.

The agent terminates after it disconnects its connector client. When you have finished viewing the agent, type the following commands to stop the agent application:


$ fg
java [...] Agent <Control-C>
^C$ 

Secure Class Loading

Because class loading exposes an agent to external classes, the Java DMK offers security within the m-let service.

Code Signing

Code signing is a security measure that you can use to identify the originator of a downloaded class. The m-let service will enforce code signatures if it is instantiated in secure mode. One of the constructors of the MLetSrv class takes a boolean parameter that specifies the security mode. For obvious security reasons, the security mode cannot be modified once the m-let service is instantiated.

When the m-let service is running in secure mode, it will only load classes and native libraries that are signed by a trusted party. A trusted party is identified by a key: this key was used to sign the code and a copy of the key is given to all parties that want to download the signed class. Therefore, you must identify trusted keys in your agent before attempting to download their signed classes.


Note –

Downloading native libraries always requires a custom security manager, regardless of whether they are trusted or not.


In the MLet class, security is not determined when you instantiate the m-let service. Rather, security is enabled or disabled for your entire agent application, including any class loaders used by the m-let service.

To enable security, start your agent applications with the java.lang.SecurityManager property on the command line. Then, when the m-let service loads a class through one of its class loaders, the class loader will check the origin and signature of the class against the list of trusted origins and signatures.

The tools involved in signing a class file are the jar, keytool, and jarsigner utilities. On the host where the agent application will download a class, you define a set of permissions for signatures and URL origins. Then, you need to use the policytool utility to generate a java.policy file containing the trusted signatures. Refer to the JDK documentation for the description of these utilities.

When the agent application is started with a security manager, it will check this policy file to ensure that the origin and signature of a downloaded class match a trusted origin and a trusted signature. If they do not match, the code is not trusted and cannot be loaded.

When the agent application is started without the security manager, all classes and native libraries can be downloaded and instantiated, regardless of their origin and signature, or lack thereof.