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.