Enterprise Java Technologies Tech Tips
Tips, Techniques, and Sample Code
Welcome to the Enterprise Java Technologies Tech Tips for
May 27, 2004. Here you'll get tips on using enterprise Java
technologies and APIs, such as those in Java 2 Platform,
Enterprise Edition (J2EE).
This issue covers:
* Converting Your Data Structures to XML
* The Enterprise Bean Timer Interface
These tips were developed using the Java 2, Enterprise Edition,
v 1.4 SDK. You can download the SDK at
http://java.sun.com/j2ee/1.4/download-dr.html.
This issue of the Tech Tips is written by Mark Johnson, president
of elucify technical communications (http://www.elucify.com/),
and co-author of Designing Enterprise Applications with the J2EE
Platform, 2nd Edition (http://java.sun.com/blueprints/guidelines/
designing_enterprise_applications_2e/). Mark Johnson runs an open
forum for discussion of the tips at
http://groups.yahoo.com/group/techtipsarchive/.
You can view this issue of the Tech Tips on the Web at
http://java.sun.com/developer/EJTechTips/2004/tt0527.html.
See the Subscribe/Unsubscribe note at the end of this newsletter
to subscribe to Tech Tips that focus on technologies and products
in other Java platforms.
You can download the sample archive for these tips at
http://java.sun.com/developer/EJTechTips/download/ttmay2004.ear.
The context root for the application is ttmay2004, and the
index.html welcome file indicates how to use the sample code. Any
use of this code and/or information below is subject to the
license terms at
http://developers.sun.com/dispatcher.jsp?uid=6910008.
For more Java technology content, visit these sites:
java.sun.com - The latest Java platform releases, tutorials, and
newsletters.
java.net - A web forum for collaborating and building solutions
together.
java.com - The marketplace for Java technology, applications and
services.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CONVERTING YOUR DATA STRUCTURES TO XML
The Java platform offers powerful APIs for parsing and generating
XML data streams. You might already know how to use JAXP to parse
an XML stream into a Document Object Model (DOM) tree in memory.
If you don't, see the Tech Tip "Creating Parsers with JAXP"
(http://java.sun.com/developer/EJTechTips/2003/tt0211.html#1).
You might also know how use interface
java.xml.transform.Transformer to turn a DOM tree into an
XML stream. But did you know that with just a few lines of code,
you can write any data into XML?
The following tip shows how to use the standard XML manipulation
facilities in the Java platform to create XML from arbitrary data
structures. The tip assumes you understand the basics of JAXP and
XSL transformations.
This tip is an example of a programming technique for turning any
data structure into XML. The example shown here converts an
SQL ResultSet object into XML, but you could use the same
technique with any data structure.
Println Considered Harmful
The standard Java APIs are fine for parsing and generating XML if
data is already represented as XML (in a file or stream), or
as a DOM (in memory). But developers often need to encode state
from arbitrary data structures into XML. These data structures
usually don't have a DOM interface. So developers usually
hand-code routines that serialize the data structures (or parts of
the structures) to XML using println statements.
That approach is problematic for several reasons. Some of the
reasons are:
o Changing the XML requires editing many println statements.
o Restructuring output XML is difficult and error prone.
o Well-formed XML can be hard to produce if logic is complex.
o You might need data in some form other than an XML text stream.
A better approach is to encode data from the data structures into
a DOM tree, and then use XSLT to rearrange the data in the way
you want. A Transformer object based on an XSLT stylesheet can
add, sort, prune, merge, and otherwise manipulate the nodes in
a DOM without first encoding the DOM as XML. After transformation,
the Transformer can convert the result of these transformations
to any text format.
This approach solves the problems listed above. Specifically:
o You can create or modify XML document types by changing
stylesheets instead of editing code.
o Creating output formats with stylesheets centralizes
XML structuring outside of the Java code, making maintenance
easier.
o The Transformer automatically ensures that output is
well-formed.
o The Transformer can create any (and many) forms of output, not
just XML.
The question is how to create a DOM tree from an arbitrary data
structure? The sample code that accompanies this tip shows how to
use the standard Java XML APIs to transform an SQL ResultSet into
a DOM tree, and then transform the result to XML or HTML using a
stylesheet. You can use the same technique to encode your own
data structures as DOM trees.
How to Fool A Parser
The sample code includes a servlet, May2004Servlet. The servlet
has a method called getXML that executes an arbitrary SQL query,
converts the result to a DOM tree, and writes the result as XML.
The servlet also has a method named resultSetToXML, that
receives an SQL ResultSet and transforms it to XML.
To understand how resultSetToXML works, you must understand how
DOM parsers work. Most DOM parsers use a SAX parser to scan XML
source streams. A SAX parser scans the XML input text. It sends
events to a listener when it encounters specific lexical features,
such as the beginning and ending of tags, or the presence of
attributes.
A DOM parser defines a callback method that builds a DOM tree in
response to a series of SAX parser callbacks. The sample code
"fools" the DOM parser by behaving as if it were scanning text,
when, in fact, it is really traversing a data structure.
This concept can be a bit difficult to understand at first.
Usually, developers define callback methods and framework calls
in response to framework events. But the sample code in this
example does the opposite. Instead of writing callback functions,
the code calls the callback methods provided by the DOM parser.
The DOM parser builds the tree.
For example, imagine a parse method that simply makes the
following calls:
startDocument();
startElement("A");
startElement("B");
endElement("B");
endElement("A");
endDocument();
With this series of events, the DOM parser creates a DOM tree
that corresponds to the following XML document:
So, to tell a DOM parser to build a DOM tree for your data, just
write a class that pretends to be a SAX parser as it traverses
your data structure. Then, present the "fake parser" class to a
DocumentBuilder. The DocumentBuilder will build a DOM for you.
The DOM can then be serialized to XML, or further transformed for
other uses.
Let's look at how to set up the Transformer to transform an
arbitrary data structure to XML.
Source Code
Here's the code in method resultSetToXML that converts a
ResultSet to XML:
protected void resultSetToXML(OutputStream out,
ResultSet rs,
String stylesheet)
throws IOException, ServletException {
// Create reader and source objects
SqlXMLReader sxreader = new SqlXMLReader();
SqlInputSource sis = new SqlInputSource(rs);
The code defines two new classes, SqlXMLReader and SqlInputSource.
The SqlXMLReader class is the XML reader for the SAX parser
(because it implements XMLReader). The SqlInputSource class simply
holds a copy of the ResultSet object for the XML reader to use.
You'll soon see how the SqlXMLReader works.
The next two lines of resultSetToXML set up the parser to create
the DOM tree as output:
// Create SAX source and StreamResult for transform
SAXSource source = new SAXSource(sxreader, sis);
StreamResult result = new StreamResult(out);
These lines create a SAXSource object that associates the XML
reader (SqlXMLReader) with the InputSource (SqlInputSource). The
code also creates a StreamResult that the XSLT Transformer uses
to create the final result as an XML stream.
At this point in the code, no actual data transformation has
occurred. The next section of the reader sets up the Transformer
object to read from the XML source. It then transforms the
results into the result stream:
// Perform XSLT transform to get results.
// If "stylesheet" is NULL, then use identity
// transform. Otherwise, parse stylesheet and
// build transformer for it.
try {
// Create XSLT transformer
TransformerFactory tf =
TransformerFactory.newInstance();
Transformer t;
if (stylesheet == null) {
t = tf.newTransformer();
} else {
// Read XSL stylesheet from app archive
// and wrap it as a StreamSource. Then use
// it to construct a transformer.
InputStream xslstream =
_config.getServletContext().
getResourceAsStream(stylesheet);
StreamSource xslsource =
new StreamSource(xslstream);
t = tf.newTransformer(xslsource);
}
// Do transform
t.transform(source, result);
}
This code first gets a new TransformerFactory from the factory
interface newInstance method. The method interprets a non-null
stylesheet name as the path in the application archive (WAR file)
to an XSLT stylesheet. It gets the stylesheet contents as an
InputStream, and then creates a Transformer based on the
stylesheet contents. If the stylesheet is null, it creates a
default identity transformer. An identity transformer simply
converts an input DOM tree to a result (in this case, an XML
stream) without transforming the DOM in any way.
The final line is where everything happens:
// Do transform
t.transform(source, result);
This line tells the transformer to read from a SAXSource (the one
created previously, which produces events from the ResultSet),
transforms the input, and writes the result to the StreamResult
object.
So where are the DocumentBuilder and the DOM tree? The
Transformer handles building the DOM tree itself. It uses the
SAXSource object that was supplied to create a DOM tree, performs
the transform (if any), and serializes the result DOM tree to the
StreamResult.
Now that you understand the broad outlines of how the parser and
transformer work together, let's have a look at how to tell the
parser to create its SAX events from a ResultSet.
Reading From a ResultSet
The standard interface for a SAX parser (for SAX2, the second
version of the interface) is called XMLReader. Despite its name,
XMLReader does not extend java.io.Reader. Instead, it defines
methods that a DOM parser uses to receive callbacks from its data
source.
As mentioned previously, the class that creates events from a
ResultSet is called SqlXMLReader. Most of SqlXMLReader's methods
are empty because those methods relate to files and other things
that are not of interest in this example. Method parse actually
does most of the work.
The first section of the parse method gets the ResultSet to be
traversed:
// Get result set from SqlInputSource.
SqlInputSource sis = (SqlInputSource)is;
ResultSet rs = sis.getResultSet();
If the DOM parser hasn't provided a ContentHandler to receive the
methods, it's an error:
if (_handler == null) {
throw new SAXException("No XML ContentHandler");
}
The code uses the result set's columns as the tag names in the
result XML:
// Get information about result set
ResultSetMetaData md = rs.getMetaData();
int nColumns = md.getColumnCount();
int iRow = 0;
The code performs a couple of callbacks to tell the parser that
a document is beginning, and also begins the document root (the
top element of the document, with tagname "results"):
// Send startDocument and startElement events
// to handler
_handler.startDocument();
_handler.startElement(uri, docroot, docroot, attrs);
The code then iterates over the result set, outputting tag names,
text and ignorable white space (for readability). For each row,
it creates an element called "row", and for each column within
that row, it creates an element with the column name as the tag
name:
while (rs.next()) {
// Output "row" tag
_handler.startElement(uri, "row", "row", attrs);
outputIgnorableWhitespace("\n");
String s;
for (int i = 1; i <= nColumns; i++) {
String tag = md.getColumnName(i);
_handler.startElement(uri, tag, tag, attrs);
s = rs.getString(i);
if (s == null) {
s = "";
}
outputString(s);
_handler.endElement(uri, tag, tag);
outputIgnorableWhitespace("\n");
}
_handler.endElement(uri, "row", "row");
outputIgnorableWhitespace("\n");
}
Finally, the code tells the parser to close the document root and
end the document:
_handler.endElement(uri, docroot, docroot);
outputIgnorableWhitespace("\n");
_handler.endDocument();
Accompanying the sample code is a sample database of geographic
information. You can use the main page in the sample code to send
a query to the interface.
The servlet uses resultSetToXML to create the following XML
document:
231
If the result doesn't look like XML, select View Source in your
browser to see the actual XML text.
Although this example specifically shows how to create an XML
document from a ResultSet, you can use the same technique to
convert your own data structures to XML.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
THE ENTERPRISE BEAN TIMER INTERFACE
The Enterprise JavaBeans (EJB) 2.1 specification defines a new
Timer feature for entity beans, stateless session beans, and
message-driven beans. (Timers are not available for stateful
session beans.) Timers allow application designers to
incorporate time-based behaviors and workflows into their
applications in a portable way.
A Timer is an EJB container-managed object that configures
a callback to the creating bean when they expire. Timers can be
configured to expire after a specific interval has elapsed, or to
expire on a particular date and time. They can also be configured
to expire only once, or perform callbacks periodically until they
are turned off. Each enterprise bean can create as many Timers as
the implementing platform allows. Timers created by entity beans
are specific to the identity of that bean. Timers created by
message-driven and stateless session beans are shared between
instances, because those types of beans have no client-accessible
identity.
Here are a few good reasons to use the Timer interface:
o EJB Timers are persistent. EJB timers work correctly even if
your server goes down and comes back up.
o EJB Timers are transactional. They benefit from the
container-managed transaction features of the EJB server. With
the Java Timer service, you would have to manage transactions
yourself.
o EJB Timers are standardized. As long as your server complies
with EBJ 2.1 or higher, you can be assured that the Timer
interface is there. Operating-system-based timing solutions,
such as UNIX cron, may or may not be available on your
application's target platform.
o When a timer times out, its ejbTimeout method has access to all
of the EJB container services.
This tip explains the Enterprise Bean Timer interface. The
source code is a LogCleaner session bean that periodically cleans
out a log table in a relational database.
Timer Interface
An enterprise bean defines and uses the Timer interface by
declaring that it implements interface TimedObject. This
interface has a single callback method that is called when the
timer expires:
void ejbTimeout(Timer timer)
The enterprise bean class must define this method. The method
does not appear in the bean's component interface. Instead, it is
called by the EJB container when the timer expires.
An enterprise bean can create multiple timers, so the timer that
expired is passed to the bean as the argument to the ejbTimeout
callback. If the bean uses multiple timers, it is responsible for
distinguishing between them when the callback occurs.
The servlet in the sample code (May2004Servlet) logs all of the
requests it receives in a database table called SERVLETLOG. The
sample code also includes an enterprise bean called TimerBean.
The enterprise bean can be configured to periodically remove the
contents of this log. The bean class for the bean shows how to
create and respond to timer events from the EJB container.
Timer Bean Local Interface
The TimerBean source code is simple. Its component interface
is local, and contains the following methods:
public interface TimerLocal extends
EJBLocalObject {
public void start(TimerData data);
public void stop(String timerName);
public boolean isRunning(String timerName);
}
The start method tells the timer to start cleaning the log every
"secs" seconds. The stop method stops the timer. The isRunning
method indicates whether or not the cleaner is running. The
sample servlet provides an interface to start, stop, and query
the timer. Of course, in a production system, such a log cleaner
would be started and stopped by application management tools,
likely using JMX Mbeans. This code uses a servlet for simplicity
and clarity.
The TimerBean can manage several timers at once, each with a
different name. The first argument to TimerBean.start is a
TimerData object. You define this object using the TimerBean. It
contains the timer's name, the period of the timer (in
milliseconds), and a "data" Object for programmer use. Each time
a timer with a given name is started, stopped, or times out, the
TimerBean makes a callback to a corresponding method (onStart,
onStop, or onTimeout) on that timer's TimerData object.
You can define subclasses of TimerData to perform timer-specific
functionality. For example, the sample program defines a
LogCleanerTimerData object that cleans out the log (by making a
call back to the servlet in the Web tier) when the timer times
out.
Timer Bean Class
The class definition for the Timer Bean says that the bean is a
session bean, and that it uses timers:
public class TimerBean
implements SessionBean, TimedObject {
The bean's start method gets a reference to the TimerService,
using servlet context method getTimerService. TimerService is a
container facility that beans use to create and manipulate
timers. The start method creates a Timer that runs once every
"secs" seconds. Other Timer create methods of TimerService create
Timers that run only once.
public void start(TimerData data) {
long ms = data.getMs();
try {
TimerService ts = _context.getTimerService();
// Execute after "secs" seconds, and then
// every "secs" seconds thereafter.
Timer timer = ts.createTimer(ms, ms, data);
data.onStart(timer);
} catch (Exception e) {
System.err.println
("TimerBean.start: " + e.getMessage());
}
}
Notice that the third method of createTimer is the String
"log cleaner". The final argument of createTimer methods is user
data: a caller-defined object. This object can be a reference to
any object that is Serializable. The caller sets this object when
the Timer is created. The object can be retrieved anywhere else
in the code by calling the Timer's getInfo method. This user data
object can be used to distinguish between multiple timers, and
might also contain other data needed by the bean that owns the
Timer.
The TimerBean's stop method turns off all Timers associated with
the bean.
public void stop(String timerName) {
try {
TimerService ts = _context.getTimerService();
Collection timers = ts.getTimers();
Iterator it = timers.iterator();
// For every timer whose "info" is TimerData,
// if the timer's name is timerName, pass that
// timer to TimerData.onStop. The TimerData object
// must decide whether or not to call Timer.cancel.
while (it.hasNext()) {
Timer t = (Timer)it.next();
if (isCalled(t, timerName)) {
TimerData td = (TimerData)t.getInfo();
td.onStop(t);
}
}
} catch (Exception e) {
System.err.println("TimerBean.stop: " + e.getMessage());
}
}
The isRunning method returns true if this bean owns any running
Timer. The method returns false otherwise:
public boolean isRunning(String timerName) {
try {
TimerService ts = _context.getTimerService();
Collection timers = ts.getTimers();
Iterator it = timers.iterator();
if (it.hasNext()) {
Timer t = (Timer)it.next();
if (isCalled(t, timerName)) {
return true;
}
}
} catch (Exception e) {
System.err.println("TimerBean.isRunning: " +
e.getMessage());
}
return false;
}
Finally, the bean's ejbTimeout method receives callbacks from the
container each time the Timer expires.
// When the EJB times out, call back
// to the Web tier to execute the SQL.
public void ejbTimeout(Timer timer) {
HttpURLConnection conn = null;
// Throw away everything in the servlet log
// each time the timer ticks. Use a URLConnection
// to ask the Web tier to do that delete for us.
try {
InitialContext ic = new InitialContext();
URL url = (URL)ic.lookup
("java:comp/env/url/cleanLog");
conn = (HttpURLConnection)url.openConnection();
int code = conn.getResponseCode();
conn.disconnect();
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
}
In the case of the log cleaner, method
LogCleanerTimerData.onTimeout is called by TimerBean.ejbTimeout.
The method calls back to the servlet and requests that the
servlet clean the log on its behalf. This code could also have
connected to the database and cleaned the log itself. The code for
onTimeout appears below:
public void onTimeout(Timer t)
throws EJBException {
System.out.print("Timer " + getName() +
"timed out. ");
System.out.println("Cleaning log by calling " +
getLogCleanURL());
try {
URL url = getLogCleanURL();
HttpURLConnection conn =
(HttpURLConnection)url.openConnection();
int code = conn.getResponseCode();
System.err.println("Cleaned log, code=" + code);
conn.disconnect();
} catch (Exception ex) {
throw new EJBException(ex.getMessage());
}
}
To experiment, open your browser to the main page for the sample
code. Under Tip 2 are the instructions for opening a separate
window that displays the servlet log. The servlet log refreshes
every 10 seconds.
A form and some links on the sample code main page allows you to
start and stop the timer.
You can use the form and the links to stop, start, and query the
timer status.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
RUNNING THE SAMPLE CODE
Note: You must start the PointBase database server before you
start your J2EE server. To start the PointBase database server:
o In the Solaris Operating System:
1. Change directory to $J2EE_HOME/pointbase/tools/serveroption.
2. Run the script "./startserver.sh" to start the server.
o On Windows, from the Start menu, choose Programs, then
Sun Microsystems, then J2EE 1.4 SDK, then Start PointBase.
Download the sample archive for these tips (from
http://java.sun.com/jdc/EJTechTips/download/ttmay2004.ear).
The application's context root is ttmay2004. The downloaded
ear file also contains the complete source code for the sample.
You can deploy the application archive (ttmay2004.ear) on
the J2EE 1.4 Application Server using the deploytool program or
the admin console. You can also deploy it by issuing the asadmin
command as follows:
asadmin deploy install_dir/ttmay2004.ear
Replace install_dir with the directory in which you installed the
ear file.
You can access the application at http://localhost:8080/ttmay2004.
For a J2EE 1.4-compatible implementation other than the J2EE 1.4
Application Server, use your J2EE product's deployment tools to
deploy the application on your platform.
See the index.html welcome file for instructions on running the
application.
. . . . . . . . . . . . . . . . . . . . . . .
Please read our Terms of Use and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://developers.sun.com/dispatcher.jsp?uid=6910008
PRIVACY STATEMENT:
Sun respects your online time and privacy (http://sun.com/privacy).
You have received this based on your e-mail preferences. If you
would prefer not to receive this information, please follow the
steps at the bottom of this message to unsubscribe.
* FEEDBACK
Comments? Send your feedback on the Enterprise Java Technologies
Tech Tips to:
http://developers.sun.com/contact/feedback.jsp?category=sdn
* SUBSCRIBE/UNSUBSCRIBE
Subscribe to other Java developer Tech Tips:
- Core Java Technologies Tech Tips. Get tips on using core
Java technologies and APIs, such as those in the Java 2
Platform, Standard Edition (J2SE).
- Wireless Developer Tech Tips. Get tips on using wireless
Java technologies and APIs, such as those in the Java 2
Platform, Micro Edition (J2ME).
To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page,
(https://softwarereg.sun.com/registration/developer/en_US/subscriptions),
choose the newsletters you want to subscribe to and click
"Update".
- To unsubscribe, go to the subscriptions page,
(https://softwarereg.sun.com/registration/developer/en_US/subscriptions),
uncheck the appropriate checkbox, and click "Update".
- To use our one-click unsubscribe facility, see the link at
the end of this email:
- ARCHIVES
You'll find the Enterprise Java Technologies Tech Tips archives at:
http://java.sun.com/developer/EJTechTips/index.html
- COPYRIGHT
Copyright 2004 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/developer/copyright.html
Enterprise Java Technologies Tech Tips
May 27, 2004
Trademark Information: http://www.sun.com/suntrademarks/
Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks
or registered trademarks of Sun Microsystems, Inc. in the
United States and other countries.