The promise of J2EE was to build more
robust, scalable, and secure enterprise
systems. J2EE promised that we could
do it quickly and easily since J2EE is
supposed to take the complexity out of
building powerful distributed systems.
But as with the J2EE spec itself, these
systems usually suffer through management
only as an afterthought.
Many management systems focus on
proprietary interfaces that react to specific
events. They offer solutions in
which your management is tied up with
the system you are managing.
JMX4ODP decouples testing and management
from the target system and
focuses on using reusable components
that are bound and deployed using
XML configurations.
This article walks you through the
process of setting up a basic service
monitor and event handler for a common
J2EE n-tier system. Developers of
J2EE systems will be able to use
JMX4ODP to create testing suites to
help them develop more reliable systems.
J2EE application administrators
will be able to use JMX4ODP to simplify
and regulate the management of
deployed systems.
JUnit and JMX are the two core foundations
for JMX4ODP's approach to
management. JUnit is becoming the de
facto standard for unit testing. JUnit
support is common in most IDE and
test suites. JMX is Java's official answer
to system management. Groups like
JBoss are pushing JMX even further to
make it a key piece in building complex
infrastructures. JMX support is common
in major J2EE application servers.
IBM has even integrated JMX functionality
with its popular Tivoli suite.
JavaMail: The J2EE extension for
e-mail (http://java.sun.com/products/javamail/).
JUnit as a Diagnostics Tool
JUnit bills itself as a regression-testing
suite for code objects, but it's not much
of a leap to see it as a tool for distributed
system diagnostics. JUnit runs tests by
instantiating objects, invoking their
methods with known inputs, and checking
the output against expected returns.
Distributed systems are built over time
as a collection of services - some standardized,
some proprietary. Each service
can be treated as an object for JUnit to
test. In a typical J2EE installation, you
have HTTP daemons, servlet engines,
JNDI trees, RMI-enabled EJB containers,
and databases accessed via JDBC.
Figure 1 illustrates these services as
extensions of common protocols or as
classes of objects to test. Since a system
has a limited number of supported protocols,
you can save a lot of coding by
creating a base test class for each protocol
and extending it as needed. The
JMX4ODP's org.jmx4odp.junit DiagnosticWorkers
package contains classes
for testing HTTP, RMI, and JDBC services.
JUnit Test for Services
The JMX4ODP test classes follow the
JUnit "assert" pattern. Each possible
test method name starts with "assert,"
allowing developers to easily identify
testing methods versus utility methods.
Each method is stateless, allowing multiple
testing objects to utilize the same
underlying protocol test object, e.g., the
HttpClientTest object contains methods
for acquiring an HttpURLConnection
and testing the connection for
HTTP statuses and content.
By extending these basic service test
classes to encapsulate a series of stateful
tests, we get objects that are simply beans
that contain a set of tests to run on a
service. For example, you could extend
the HTTP protocol test class to create an
object that checks if you can reach a URL.
You could then extend this class to make
a test that checks a secured URL.
JMX4ODP's org.jmx4odp.junitDiagnosticWorkers
package contains tests for
three basic services: HTTP, RMI, and
JDBC. Each is implemented as a stateful
bean that you instantiate, set parameters
for, and then hand over to JUnit to run.
To test HTTP services with
BasicHttpUrlTest, set the URL you want
to check and hand the object to JUnit,
which will invoke each method that
starts with the word "test."
BasicHttpUrlTest's only test method is
"test_URLOk," which checks the URL
for a returned HTTP: 200 OK.
BasicEjbTest tests EJB services. It can
use the settings in your jndi.properties
to connect to your JNDI tree, or you
can set them programmatically. You
must set the JNDI name of the EJB. It
contains one test, "test_AccessEJB,"
which tries to retrieve a RemoteObject
from the JNDI tree by the set name and
invoke getEJBMetaData upon it.
The BasicJDBCTest is more complicated.
You need to set the JDBC URL, a
test SQL select statement, and the database
username and password. You can
set an optional record threshold, which
defaults to 1. This test object checks for
two things before giving the service a
green light. First, it runs "test_Can-
Connect" to check if JDBC can connect
to the database. Second, it runs "test_
SelectGood" to ensure that the test SQL
select statement returns at least as
many records as the threshold is set to.
Building Your TestSuite
JUnit has the ability to hierarchically
arrange tests using TestSuite objects,
but TestSuites are maintained programmatically,
which is a big maintenance
hit. JMX4ODP uses the org.jmx4odp.
junitDiagnosticWorkers.SuiteAssembler
object to translate an XML file into a
TestSuite object, which cuts all the coding
from maintenance.
Figure 2 shows the TestSuite XML
entities used by the SuiteAssembler.
TestSuite objects can hold either another
TestSuite or TestCase. A TestCase is
one of the stateful test beans. You
invoke the get/set methods for service
and test properties such as URLs and
thresholds with the INVOKE element,
which can take arguments of
java.lang.String, boolean, and int. A
TestSuitexml to test Yahoo's Web servers
would look like:
Figure 2
<TESTSUITE name="Web Servers" >
<TESTCASE name="Yahoo" className=\
"org.jmx4odp.junitDiagnosticWorkers\
BasicHttpUrlTest" >
<INVOKE method="setUrl" >
<ARG type="java.lang.String"
value="http://www.yahoo.com"
/>
</INVOKE>
</TESTSUITE>
SuiteAssembler will parse the XML
and generate a TestSuite called "Web
Servers" that contains a single
BasicHttpUrlTest test bean called
"Yahoo". It then hands the TestSuite
over to JUnit, which will run the
BasicHttpUrlTest.test_UrlOk method to
see if a connection to www.yahoo.com
returns an HTTP 200:OK. If Yahoo is
unreachable or returns a different status,
JUnit will display a failure.
You can use JUnit's junit.swingui
.TestRunner to run JUnit tests in a
graphical interface. Pass TestRunner
the name of your test class as an argument.
The JMX4ODP SuiteAssembler
will load a TestSuite.xml file in the
working directory. If you use the example
TestSuite.xml and type:
> java junit.swingui.TestRunner
org.jmx4odp.junitDiagnosticWorkers.SuiteAssembler
in the same directory, JUnit will parse
the file, try to connect to www.yahoo.com, and display the results.
At this point you could simply build
a TestSuite.xml file to check all the services
you want and just use JUnit to run
on-demand diagnostics of all your systems.
If the line is green, the servers are
clean. This would certainly be handy at
3 a.m. when you get the "the site is acting
weird" phone call.
Already you can quickly and repeatedly
run a set of known tests on your
site and identify problems. However,
this is a reactive instead of proactive
solution. We now need to automate
these tests and feed the results to a system
that can use them.
Using Tests to Manage with JMX
Before JMX, there was no Java standard
way for starting, stopping, monitoring,
and managing components. If
you're not familiar with JMX, there are
some great books, such as JMX:
Managing J2EE with Java Management
Extensions by Marc Fleury, Juha
Lindfors, and The JBoss Group.
JMX is a powerful and convenient
way of building loosely coupled systems.
The JMX agent is a bean container
for specialized management beans
called MBeans. The agent allows you to
instantiate new MBeans, register existing
MBeans, bind MBeans together,
and send and receive notifications.
Many J2EE engines and management
packages have adopted JMX as a
core feature, because it's flexible and
extensible. J2EE programmers are
familiar with component-based programming,
and JMX capitalizes on that
to create component-based management
systems that are scalable. JSR 160
(www.jcp.org/en/jsr/detail?id=160) is
extending the core JMX specification to
include remoting functionality, which is
used by JMX4ODP to connect clients
and other agents to each other via RMI.
EventRunner
org.jmx4odp.junitRunner.Event
Runner is an MBean that implements a
JUnit TestRunner. It serves as the bridge
between your JUnit diagnostic setup and
JMX management. It will fetch a Test-
Suitexml from a given URL, use the
SuiteAssembler to construct a TestSuite,
hand it to a JUnit TestRunner to run all the
tests, and broadcast any failures or errors
as notifications to any registered listeners.
The EventRunner will then sleep for the
specified time and do it all over again.
You can think of JMX4ODP as a reflex
system for your J2EE system. JUnit test
objects act as live nerves gathering information
about the state of objects and services
in your system. The JMX agent is like a
spinal cord, transmitting these impulses
between the brain and muscles. The Event-
Runner MBean, the system's brain, coordinates
all the JUnit tests and keeps them
running regularly. Now you just need to
add some muscle to complete the system.
Muscle takes the form of JMX
NotificationListener MBeans. Any
MBean that implements the javax.
management.NotificationListener
interface can register itself with the
EventRunner to receive failure and
error notifications. The Notification-
Listener will receive a javax. management.
Notification object that contains
attributes including timestamp, type,
and message. The type of message can
be set via the two EventRunner methods:
setErrorTopic(String s) and
setFailureTopic(String s). The event
runner will broadcast the message as
an error type if an error occurred while
trying to run the test. It will use the failure
topic if the test was unsuccessful.
The event notification message will be
formatted like TestFailure.getName() +
": " + failure.toString(); if failure.to
String() returns a null, it will use failure.
thrownException().toString().
A NotificationListener can register
with the EventRunner and be activated
upon these events. It can use the
Notification.getMessage() to learn
which test failed and how. We'll use the
org.jmx4odp.notificationWorkers.Notifi
cationMailer as a simple starting place.
This MBean uses the javax.mail.* package
to e-mail JMX notifications. By registering
the Notification Mailer with the
EventRunner, you have a failure notification
system that will alert your sysadmin
when a problem occurs.
All this needs to be set up programmatically
which is a maintenance issue.
JMX's MLet object can be used to make
a JMX agent load MBeans specified in
an XML file, but it doesn't include the
ability to invoke functions on instantiated
MBeans. To overcome this, JMX-
4ODP uses its org.jmx4odp. j4oNet.Xml-
Executor object to load and access
MBeans in an agent.
Figure 3 shows the XML entities used
by the XmlExecutor. Listing 1 shows the
beginning of such a file.
Figure 3
Everything is a child of the
EXECUTEXML element. The JMXREMOTE
element tells XmlExecutor to
connect to the JMX agent's RMIAdaptor.
TRY groups elements together, so if one
fails, it will skip the rest in the group so
you don't have to wait for each element
to fail. Use CREATEMBEAN to tell the
JMX agent to instantiate a new MBean.
COMMAND is the big advantage of
XmlExecutor; it allows you to invoke
methods on existing MBeans.
BaseAgent
JMX4ODP ships with org.jmx4odp.
j4oNet.BaseServer as its JMX agent. It
takes the HTMLAdaptor port and the
RMIAdaptor port as its two arguments.
Type
> java org.jmx4odp.j4oNet.BaseServer 80801099
to start the BaseServer. Now point a
Web browser to localhost:8080 to see an
HTML interface of your MBeans.
JMXREMOTE will use port 1099.
Now that you have a JMX agent with
an RMIAdaptor running, you can create
an XML Execute.xml file that will tell
XmlExecutor to:
1. Create an EventRunnerMBean.
2. Invoke EventRunner.setFailureTopic
to set the failure Notification type.
3. Invoke EventRunner.setErrorTopic
to set the error Notification type.
4. Invoke EventRunner.SetSuiteAssemblerConfig to give the URL for
your TestSuite.xml.
5. Invoke EventRunner.setSleepCount
to set how many milliseconds to
sleep between test cycles.
6. Create a NotificationMailer MBean.
7. Invoke NotificationMailer.setSmtpHost to set the host name of your
mail gateway.
8. Invoke NotificationMailer.setSmtpUser and setSmtpPassword to set the
username and password for your SMTP user if needed.
9. Invoke NotificationMailer.setSmtpPort if your gateway uses anything
other than port 25.
10. Invoke NotificationMailer.setFrom
Address to the address you want the
notification e-mails to come from.
11. Invoke NotificationMailer.setSubject
to set the e-mail subject line.
12. Invoke NotificationMailer.addTo
Address to add an address to which
to send notification e-mails.
13. Invoke NotificationMailer.setActive
to activate the mailer; otherwise it
will ignore all notifications while
inactive.
14. Invoke NotificationMailer.add
ListenedToObject to ."MONITOR:
name=EventRunner,NotificationLogger=true," which will tell
Notification Mailer to listen to the Notification
Logger created by the EventRunner.
15. Invoke EventRunner.startDaemon
to start the testing cycle.
Figure 4 illustrates how JMX4ODP
combines the JUnit tests and JMX management
components to create a management
system. If you use the example
TestSuite.xml, you're testing if you can
reach Yahoo. If this test fails, a JUnit
event will be broadcast to all registered
listeners. Your only listener right now is
an MBean that will send out the event
as an e-mail. If you start up a Web
browser to http://localhost:8080, you'll
see your JMX agent's HTMLAdaptor and
all the new MBeans you started.
Figure 4
We've Only Just Begun...
The most obvious ways to expand
JMX4ODP is to: