Enterprise Java Technologies Tech Tips Tips, Techniques, and Sample Code Welcome to the Enterprise Java Technologies Tech Tips for April 15, 2003. 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: * Publish/Subscribe Messaging With JMS Topics * Using the Document Object Model These tips were developed using Java 2 SDK, Standard Edition, v 1.4 and Java 2 SDK, Enterprise Edition, v 1.3.1 (Reference Implementation). 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 Java 2, Enterprise Edition, 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/jdc/EJTechTips/2003/tt0415.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/jdc/EJTechTips/download/ttapr2003.ear. The context root for the application is ttapr2003. 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://developer.java.sun.com/berkeley_license.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PUBLISH/SUBSCRIBE MESSAGING WITH JMS TOPICS The tip "Using JMS Queues" in the March 11, 2003 issue (http://java.sun.com/jdc/EJTechTips/2003/tt0311.html) explained how to use Java Messaging Service (JMS) Queues for point-to-point messaging. The tip that follows explains how to implement publish/subscribe messaging using JMS Topics. Publish/Subscribe Messaging In publish/subscribe messaging, a single publisher sends each message to multiple subscribers with a single method call. Between the publisher and subscriber is a messaging server. In JMS, the messaging server is called a "JMS provider". The publisher sends messages to the JMS provider. Subscribers receive messages from the JMS provider. In JMS, publish/subscribe messaging uses a JMS-managed object called a Topic to manage message flow from publishers to subscribers. JMS publishers are called message producers, and JMS subscribers are called message consumers. A message producer acquires a reference to a JMS Topic on a server, and sends messages to that Topic. When a message arrives, the JMS provider is responsible for notifying all message consumers subscribed to that Topic. The JMS provider (optionally) receives acknowledgment of the message receipt each time it sends the message. Publish/subscribe messaging using JMS Topics is similar to point-to-point messaging in several ways. The following properties apply to both kinds of messaging: o Messaging can be object-oriented, allowing entire objects to be sent as messages. o Messaging can be transactional. o Messaging can be synchronous or asynchronous. o Messaging can be integrated with underlying third-party products. o A message may be sent to a message consumer (that is, a QueueReceiver or TopicSubscriber) that is not running at the time the message is sent. o A function call that sends a message returns as soon as the message is delivered to the queue or topic. o Message receipt can be acknowledged explicitly or automatically. There are also several differences between point-to-point and publish/subscribe messaging: o Publish/subscribe messaging is one-to-many, while point-to-point messaging is one-to-one. o A published message is delivered only to current subscribers of a Topic. A client receives only those messages published after the client subscribes to a Topic. By contrast, in point-to-point messaging, a persistent message sits in a Queue until either it times out, or until some receiver comes along to retrieve the message. o Persistent messages in publish/subscribe messaging are provided by "durable subscriptions". The JMS provider stores messages that can't be delivered to a subscriber because the subscriber is unavailable for some reason. The stored messages are delivered the next time the subscriber connects. This ensures delivery of all messages published after the client subscribes to a Topic -- even if the subscriber isn't always running. If a subscription isn't durable, any messages published while the subscriber is down are never delivered to the subscriber. Neither publish/subscribe messaging nor point-to-point messaging is superior to the other. Rather, they are complementary tools, used for different purposes. Point-to-point messaging is often used where the message receiver has a unique identity within a system. Publish/subscribe messaging is more often used when several agents in a system need to know when an event or condition occurs. The JMS messaging model is very similar to event listeners in conventional Java 2 programming. Point-to-point messaging is like a unicast event listener model. Publish-subscribe messaging is like a multicast listener model. The difference between traditional Java event listeners and JMS (other than programming syntax) is that the event sources and listeners are called message producers and consumers, respectively. JMS message producers and consumers can run in different address spaces, or even on different machines. JMS messaging also provides a much higher level of service than does the traditional event listener model. Nevertheless, the basic messaging model is the same. The sample code for this tip, in http://java.sun.com/jdc/EJTechTips/download/ttapr2003.ear, comprises three programs: 1. A servlet, PublishWeatherServlet, that publishes an XML-formatted weather report to a JMS Topic 2. A command-line Java application, WeatherReceiver, that subscribes to the Topic and prints the XML messages it receives. 3. A GUI application client, WeatherClient, that parses and graphically displays the data in the XML message. Publishing a Message To A Topic The PublishWeatherServlet servlet receives POST parameters from an HTML form, formats them as XML, and uses a JMS Topic to publish the resulting XML document to all listeners. Much of the code in this servlet is dedicated to receving the POST parameters and formatting them as an XML document. The interesting part of the code is in the publish method. This method takes a single String argument containing the XML text to be published. Let's examine the publish method in detail, and see how it publishes to a JMS Topic: 1. Get a TopicConnectionFactory and a Topic. protected void publish(String text) { TopicConnectionFactory tcf = null; Topic topic = null; try { Context jndiContext = new InitialContext(); tcf = (TopicConnectionFactory) jndiContext.lookup( "java:comp/env/jms/TopicConnectionFactory"); topic = (Topic) jndiContext.lookup( "java:comp/env/jms/Topic"); } catch (NamingException nameEx) { System.err.println(nameEx.toString()); } This code uses the Java Naming and Directory Interface (JNDI) API to look up two objects on the JMS provider: a Topic and a TopicConnectionFactory. The servlet will send the message to the Topic. The TopicConnectionFactory will be used to create a connection to the JMS provider. Note the names the servlet uses to look up these objects. Remember that the JNDI API names of all objects in a J2EE application should begin with "java:comp/env/". 2. Create a Connection, Session, and Publisher. TopicConnection tc = null; try { tc = tcf.createTopicConnection(); TopicSession ts = tc.createTopicSession( false, Session.AUTO_ACKNOWLEDGE); TopicPublisher tp = ts.createPublisher(topic); This code uses the TopicConnectionFactory acquired from the JNDI API to create a TopicConnection. The TopicConnection can then be used to create a TopicSession. The arguments to createTopicSession tell the connection to create a TopicSession that is not transactional, and that automatically acknowledges message receipt. (If message delivery were transactional, all messages sent in the same TopicSession would form a unit of work that could be committed or rolled back.) The Session is then used to create a TopicPublisher, which acts as the channel through which messages can be published. Note that the J2EE 2.0 Specification indicates that the transactionality and acknowledgement of JMS messaging is managed by the J2EE container. This means that these arguments are ignored if the code is running within a container. Unfortunately, not all vendors comply with this requirement. If transactions, acknowledgement, or both are important for your application, be sure to check your product documentation. Alternatively, test the behavior of these arguments yourself. These arguments should work as expected outside of a container. 3. Create and publish the message. TextMessage textMessage = ts.createTextMessage(); textMessage.setText(text); tp.publish(textMessage); ... } // End of method publish Publishing the message is simple. The TopicSession acts as a factory to create a new TextMessage. The code sets the text of the TextMessage to the string containing the XML to send. It then uses the TopicPublisher to publish the message to the Topic. That's all there is to it. The JMS provider is responsible for delivering the message to all subscribers. Subscribing to a Topic and Receiving Messages The command-line program, WeatherReceiver, subscribes to a Topic and prints any messages it receives from the Topic. To simplify matters, the process of subscribing to a Topic is encapsulated in the auxiliary class SubscriptionHelper. The WeatherReceiver class acts as an asynchronous message receiver, and actually performs the printing. 1. Subscribing to a Topic The following code, from class SubscriptionHelper, creates a subscription to a Topic: protected TopicConnection _tc; ... public SubscriptionHelper(String tcfName, String topicName, MessageListener listener) { // Get references to topic connection factory // and topic. _tc = null; TopicConnectionFactory tcf = null; Topic topic = null; try { InitialContext ic = new InitialContext(); tcf = (TopicConnectionFactory) ic.lookup(tcfName); topic = (Topic) ic.lookup(topicName); } catch (NamingException e) { System.err.println(e.toString()); e.printStackTrace(System.err); } try { // Create a connection and so on // Subscribe self to topic--messages will be // delivered to this.onMessage() _tc = tcf.createTopicConnection(); TopicSession ts = _tc.createTopicSession( false, Session.AUTO_ACKNOWLEDGE); TopicSubscriber tsub = ts.createSubscriber(topic); tsub.setMessageListener(listener); } catch (JMSException e) { System.err.println(e.toString()); e.printStackTrace(System.err); close(); } } Most of the SubscriptionHelper class is identical to the publisher code. It uses the JNDI API to acquire references to a Topic and TopicConnectionFactory, and creates TopicConnection and TopicSession objects. But instead of creating a TopicPublisher, this class creates a TopicSubscriber, and sets the TopicSubscriber's message listener to the MessageListener that was passed in. After this point, whenever that Topic receives a message, the message is delivered to the onMessage method of the MessageListener. Because a callback in used in this way, this example demonstrates asynchronous message reception. 2. Receiving Messages The only requirement to receive messages is that the class implement interface javax.jms.MessageListener. The WeatherReceiver class is itself a MessageListener. The MessageListener interface has only method: onMessage. The WeatherReceiver's onMessage method appears below: public class WeatherReceiver implements MessageListener { // Print a weather message when it is received public void onMessage(Message message) { try { if (message instanceof TextMessage) { TextMessage m = (TextMessage) message; System.out.println( "--- Received weather report"); System.out.println(m.getText()); System.out.println("----------"); } else { System.out.println( "Received message of type " + message.getClass().getName()); } } catch (JMSException e) { System.err.println(e.toString()); e.printStackTrace(System.err); } } ... public static void main(String[] args) { if (args.length != 2) { System.out.println( "Usage: WeatherReceiver " + "topicConnectionFactorName topicName"); System.exit(1); } // Create a receiver, then set it up to listen // for messages on the topic. Then wait for // messages and print them as they come in. WeatherReceiver wr = new WeatherReceiver(); SubscriptionHelper sh = new SubscriptionHelper(args[0], args[1], wr); // Wait for publications... System.out.println( "Waiting for publications to topic " + args[1]); sh.waitForMessages(); } The WeatherReceiver's main method creates a WeatherReceiver instance and a SubscriptionHelper instance. It passes the SubscriptionHelper the WeatherReceiver and the names of the Topic and TopicConnectionFactory that the application should use (these are specified on the command line). The SubscriptionHelper instance creates the subscription. It then registers the WeatherReceiver as the message consumer for messages from the Topic. The onMessage method simply casts received Messages to class TextMessage, where appropriate, and prints the received XML document. Note that using JMS listeners in the Web tier is a bad idea. In fact, it's disallowed by the J2EE 1.3 Reference Implementation. Server-side JMS listeners are appropriately modeled as Message Driven Beans in the EJB tier. Deploying the Web Application Publish/subscribe messaging code is easy with JMS. However, the deployment descriptor presents an issue that requires resolution. PublishWeatherServlet is a Web component that looks up external components using the JNDI API. Web components look up external resources (such as Topics and TopicConnectionFactories) using a coded name. The deployment descriptor must define these coded names as resource references or resource environment references. The following excerpt from the Web application's deployment descriptor web.xml defines the coded names used by the servlet. (This code appears after in web.xml.) jms/Topic javax.jms.Topic jms/TopicConnectionFactory javax.jms.TopicConnectionFactory Container The resource-env-ref block defines the name "jms/Topic" as being of type javax.jms.Topic. The string "jms/Topic" is the string used to look up the Topic ("java:comp/env/jms/Topic"), with the "java:comp/env/" part deleted. The deployment tools for a product allow the application deployer to map this name to a Topic in the environment. In the case of the J2EE Reference Implementation, this mapping is already configured in the Web archive in the file META-INF/sun-j2ee-ri.xml. This file is the Web application's runtime deployment descriptor. The runtime deployment descriptor is vendor-specific both in name and in content. The resource-ref block defines the name, type, and authorization mode for the TopicConnectionFactory. Usually, a deployer would use deployment tools to associate the coded name jms/TopicConnectionFactory with a TopicConnectionFactory in the platform. The J2EE Reference Implementation comes preconfigured with a TopicConnectionFactory called jms/TopicConnectionFactory in the JNDI namespace. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING THE DOCUMENT OBJECT MODEL The WeatherReceiver described in the tip "Publish/Subscribe Messaging With JMS Topics" above receives XML messages from a publisher, and prints those messages to the screen. But one of the major reasons to format data as XML is to be able to separate the pieces of data in the file and use them in various places. The tip "Creating Parsers with JAXP" in the February 11, 2003 issue (http://java.sun.com/jdc/EJTechTips/2003/tt0211.html) explained how to use JAXP to create a DocumentBuilder class that can parse an XML document, returning an object of type org.w3c.dom.Document. The tip that follows explains how to use the Document Object Model (DOM) APIs to get information from a parsed XML Document. DOM Concepts XML markup can be represented as a tree of nodes. Each node can represent a feature in the XML such as an element, an attribute, a comment, or a piece of text. A tree of these objects forms a model of the XML document. The Document Object Model is a set of APIs that provide programmatic access to a virtual tree of nodes that represent XML document features. A JAXP DocumentBuilder parses a character stream formatted in XML notation, and returns an object that implements interface org.w3c.dom.Document. The Document object represents the entire XML document as one object, but the objects within the XML document are accessible through the Document methods. The virtual tree represented by the Document object is often called a DOM tree. The DOM has quite a number of interfaces, all of which reside in package org.w3c.dom (as defined by the World Wide Web Consortium, W3C). A few of the interfaces are of special interest: o The Node interface is the highest-level interface in the DOM. Almost every interface in the DOM extends Node. A Node simply represents a distinct feature in the input XML. A Node has exactly one parent node, zero or more sibling nodes, and zero or more child nodes. Methods of interface Node, which its subinterfaces inherit, allow a programmer to access the parent, child, and siblings of any Node in a DOM tree. o The Document interface represents an entire XML document. It is usually created by an XML parser. It contains the top-level element, and can also contain nodes for the XML declaration, processing instructions, and DTD elements. o The Element interface represents a tag in an XML document. Its parent Node is the Node that contains it (the containing Node is the Document, in the case of the top-level tag). An Element's sibling nodes are all nodes that share that parent, and its child nodes are all tags that it contains. Elements are frequently accessed by their tag name. Attributes are modeled separately. o The CharacterData interface represents character data that is not in a tag. CharacterData has three subinterfaces: Text, which represents Text nodes; CDATASection, which represents CDATA; and Comment, which (optionally) represents comments. Parser settings determine whether or not comments appear in the parser output. Many DOM methods have return type NodeList. A NodeList does not represent a feature in an XML document, but rather represents a list of Nodes. Interface NodeList has two methods: getLength, which returns the number of items in the list; and item, which returns the nth item in the list (numbered from 0 to getLength-1). Methods that access attributes return a NamedNodeMap instead. You can explore the DOM interfaces for yourself in the documentation for package org.w3c.dom (http://java.sun.com/j2ee/sdk_1.3/techdocs/api/org/w3c/dom/package-tree.html), which is a part of the standard J2EE distribution. Getting Data from a DOM Tree The WeatherClient sample program that was introduced in the tip "Publish/Subscribe Messaging With JMS Topics" receives an XML document from a Topic in exactly the same way as the WeatherReceiver described in that tip. But instead of printing the XML, as WeatherReceiver does, WeatherClient parses the XML as a DOM tree. WeatherReceiver then uses pieces of data from the tree to create HTML, which it then displays in a Swing GUI. Receiving the XML and parsing it have already been covered in other tips, such as "Creating Parsers with JAXP" (http://java.sun.com/jdc/EJTechTips/2003/tt0211.html), so this section focuses on how the WeatherReceive program accesses the data in the DOM tree. The WeatherClient's onMessage method receives the XML and parses it with a DocumentBuilder, resulting in a DOM tree rooted in the Document variable doc. The following code from class WeatherClient gets the top-level element from the document. Then three methods pull different subsets of data out of that element, and return the data subsets formatted as HTML. Each of the three HTML display panels in the application gets different HTML. if (doc != null) { Element e = doc.getDocumentElement(); // Set the HTML for the first panel _panels[0].setText(getCurrentConditionsHtml(e)); _panels[1].setText(getForecastHtml(e)); _panels[2].setText(getDetailedForecastHtml(e)); pack(); } A sample of the beginning of the XML message received by the program appears below. C 15 23 14 SW ... The top-level Element returned by getDocumentElement() is the top-level tag, . Method getCurrentConditionsHtml pulls pieces of text out of the XML and formats them as HTML for display in the GUI. The method first creates a PrintWriter that writes to a String, so the resulting XML can be returned as a String. // Get "current conditions" from document protected String getCurrentConditionsHtml( Element top) { StringWriter sw = new StringWriter(4096); PrintWriter pw = new PrintWriter(sw); Element current, conditions, el; pw.println("
"); pw.println(""); current = getFirstMatchingElement( top, "current"); conditions = getFirstMatchingElement( current, "conditions"); The code then gets the first element matching the name "current". Then it gets the first element matching the name "conditions" under "current". At this point, the method has a handle to the object representing the tag in the XML excerpt above. The next section of code formats the display of the condition of the sky: el = getFirstMatchingElement(conditions, "sky"); String sky = getMergedTextChildren(el); sky = getSkyDescription(sky); pw.println( ""); Because the Element "conditions" contains only one Element called "sky", the Element can be retrieved by name, using method getFirstMatchingElement. Method getMergedTextChildren gets the contents of the element as a string. That string is then formatted as HTML. Method getFirstMatchingElement is defined in WeatherClient as follows: static protected Element getFirstMatchingElement( Node n, String nodeName) { NodeList nl; Element el = null; if (n instanceof Element) { el = (Element)n; } if ((el != null) && (nl = el.getElementsByTagName(nodeName)) != null && nl.getLength() != 0) { el = (Element)(nl.item(0)); return el; } return null; } The first line ensures that what was passed in was an element, and typecasts the Node to an Element. If the Element is non-null and contains (however far down the tree) nodes with the requested name, the first of these nodes is returned. Otherwise, the method returns null. Method getMergedTextChildren combines all non-comment CharacterData nodes (Text nodes and CDATASection nodes) into a single string. The text contained inside the sky element is composed of CharacterData nodes, not Elements. Method getMergedTextChildren combines the zero or more Text and CDATASection nodes under the node passed in to a single string. protected static String getMergedTextChildren( Element e) { NodeList nl = e.getChildNodes(); String result = ""; for (int i = 0; i < nl.getLength(); i++) { Node n = nl.item(i); if (n instanceof Text) { result = result + ((Text)n).getData(); } else if (n instanceof CDATASection) { result = result + ((CDATASection)n).getData(); } } return result; } These methods are used multiple times in the code to simplify working with the DOM. Making It Easier On Yourself You might have noticed that the DOM interfaces are somewhat clumsy to use. The reason DOM seems to require a great deal of coding to do something simple is because it was designed to interoperate with many different source languages. Not all of these languages have a syntax that is as succinct as Java. Fortunately, there are other solutions: o Create your own suite of auxiliary methods (the approach taken in the sample above) and handle such problems yourself. o Use stylesheets to transform the XML from one format to another, instead of manipulating them programmatically. o Consider a free, open-source interface such as JDOM, which simplifies XML coding. While the DOM was designed for multiple platforms and language bindings, JDOM is designed to make writing code that manipulates XML trees in memory as easy as possible in Java. While it is not part of the J2EE distribution, it is freely available, will work with J2EE installations, and is backward-compatible with the DOM interfaces. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - RUNNING THE SAMPLE CODE The sample code for the tips consists of two archives: an EAR file containing a Web application, and a JAR file containing two application clients. For convenience, the JAR file is packaged inside the EAR file. Visit the application context root (see below) and follow the instructions to download the application client JAR file from the Web application. The Web application publishes from the Web tier to both application clients in the Client Tier. Running both clients, or even multiple copies of both clients simultaneously, helps to demonstrate the one-to-many nature of publish/subscribe messaging. Publishing: Download the sample archive for these tips (from http://java.sun.com/jdc/EJTechTips/download/ttapr2003.ear). The application's context root is ttapr2003. The downloaded EAR file also contains the complete source code for the sample. You can deploy the application archive (ttapr2003.ear) in the J2EE Reference Implementation using the deploytool program: $J2EE_HOME/deploytool -deploy ttapr2003.ear localhost Replace localhost with the name of the host on which the server is installed. For a standard installation on a single machine, the hostname typically (and literally) is localhost. You can access the application at http://localhost:8000/ttapr2003. Visiting this URL in a Web browser will present you with an HTML form that you can use to publish weather reports. The servlet publishes to the JMS Topic called "jms/Topic", and uses the Topic Connection Factory called "jms/TopicConnectionFactory" (these are the names of the default topic and connection factory that come pre-configured with the Reference Implementation). Also, use deploytool to undeploy the application when you're finished with it. The -uninstall argument of deploytool takes the application name, not the EAR file name, as an argument: deploytool -uninstall Apr2003 localhost For a J2EE-compliant implementation other than the Reference Implementation, use your J2EE product's tools to deploy the application on your platform, and then undeploy it when you're finished with it. Receiving XML Text: The sample code also contains a JAR file, ttapr2003.jar, that receives messages from the topics listed above. To run the WeatherReceiver on the command line, be sure that ttapr2003.jar is in your classpath, and execute the following command: java com.elucify.tips.apr2003.WeatherReceiver Using the GUI: The sample JAR ttapr2003.jar also contains a Swing GUI. To use it, be sure that the jar file is in your path, and execute: java com.elucify.tips.apr2003.WeatherClient Either of the two receivers can be configured to use a different TopicConnectionFactory and Topic by providing their JNDI names on the command line. . . . . . . . . . . . . . . . . . . . . . . . IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies: http://www.sun.com/share/text/termsofuse.html http://www.sun.com/privacy/ http://developer.java.sun.com/berkeley_license.html * FEEDBACK Comments? Send your feedback on the Enterprise Java Technologies Tech Tips to: jdc-webmaster@sun.com * 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://developer.java.sun.com/developer/EJTechTips/index.html - COPYRIGHT Copyright 2003 Sun Microsystems, Inc. All rights reserved. 4150 Network Circle, Santa Clara, California 95054 USA. This document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html Enterprise Java Technologies Tech Tips April 15, 2003 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.
"); pw.println("

"); pw.println(sky); pw.println("