Tutorial: jademx - JMX access to JADE agents

Author: David Bernstein (Caboodle Networks, Inc.)

Date: 1 July 2006

Java platform: J2SE 5.0, JMX 1.2

JADE version: JADE 3.4

Overview

jademx is an open source implementation of Java JMX access for JADE software agents. This allows a JADE agent to expose attributes, operations, and notifications in a JMX-compliant manner as a DynamicMBean, as well as providing a convenient method for deploying JADE agents (regardless of whether the JADE agents are jademx-aware), particularly in a J2EE environment. Also, jademx exposes predefined access to both the JADE platform and agents. Further, jademx easily enables the running of a JADE agent unit test under JUnit; the unit level is an ACL message.

jademx can be used with both J2SE and J2EE. jademx has so far been validated with JDK 1.5.0, WebLogic 8.1 SP4, and JBoss 4.0.2.

Although compiling jademx and running it directly under J2SE requires J2SE 5.0, jademx has been run successfully in J2EE environments using a Java virtual machine corresponding to JDK 1.4.1 and JMX 1.0. The supported configuration is expected to require J2SE 5.0 and JMX 1.2 in the future.

Instructions

The directory structure and Ant build file for jademx are both organized according to the JADE standard for add-ons.

Installation

Download the desired version distribution file from the jademx project page on SourceForge. Unzip the distribution file into your desired file directory. It will create a file tree rooted with a directory named jademx, and if you are installing it in a JADE distribution, then it should be installed under JADE_HOME/add-ons, where add-ons is a directory at the same level as the src, lib, and doc directories for JADE.

If you are installing the source (i.e. not the full) version, and therefore will be compiling the source to create your own jademx.jar, then you will need to place external jar files in the .../jademx/lib/ext directory:

module version purpose URL.jar files used
Apache Tomcat common 5.0.28servlet compilation API http://jakarta.apache.org/tomcat/ servlet-api.jar
Apache Xerces2.7.1 XML processing http://xml.apache.org/xerces2-j/ xercesImpl.jar, xml-apis.jar
emma 2.0.5312 code coverage http://emma.sourceforge.net/ emma.jar, emma_ant.jar
JADE 3.4 JADE agent software http://jade.tilab.com/ jade.jar
junit 3.8.1 unit testing http://www.junit.org/ junit.jar

If you want to build jademx, then you will also need Ant 1.6.5.

To run jademx, you need a Java virtual machine for J2SE 5.0 and to compile it you will need the corresponding JDK.

Compilation

If you get the full distribution, you can just use its .../jademx/lib/jademx.jar. Otherwise, if you want to build jademx yourself, then compilation requires various external jar files as described above, Ant as described above, and the JDK as described above.

jademx's ant build.xml file supports the JADE standard targets:

target action
compile (default target) Create all .class files
lib Create .../jademx/lib/jademx.jar
doc Create all javadoc files in the .../jademx/doc directory. Note: jademx creates three kinds of javadoc output: standard (api), maintenance (api-internal which include private declarations), and test (api-test which is the documentation for the jademx JUnit test suite)
dist Create the JADE standard distribution file. Note: use the dist.full target to create a full distribution.

If you are building jademx yourself, then you are strongly encouraged to build by using the all Ant build target, which will from scratch rebuild the code and documentation, run the regression test suite, generate code coverage results, and generate both the standard and full distribution files. Important note: the test suite tests several intentional exceptions; you should look at .../jademx/build/test-results/junit.html to see whether the test suite succeeded, not just at the output from running the tests, which will show multiple expected exception stack traces.

Software Configuration

To use jademx you need to put jademx.jar in your application's classpath.

Usage

There are three parts to using jademx:

agent coding

There are two ways to code your agent using jademx:

simple agent control

No code change is required for simple agent control. jademx can get the name and state for any agent and can also kill any agent if the JADE platform has been launched by jademx.

jademx access to agent attributes, operations, notifications

The best way to learn how to write your own jademx-enabled agent to study the example agent .../jademx/test/jade/jademx/agent/JademxPingAgent.java. It extends jade.jademx.agent.JademxAgent which extends jade.core.Agent and implements jade.jademx.mbean.JadeJMXAgent, which in turn extends javax.management.DynamicMBean. Your own agent should also extend jade.jademx.agent.JademxAgent. Your agent's implementation of any DynamicMBean method (i.e. getAttribute(), getAttributes(), getMBeanInfo(), invoke(), setAttribute(), or setAttributes()) should not call its superclass's implementation of the method, because that could lead to an infinite loop. If the agent implements setup() or takedown(), then it should also be careful to also call its superclass's implementation of the method, or else it will not have access to jademx control. See JademxPingAgent for how this works.

