Author: David Bernstein (Caboodle Networks, Inc.)
Date: 1 July 2006
Java platform: J2SE 5.0, JMX 1.2
JADE version: JADE 3.4
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.
The directory structure and Ant build file for jademx are both organized according to the JADE standard for add-ons.
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.28 | servlet compilation API | http://jakarta.apache.org/tomcat/ | servlet-api.jar |
Apache Xerces | 2.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.
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.
To use jademx you need to put jademx.jar
in your application's classpath.
There are three parts to using jademx:
There are two ways to code your agent using jademx:
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.
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.)
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).
You can configure your jademx agents by:
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>
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; } // ...
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.
To use jademx under J2SE, an application must:
JadeMXServer
to define the MBeanServer to use,JadeFactory
, which will instantiate the MBeans,JadeRuntime
(which corresponds to a JADE Runtime
) from the fatory,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
.
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.
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
.
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
.
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 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.
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:
jade.jademx.agent.JademxAgent
@platform
part)
jade.jademx.unit.UnitTestingTest.testComparisonCustomization()
for an example of this.)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") ); } }
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.
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; } // ...