Enterprise Java Technologies Tech Tips Tips, Techniques, and Sample Code Welcome to the Enterprise Java(tm) Technologies Tech Tips for March 11, 2003. Here you'll get tips on using enterprise Java technologies and APIs, such as those in Java 2 Platform, Enterprise Edition (J2EE(tm)). This issue covers: * Changing the JAXP Parser Classes * Using JMS Queues 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/tt0311.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 code for the Using JMS Queues tips at http://java.sun.com/jdc/EJTechTips/download/ttmar2003.jar. Any use of this code and/or information below is subject to the license terms at http://developer.java.sun.com/berkeley_license.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CHANGING THE JAXP PARSER CLASSES The February 11, 2003 issue of the Enterprise Java Technologies Tech Tips (http://java.sun.com/jdc/EJTechTips/2003/tt0211.html) contained a tip that explained how to use the Java API for XML Processing (JAXP). The tip mentioned that JAXP is vendor-neutral. It also showed how to use JAXP's built-in set of interfaces to create and configure XML parsers in J2EE applications. This tip extends that discussion by explaining what vendor-neutral means in JAXP, and how to change the parsers that JAXP configures and constructs. If you look closely at the JAXP interfaces in standard extension package javax.xml.parsers, you'll notice that the classes are all marked "abstract". This is because the parsers and parser factories are implemented by classes outside of the package. JAXP's vendor independence comes from this separation. The abstract classes DocumentBuilderFactory and SAXParserFactory each implement one method: newInstance. This method creates a new instance of the factory class. The package is vendor-neutral because the newInstance method looks in several places for the name of a class that fully implements the abstract factory interface. JAXP comes pre-configured to use a default set of classes for parsers and parser factories. Which class is used depends on the application server vendor. To change the parser used by an application, you need to tell JAXP the fully-qualified class name of the parser factory (SAXParserFactory or DocumentBuilderFactory) to use. There are three ways to do this: o Set a system property. The system property javax.xml.parsers.SAXParserFactory can be set to the fully-qualified class name of your alternative SAX parser factory. The system property javax.xml.parsers.DocumentBuilderFactory can be set to the class name of the alternative DOM parser factory. System properties are set when the application server starts. See your application server's documentation for details. For the J2EE Reference Implementation, you can simply set the system property on the command line using the -D option. o Specify classes in the file $JAVA_HOME/lib/jaxp.properties. You can set the same two properties (javax.xml.parsers.SAXParserFactory and javax.xml.parsers.DocumentBuilderFactory) using standard properties notation in a file called jaxp.properties in the lib directory of your J2EE platform main directory. For example, here are two lines the the files that identify classes for alternative SAX and DOM parsers: javax.xml.parsers.SAXParserFactory=com.myco.parsers.MySAXFactory javax.xml.parsers.DocumentBuilderFactory=com.myco.parsers.MyDOMFactory o Specify the classes as an entry in the services directory of a JAR file. The JAR file specification defines a directory called META-INF/services that JAXP checks to find definitions of parser factories. JAXP uses the fully-qualified class names found in the files META-INF/services/javax.xml.parsers.SAXParserFactory and META-INF/services/javax.xml.parsers.DocumentBuilderFactory as the parser factory classes, if those files exist. Why not just specify the application in a deployment descriptor? The most obvious reason is that deployment descriptors are XML files. It would be difficult to get the XML parser name without first parsing the XML file! The second reason is that not all J2EE technologies use deployment descriptors. For example, the JMS sample program in the next tip, "Using JMS Queues", uses J2EE technology, but no deployment information is required. The techniques described above for changing the parser used by an application, can be used in any application deployment scenario. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING JMS QUEUES The Java Messaging Service (JMS) (http://java.sun.com/products/jms/) is a vendor-neutral set of APIs for reliable messaging between programs. In client-server computing, a client program contacts a server and requests a service. By contrast, messaging applications send messages between cooperating programs. Some programs (in so-called "peer-to-peer applications") exchange messages directly with one another (JXTA uses this model -- see http://www.jxta.org). JMS provides a middleware message broker that provides reliable, transactional message delivery between programs. A JMS provider is a program that implements the JMS service contracts in terms of the JMS public interfaces. The J2EE platform specification requires that platform implementations include a JMS provider. Most people are familiar with the client-server model of messaging, where a client program contacts a server and requests a service, data, or both. The client then receives a reply, usually synchronously. By contrast, JMS provides a richer messaging model with the following high-level features: o Reliable message delivery. A message receiver does not have to be running when the sender sends the message. The next time the receiver is available, the message is delivered. o Point-to-point or publish/subscribe messaging models. Message transmission may be one-to-one or one-to-many. o Transactions. Message delivery can be made part of a distributed transaction. o Synchronous or asynchronous delivery. A message producer may or may not wait for an acknowledgement from the receiver. o Object-oriented messaging. Rather than sending structured data using protocols, JMS allows objects to be sent between clients. o Legacy integration. JMS is designed to integrate with underlying third-party messaging systems. The tip explains how to use JMS message queues to implement simple, reliable point-to-point messaging between two processes. Publish/subscribe messaging will be covered in a later edition of the Enterprise Java Technologies Tech Tips. JMS QUEUE TERMINOLOGY JMS uses the concept of a message queue to implement point-to-point messaging. In point-to-point messaging, there is always exactly one message producer and one message consumer. Unless the sender defines a timeout for the message, there is no time dependence in point-to-point messaging. A message consumer can receive a message sent at any time in the past by a message producer, even if the consumer wasn't running when the message was queued. The JMS provider is a messaging server that handles message persistence, timeout, redelivery, transaction rollbacks, and other services provided by JMS. In the case of the J2EE SDK, the JMS provider is part of the j2ee server program. A message producer sends objects to a queue that is maintained by the JMS server. A message consumer receives messages from the queue and acknowledges their receipt. The JMS specification defines several types of objects used to send messages between processes: o JMS administered objects. There are are two kinds of JMS administered objects: destinations and connection factories. Both are created by a system administrator using environment management tools. o Destination. This is a server-side object through which messages may be sent and received. A JMS Queue is one type of destination. o Connection factory. This is a server-side object that configures and creates a connection to a specific destination. A JMS connection factory for a JMS Queue is of type QueueConnectionFactory. o Connection. This is a virtual connection to a JMS provider (not to a destination). It is used to create sessions. A connection for accessing queues is of type QueueConnection. o Session. This is a potentially transactional work unit of message transmission and receipt. A session is used to create messages, message producers, and message consumers. A session used to access queues is of type QueueSession. o Message. This is an object that can be sent through a message destination to a message consumer. Message types vary by the sort of object being sent. For example, the sample code provided for this tip uses TextMessage objects. o Message producer. This is an object used by a JMS client program to send a message to a destination. The program acquires the message producer object from a session. A program can send Messages to a Queue using a message producer of type QueueSender. o Message consumer. This is an object used by a JMS client program to receive messages from a destination. The program acquires the message receiver object from a session. A program can receive Messages from a Queue using a message consumer of type QueueReceiver. That's quite a few new terms. Now let's look at how to send a message. The steps below show examples taken from the sample code provided with this tip. (See the section "Running the Sample Code" for instructions on how to download and run the sample code.) However, before you run the sample code, you need to configure the server (see the Configuring the Server section). SENDING MESSAGES The J2EE Reference Implementation comes pre-configured with a queue connection factory (called QueueConnectionFactory) and a queue (called jms/Queue). If you are using a JMS server other than the Reference Implementation, or if you want to experiment with changing the names of the connection queue factory and/or the queue, see the section "Configuring the Server". Here are the steps in sending a Message through a JMS queue. Code snippets from the sample program, TestQueue, illustrate the steps. 1. Get a reference to a QueueConnectionFactory by looking it up by name with JNDI: protected static String qfactoryName = "jms/queue/TechTipsQueueConnectionFactory"; ... try { // Get JNDI context InitialContext ctx = new InitialContext(); // Get connection factory QueueConnectionFactory qcf = (QueueConnectionFactory)ctx.lookup(qfactoryName); 2. Get a QueueConnection from the QueueConnectionFactory, and get a QueueSession from the connection. Look up the queue by name in JNDI: // Get a connection to the queue qc = qcf.createQueueConnection(); // Get a session from the connection QueueSession qs = qc.createQueueSession( false, Session.AUTO_ACKNOWLEDGE); // Get a queue Queue q = (Queue)ctx.lookup(queueName); 3. Use the QueueSession to create a QueueSender, passing the Queue as an argument. (The QueueSender is an association between a QueueSession and a particular queue.): // Use the session to create a QueueSender // and a TextMessage. QueueSender qsnd = qs.createSender(q); The QueueSender can now be used to send messages to the Queue. 4. Create Message objects (subclasses of type Message), and use the QueueSender's send method to send them to the destination. The sample program acquires a TextMessage object from the Session. It then packages each of the program arguments into the TextMessage, and sends it to the queue using the QueueSender. Notice that the same TextMessage can be used multiple times. TextMessage tm = qs.createTextMessage(); // For each argument (after the first), // send the argument string as a text message. for (int i = 2; i < args.length; i++) { tm.setText(args[i]); qsnd.send(tm); } 5. Close the QueueConnection. It's good practice to close the connection in the finally clause of a try/finally block. This step is important: forgetting to close QueueConnections can cause resource leaks on the server: } finally { if (qc != null) { qc.close(); } } To send a message to a message queue, use the TestQueue program (in the default package) with the argument "send", for example: $ java TestQueue send jms/queue/MyTestQueue a b c d Java(TM) Message Service 1.0.2 Reference Implementation (build b14) Sent: 'a' Sent: 'b' Sent: 'c' Sent: 'd' RECEIVING MESSAGES The TestQueue program follows these steps for receiving a message: 1. and 2. The first two steps of receiving a message from a queue are identical to those for sending: look up the connection factory, get a QueueConnection and QueueSession, and look up the Queue. 3. Get a QueueReceiver from the QueueSession: QueueReceiver qrcv = qs.createReceiver(q); 4. Receive the message from the Queue. The sample program enters a loop where it receives messages from the queue and prints them to standard output. qc.start(); Message m = qrcv.receive(10000); while (m != null) { if (m instanceof TextMessage) { TextMessage tm = (TextMessage)m; System.out.println("Received text: '" + tm.getText() + "'"); } else { System.out.println("Received a " + m.getClass().getName()); } m = qrcv.receive(100); } finally { if (qc != null) { qc.close(); } } The call to the QueueConnection's start method tells the connection to begin receiving messages. The QueueReceiver receive method takes an argument of the number of milliseconds to wait for a message to arrive. If no message is delivered within the timeout value, the method returns null. The program reads and prints received messages until the message queue remains empty for 100 milliseconds. A finally clause at the end of the try/finally block closes the QueueConnection as in the previous example. To receive the messages in the message queue, use the TestQueue program with the argument "recv", for example: $ java TestQueue recv jms/queue/MyTestQueue Java(TM) Message Service 1.0.2 Reference Implementation (build b14) Received text: 'a' Received text: 'b' Received text: 'c' Received text: 'd' CONFIGURING THE SERVER If you are not using the Reference Implementation, or if you want to change the queue and/or queue connection factory names, you need to configure the JMS provider using the platform's administration tools. After a queue or connection factory is created, it remains in the server installation, and survives server restarts. Some platform implementations might provide extension APIs for clients to create destinations and connection factories programmatically. The tool for creating administered objects in the J2EE SDK is j2eeadmin. To configure the server: 1. Start the application server. 2. Create a message queue, giving it a JNDI name. (Do this only once.) To create a new message queue in the J2EE SDK, first decide on a name for your message queue. It can be convenient for administration purposes to choose a name that indicates that the queue is used by JMS, for example, jms/MyTestQueue. Then execute the command: j2eeadmin -addJmsDestination queue Replace with the actual name of the message queue. 3. The connection factory name QueueConnectionFactory is hard-wired into the sample program, so to tell the program to use a different connection factory name, you have to change the source code and recompile. For example, in TestQueue.java: // Change this variable's value to change the // connection factory name protected static String qfactoryName = "QueueConnectionFactory"; After you recompile, use j2eeadmin (or your server's tool) to create a connection factory, giving it a JNDI name, like this: j2eeadmin -addJmsFactory jms/MyQueueConnectionFactory To list connection factories, use: j2eeadmin -listJmsFactory and to list destinations, use: j2eeadmin -listJmsDestination For a platforms other than the J2EE SDK, use your J2EE product's deployment tools to create the required message queue and connection factory on your platform. RUNNING THE SAMPLE CODE Download the sample archive for these tips (from http://java.sun.com/jdc/EJTechTips/download/ttmar2003.jar). The JAR file contains the complete source code for the sample program, both as a Java program file and formatted as HTML. You can use the command jar xvf ttmar2003.jar to extract the contents of the sample jar into your working directory. Be sure the J2EE server is running before running the sample program. Run the sample program several times. Try sending several batches of messages without receiving, and examine the order of the output. The sample program provides a starting place for you to experiment with JMS queues. Explore the APIs of the JMS interfaces used. For example, change the program to use the three different forms of receive. Explore transactional message delivery, or try sending Message objects of various types. JMS has many features, and using it skillfully can improve your application designs. For further information about JMS, see the Java Message Service Tutorial (http://java.sun.com/products/jms/tutorial/). . . . . . . . . . . . . . . . . . . . . . . . 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(tm)). - Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME(tm)). 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 March 11, 2003 Sun, Sun Microsystems, Java, Java Developer Connection, J2SE, J2EE, and J2ME are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.