A JademxAgent figures out via a system property how to register self as part of jademx. (An enhancement to JADE where the jade.wrapper package exposed a method to say what class an agent was would allow this to be done more cleanly.)

jademx agent proxy

In addition to standard JMX access to attributes and operations exposed from the agent as a DynamicMBean, you can also use a proxy design pattern for ease of coding. To do this you create a proxy class that extends jade.jademx.agent.JademxAgentProxy and proxies the attribute and operation actions of your real agent.

For an example see .../jademx/test/jade/jademx/agent/JademxPingAgent.java (which implements a proxy) and .../jademx/test/jade/jademx/unit/UnitTestingTest.java (which uses the proxy).

agent configuration

You can configure your jademx agents by:

XML agent configuration

You can configure jademx applications by use of an XML configuration file, usually named jademx-config.xml, which may be read from either a resource or URL. The definition of this file is available at .../jademx/src/jade/jademx/config/jademxconfig.xsd. An example configuration is:

<?xml version="1.0" encoding="UTF-8"?>
<jademx-config>
  <runtime>
    <platform>
      <options>
        <option>port=1098</option>
      </options>
      <agent-specifiers>
        <agent-specifier>
          <name>pinger1</name>
          <class>jade.jademx.agent.JademxPingAgent</class>
        </agent-specifier>
        <agent-specifier>
          <name>pinger2</name>
          <class>jade.jademx.agent.JademxPingAgent</class>
          <arguments>
            <argument>foo</argument>
            <argument>bar</argument>
            <argument class="jade.jademx.agent.Arg">baz</argument>
          </arguments>  
        </agent-specifier>
      </agent-specifiers>      
    </platform>
  </runtime>
</jademx-config>  
programmatic agent configuration

You can also configure jademx applications programmatically using the class jade.jademx.config.jademx.onto.JademxConfig. It worth noting that JademxConfig implements jade.content.AgentAction and can be passed around as content in ACL messages; see .../jademx/test/jade/jademx/config/jademx/onto/JademxConfigTest.java for an example. Here is a code fragment demonstrating programmatic agent configuration (exception handling omitted):

// ...
	
import jade.jademx.config.jademx.onto.ConfigAgentSpecifier;
import jade.jademx.config.jademx.onto.ConfigPlatform;
import jade.jademx.config.jademx.onto.ConfigRuntime;
import jade.jademx.config.jademx.onto.JademxConfig;
	
// ...
	
    /** MBeanServer being used */
    private MBeanServer mBeanServer = null;
    /** created JadeRuntimeMBean */
    private JadeRuntimeMBean runtime = null;
    /** ObjectName for JadeRuntimeMBean */
    private ObjectName runtimeON = null;
	
// ...
	
    protected void setUp() throws Exception {
        // create a jademx config of one ping agent
        JademxConfig jademxConfig = buildPingerPingeeAgentCfg();
        // start the configuration
        // first get JadeMXServer
        JadeMXServer jadeMXServer = null;
        jadeMXServer = JadeMXServerFactory.jadeMXServerBySysProp();
        mBeanServer = jadeMXServer.getMBeanServer();
        // now get a factory to use
        JadeFactory jadeFactory = new JadeFactory( jadeMXServer );
        // instantiate the configuration
        runtime = jadeFactory.instantiateRuntime( jademxConfig );
        // get ObjectName for runtime
        runtimeON = runtime.getObjectName();
    }	
	
    private JademxConfig buildPingerPingeeAgentCfg() {
        // create jademx configuration object
        JademxConfig jademxConfig = new JademxConfig();
        // make object to configure a JadeRuntime and add it to configuration
        ConfigRuntime runtime = new ConfigRuntime();
        jademxConfig.addRuntime( runtime );
        // make object to configure a JadePlatform and add it to configuration
        ConfigPlatform platform = new ConfigPlatform();
        runtime.addPlatform( platform );
        platform.addOption( "port=1917" );
        platform.addOption( "nomtp=true" );
        // make objects to configure 2 JadeAgents and add them to configuration
        ConfigAgentSpecifier agentSpecifier;
        agentSpecifier =
            new ConfigAgentSpecifier(
                    "pinger",
                    "jade.jademx.agent.JademxNopAgent" );
        platform.addAgentSpecifier( agentSpecifier );
        agentSpecifier =
            new ConfigAgentSpecifier(
                    "pingee",
                    "jade.jademx.agent.JademxPingAgent" );
        platform.addAgentSpecifier( agentSpecifier );
        // return the programmatic jademx configuration
        return jademxConfig;
    }

// ...
	

deployment environment

A set of agents deployed by jademx may run:

A particularly helpful method for becoming familiar with jademx is to use the JBoss servlet context or service methodologies, because JBoss uses JMX as a central technology and has a simple and straightforward browser-based way to access MBeans. It is also relatively easy to install.

J2SE

To use jademx under J2SE, an application must:

  1. instantiate a JadeMXServer to define the MBeanServer to use,
  2. construct a JadeFactory, which will instantiate the MBeans,
  3. get a JadeRuntime (which corresponds to a JADE Runtime) from the fatory,
  4. use the runtime to create JadePlatform MBeans.

Afterwards, the application should call JadeRuntime.shutdown() to shut down cleanly.

A good example of how to do this is at .../jademx/test/jade/jademx/mbean/JadeAgentTest.java.

J2EE servlet context

jademx provides the jade.jademx.server.JadeServletContextListener class to create and destroy a jademx runtime when the corresponding servlet context is created and destroyed. To use this method, you must use a web.xml file in a J2EE .war file. The following context parameters are available for setting in the web.xml file:

name needed description
jade.service.config.doc.resource optional name of resource with jademx XML configuration (defaults to jademx-config.xml)
jade.service.config.doc.url optional URL of jademx XML configuration (mutually exclusive with jade.service.config.doc.resource)
jade.service.jndi.name optional JNDI name to register as (defaults to jade)
jade.service.object.name.domain optional ObjectName domain to use for created MBeans (defaults to jade)
jademx.server.class.name required name of the class used to find the MBeanServer for jademx

jademx comes with files already written that you can use to deploy under JBoss and WebLogic. You can also write your own for other application servers.

JBoss

Look in .../jademx/src/jade/jademx/config/jboss for web.xml and jboss-web.xml files that you can use or modify for deploying your application to JBoss. The jade.jademx.server.JBossMBeanServerLocator class uses the JBoss class MBeanServerLocator to locate the MBeanServer to use.

For an example of how to create a WAR and deploy to JBoss, see the build.xml targets jboss.jademx.ping.war and deploy.jboss.jademx.ping.war.

WebLogic

Look in .../jademx/src/jade/jademx/config/weblogic for web.xml and weblogic.xml files that you can use or modify for deploying your application to WebLogic. The jade.jademx.server.WebLogicMBeanHome class uses the WebLogic interface weblogic.management.MBeanHome to locate the MBeanServer to use.

For an example of how to create a WAR and deploy to WebLogic, see the build.xml targets weblogic.jademx.ping.war and deploy.weblogic.jademx.ping.war.

other J2EE application servers

To use another application server, you will need to write your own web.xml file and any application server-specific files.

Should you want to contribute any files back to the jademx project, then implement them so that compilation doesn't require implementation-specific libraries (use reflection or jade.jademx.server.StaticMethod like jade.jademx.server.JBossMBeanServerLocator does).

JBoss service

JBoss has its own concept of service. If you would like to deploy your JADE application as a JBoss service, see the build.xml targets jboss.jademx.ping.ear and deploy.jboss.jademx.ping.ear. jademx provides the files jboss-app.xml and jboss-service.xml to help in building your .sar file.

Testing using JUnit

Steps using JUnit

jademx easily enables the running of a JADE agent unit test under JUnit; the unit level is an ACL message. To do this, you take the following steps:

  1. the class for the agent-under-test must extend jade.jademx.agent.JademxAgent
  2. set up the agent-under-test and a dummy agent to receive the generated message
  3. wait to make sure that your agent as a dynamic MBean has bound to the MBean server
  4. set the message that you will inject to the agent-under-test (important: make sure that the sender and receiver agents use local names, i.e. without the@platformpart)
  5. set the message that you will expect to receive back
  6. set what strings in the messages can vary (This is so you don't get spurious differences for things like the agent platform or reply-id.) There are two ways to do this:
  7. assert that the expected message is received after the agent-under-test receives the injected message

JUnit example

A real example of this, taken from the jademx test suite, follows:

package jade.jademx.unit;

import jade.jademx.JadeMXSuiteTest;
import jade.jademx.agent.JademxAgent;
import jade.jademx.agent.JademxPingAgentProxy;
import jade.jademx.config.jademx.onto.ConfigAgentSpecifier;
import jade.jademx.config.jademx.onto.ConfigPlatform;
import jade.jademx.config.jademx.onto.ConfigRuntime;
import jade.jademx.config.jademx.onto.JademxConfig;
import jade.jademx.mbean.JadeFactory;
import jade.jademx.mbean.JadePlatformMBean;
import jade.jademx.mbean.JadeRuntimeMBean;
import jade.jademx.server.JadeMXServer;
import jade.jademx.server.JadeMXServerFactory;
import jade.jademx.util.AclMsgCmp;
import jade.jademx.util.ThrowableUtil;

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

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

public class UnitTestingTest extends TestCase {

    // original inject message before AIDs localized
    //  "(REQUEST"+
    //  " :sender  ( agent-identifier :name pinger@diamond:1099/JADE )"+
    //  " :receiver  (set ( agent-identifier :name pingee@diamond:1099/JADE ) )"+
    //  " :content  \"((action (agent-identifier :name pingee@diamond:1099/JADE) (ping)))\" "+
    //  " :reply-with  ping1  :language  fipa-sl  :ontology  ping  :protocol  fipa-request"+
    //  " :conversation-id  ping-conv-1127942689278-18131271 )";

    /** reply-id used in message to inject */
    private static final String INJECT_REPLY_ID = "pinger@diamond:1099/JADE1127942689278";
    /** AID for pingee agent in message to inject */
    private static final String PINGEE_INJECT_AID = "pingee@diamond:1099/JADE";
    /** AID for pinger agent in message to inject */
    private static final String PINGER_INJECT_AID = "pinger@diamond:1099/JADE";
    /** local name for pingee agent */
    private static final String PINGEE_LOCAL_NAME = "pingee";
    
    /** message to inject */
    private final static String injectMsg = 
        "(REQUEST"+
        " :sender  ( agent-identifier :name pinger )"+
        " :receiver  (set ( agent-identifier :name pingee ) )"+
        " :content  \"((action (agent-identifier :name pingee@diamond:1099/JADE) (ping)))\" "+
        " :reply-with  ping1  :language  fipa-sl  :ontology  ping  :protocol  fipa-request"+
        " :conversation-id  ping-conv-1127942689278-18131271 )";
    
    /** message to expect */
    private final static String expectMsg = 
        "(INFORM"+
        " :sender  ( agent-identifier :name pingee@diamond:1099/JADE )"+
        " :receiver  (set ( agent-identifier :name pinger@diamond:1099/JADE ) )"+
        " :content  \"((result (action (agent-identifier :name pingee@diamond:1099/JADE) (ping)) pong))\" "+
        " :reply-with  pinger@diamond:1099/JADE1127942689278  :in-reply-to  ping1  "+
        ":language  fipa-sl  :ontology  ping  :protocol  fipa-request"+
        " :conversation-id  ping-conv-1127942689278-18131271 )";
    
    // SETUP/TEARDOWN
    
    /** MBeanServer being used */
    private MBeanServer mBeanServer = null;
    /** created JadeRuntimeMBean */
    private JadeRuntimeMBean runtime = null;
    /** ObjectName for JadeRuntimeMBean */
    private ObjectName runtimeON = null;
    
    protected void setUp() throws Exception {
        // create a jademx config of one ping agent
        JademxConfig jademxConfig = buildPingerPingeeAgentCfg();
        // start the configuration
        // first get JadeMXServer
        JadeMXServer jadeMXServer = null;
        try {
            jadeMXServer = JadeMXServerFactory.jadeMXServerBySysProp();
        } 
        catch (Exception e) {
            fail(ThrowableUtil.errMsg("problem getting jademx server",e));
        }
        mBeanServer = jadeMXServer.getMBeanServer();
        // now get a factory to use
        JadeFactory jadeFactory = new JadeFactory( jadeMXServer );
        // instantiate the configuration
        try {
            runtime = jadeFactory.instantiateRuntime( jademxConfig );
        } 
        catch (Exception e) {
            fail(ThrowableUtil.errMsg("problem instantiating configuration",e));
        }
        // get ObjectName for runtime
        runtimeON = runtime.getObjectName();
    }
    
    protected void tearDown() throws Exception {
        // shutdown the configuration
        try {
            mBeanServer.invoke( runtimeON, 
                    JadeRuntimeMBean.OPER_SHUTDOWN, 
                    new Object[]{},
                    JadeRuntimeMBean.OPER_SHUTDOWN_SIGNATURE );
        }
        catch ( Exception e ) {
            fail(ThrowableUtil.errMsg("problem shutting down JADE runtime", e));
        }
    }
    
    /**
     * make a configuration for testing use programmatically
     */
    private JademxConfig buildPingerPingeeAgentCfg() {
        JademxConfig jademxConfig = new JademxConfig();
        ConfigRuntime runtime = new ConfigRuntime();
        jademxConfig.addRuntime( runtime );
        ConfigPlatform platform = new ConfigPlatform();
        runtime.addPlatform( platform );
        platform.addOption( "port=1917" );
        platform.addOption( "nomtp=true" );
        ConfigAgentSpecifier agentSpecifier;
        agentSpecifier =
            new ConfigAgentSpecifier(
                    "pinger",
                    "jade.jademx.agent.JademxNopAgent" );
        platform.addAgentSpecifier( agentSpecifier );
        agentSpecifier =
            new ConfigAgentSpecifier(
                    PINGEE_LOCAL_NAME,
                    "jade.jademx.agent.JademxPingAgent" );
        platform.addAgentSpecifier( agentSpecifier );
        return jademxConfig;
    }
	
    // TESTS
    
    /** test unit testing with calls via MBeanServer */
    public void testUnitTest() {

        // find the agent MBean
        ObjectName agentON = null;
        JademxPingAgentProxy pingAgentProxy = null;
        try {
            ObjectName platformONs[] = 
                (ObjectName[])mBeanServer.getAttribute( 
                        runtimeON, JadeRuntimeMBean.ATTR_PLATFORM_OBJECT_NAMES);
            ObjectName platformON = platformONs[0];
            agentON = (ObjectName)mBeanServer.invoke( platformON, 
                    JadePlatformMBean.OPER_GET_AGENT_OBJECT_NAME, 
                    new Object[]{PINGEE_LOCAL_NAME},
                    JadePlatformMBean.OPER_GET_AGENT_OBJECT_NAME_SIGNATURE );
            if ( null == agentON ) {
                throw new Exception("null agent ObjectName");
            }
            pingAgentProxy = new JademxPingAgentProxy( agentON, mBeanServer );
        }
        catch (Exception e) {
            fail(ThrowableUtil.errMsg("problem finding agent MBean", e));
        }
        
        // make sure agent MBean bound to JademxAgent
        JademxAgent.assertIsJademxAgent( agentON, mBeanServer );
        
        // set unit testing attributes on agent
        try {
            // set the message to inject
            pingAgentProxy.setUnitTestInjectMessage( injectMsg );
            
            // set the expected message
            pingAgentProxy.setUnitTestExpectedMessage( expectMsg );
        
            // set message comparison variable properties
            pingAgentProxy.unitTestAddVariable( PINGER_INJECT_AID, AclMsgCmp.AID_MAP );
            pingAgentProxy.unitTestAddVariable( PINGEE_INJECT_AID, AclMsgCmp.AID_MAP );
            pingAgentProxy.unitTestAddVariable( INJECT_REPLY_ID, AclMsgCmp.REPLY_ID_MAP );
        }
        catch ( Exception e ) {
            fail(ThrowableUtil.errMsg("problem setting agent test setup", e));
        }
        
        // assert the expected message
        JademxAgent.assertExpectedMessage( agentON, mBeanServer );

    }
    
    // SUITE

    public static Test suite() {
        return new TestSuite( 
                UnitTestingTest.class, 
                JadeMXSuiteTest.nameWithClass( UnitTestingTest.class, 
                "testing unit testing") );
    }
    
}
	

controlling message comparison

You can control precisely which slots are compared by setting the agent's UNIT_TEST_CMP_* attributes, for example agentProxy.setUnitTestCmpReplyBy( false ) if you have a proxy to your agent.

getting actual compared message

You can read the attribute UnitTestActualMessage to jade.jademx.agent.JademxAgent to see what actual message was compared against the expected message after asserting the expected message. For example:

       // ...	
       try {
           JademxAgent.assertExpectedMessage( "my test failed!", agentObjectName, mBeanServer );
       }
       catch ( junit.framework.AssertionFailedError afe ) {
           System.err.println("actual failing message:"+agentProxy.getUnitTestActualMessage());
           throw afe;
       }
       // ...	

SourceForge.net Logo