|
Welcome to the Enterprise Java Technologies Tech Tips,
for July 28, 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:
Creating a Message-Driven Bean
Iterating Nodes in DOM Trees
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, and co-author of Designing Enterprise Applications with the J2EE Platform, 2nd Edition. Mark Johnson runs an open forum for discussion of
the tips at http://groups.yahoo.com/group/techtipsarchive/.
You can download the sample code. The context root for the application is ttjul2003. 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.
CREATING A MESSAGE-DRIVEN BEAN
The J2EE platform offers several types of enterprise components.
Entity beans represent business data and functionality, and
session beans represent business processes. A Message-Driven Bean,
or MDB, implements business process logic in response to receipt
of an asynchronous message through the Java Messaging Service
(JMS). This month's first tip explains how to create a simple
message-driven bean.
An MDB provides server-side business functionality in response to
an asynchronous message. The communication path from a client to
an MDB is as follows:
- The client sends a message to a JMS destination, which is associated with an EJB container.
- When a message arrives in a queue associated with an MDB, the container passes the message to the
onMessage method of an idle MDB instance.
- The MDB instance then performs the requested service using the message as its input.
Note that as of EJB 2.1, MDBs can also receive messages from
Connectors.
The client can be any JMS message sender, not just an application
client or servlet. The message destination can be either a JMS
Queue or JMS Topic. JMS ensures delivery of the message to the
destination, and automatically handles acknowledgment, retries,
and transactions.
MDBs have several traits in common with stateless session beans:
- Both are enterprise beans components that implement business processes within an EJB container.
- Both are "stateless", meaning that they contain no conversational, client-specific state across invocations.
- Both can contain state that is not specific to a particular client, such as an EJB object or home interface reference, or a database connection.
- Multiple instances of both MDBs and stateless session beans can be managed by the container.
- The EJB container serializes service requests to the component, so the component code does not have to be reentrant.
- Both kinds of beans can use container services such as life cycle management, security, concurrency, and transaction handling.
However there are some important differences between MDBs and stateless session beans:
- Service requests to a stateless session bean are synchronous (by calling methods on a component interface). Service requests to an MDB are asynchronous (by sending a JMS message to a message destination).
- A stateless session bean can return data directly to a caller as the return value of a component interface method invocation. While the JMS destination of an MDB can acknowledge receipt of a message, the component can't return data to the sender of the message. If an MDB wants to send information back to the sender, it must send another JMS message as a reply.
- Only valid EJB clients (such as J2EE application clients, and Web components such as servlets and JSP pages) can access stateless session beans. By contrast, any JMS sender can invoke an MDB by sending a message to that bean's JMS destination.
- A session bean has a client security context that controls which clients can create and use the bean. MDBs have no client security context. Access to an MDB is managed by controlling access to the MDB's JMS destination.
MDB REQUIREMENTS
Developing an MDB is as easy as developing any other JMS message
listener. You need to create only one class, the message-driven
bean class (MDB class). The MDB class must meet the following
requirements:
- It must be public, and must not be abstract or final. Also, it must not implement a finalize method. It can implement other methods as desired.
- It must have a public, zero-argument constructor. Keep in mind that J2EE services (such as access to JNDI) are not available in the constructor. MDB instances should be initialized in the
ejbCreate method.
- It must have a single public void method,
ejbCreate, which takes no arguments. ejbCreate is called after an MDB instance is created, and also after setMessageDrivenContext is called (see below). The purpose of ejbCreate is to allow the bean to initialize its internal state. For example, a database connection used by an MDB might be created in ejbCreate.
- It must implement interface
javax.ejb.MessageDrivenBean. The interface has two methods:
setMessageDrivenContext: The EJB container calls
setMessageDrivenContext when it creates a new MDB instance,
passing an argument of type javax.ejb.MessageDrivenContext.
The MessageDrivenContext interface is analogous to the
EntityContext and SessionContext interfaces of entity and
session beans. The MDB usually retains a copy of the object,
and uses it later to request services from the container.
Because MDBs do not have home interfaces, the
MessageDrivenContext methods getEJBHome and getEJBLocalHome
are meaningless and should not be called.
ejbRemove: The EJB container calls ejbRemove before removing
an MDB instance. This method allows the bean to clean up any
resources it might hold, such as database connections. The
container may automatically remove MDB instances as a part of
its resource management mechanism.
- It must implement
javax.jms.MessageListener. This interface has a single method:
onMessage: An MDB's onMessage method implements application-specific business functionality. This is the method that receives Message objects from a JMS destination.
The application-specific functionality you want to implement in
the MDB goes in the onMessage method. The other methods provide
support for the bean's life cycle events. After you write the MDB
class, the next step is to deploy the MDB.
MDB DEPLOYMENT
Because an MDB is an enterprise bean, it is deployed in a JAR
file. The deployment descriptor in the JAR file
(META_INF/ejb-jar.xml) contains a <message-driven> tag for each
MDB used in the application. The contents of this tag configure
the MDB to its container, controlling such aspects as transaction
behavior, acknowledgment mode, and external component references.
A deployer uses application deployment tools to associate an MDB
class with a JMS Queue or Topic.
The following figure shows the J2EE SDK's deploytool being used
to configure LoaderMDB bean (the MDB in the sample code) with
a queue called jms/queue/LoaderQueue.
SAMPLE CODE
This month's sample code demonstrates a simple EJB loader that
loads data into entity beans. In this program, a standalone
program called TestQueue sends the contents of an XML file to
a named JMS message destination. The EJB container routes the
message to an MDB named LoaderMDB. The MDB's onMessage method
parses the XML and creates corresponding PersonBean objects, if
it can.
Keep in mind that this is a sample program. A "real world" entity
bean loader would bulkload entity bean data into a database using
backend tools, instead of creating beans through a home interface.
The sample code for LoaderMDB contains all of the required
methods for an MDB:
- The class implements
MessageDrivenBean and MessageListener:
public class LoaderMDB
implements MessageDrivenBean, MessageListener {
- A public, zero-argument constructor is called when a new instance is created:
public LoaderMDB() {
}
- The methods
ejbCreate and ejbRemove initialize and remove the internal state of the LoaderMDB:
private Logger _logger;
private PersonLocalHome _plh;
public void ejbCreate() {
_logger = Util.createLogger(
this.getClass().getName());
_plh = null;
}
public void ejbRemove()
throws EJBException {
}
- The method
setMessageDrivenContext saves the MessageDrivenContext for later use:
private MessageDrivenContext _mdc;
public void setMessageDrivenContext(
MessageDrivenContext mdc)
throws EJBException {
_mdc = mdc;
}
-
The
onMessage method creates entity beans corresponding to the
data in the XML document. Method onMessage receives a
TextMessage containing XML input to the MDB. The code uses
instanceof to ensure that only TextMessages are processed. The
code downcasts the message to a TextMessage and gets its text.
The text is then parsed into a DOM Document. If the document's
top node is "LOAD", it passes the entire document tree to the
loadDocument method. The details of how the loadDocument
method extracts data from the DOM tree are the topic of the next
tip (see "Iterating Nodes in DOM Trees").
public void onMessage(Message message) {
// Process only TextMessages
if (!(message instanceof TextMessage))
return;
try {
String msg = ((TextMessage)message).getText();
_logger.info(
"MDB received message: " + msg + "---");
Document doc = parseMessage(msg);
Element root = doc.getDocumentElement();
if (
root.getTagName().equalsIgnoreCase("LOAD")) {
loadDocument(root);
}
} catch (JMSException jex) {
_logger.warning(jex.toString());
throw new EJBException(jex);
}
}
The MDB class is deployed in an ejb-jar file with a suitable
deployment descriptor called META-INF/ejb-jar.xml. Here is an
excerpt from the descriptor, containing the declaration of the
MDB LoaderMDB:
<ejb-jar>
<display-name<LoaderJAR</display-name>
<enterprise-beans>
<!-- Process receiving a PO from -->
<!-- the Java Pet Store Start-->
<message-driven>
<description>
Message-driven bean that loads data
into enterprise beans
</description>
<display-name>LoaderMDB</display-name>
<ejb-name>LoaderMDB</ejb-name>
<ejb-class>
com.elucify.tips.jul2003.LoaderMDB
</ejb-class>
<transaction-type>Container</transaction-type>
<message-driven-destination>
<destination-type>
javax.jms.Queue
</destination-type>
</message-driven-destination>
<!-- EJB local refs -->
<ejb-local-ref>
<ejb-ref-name>ejb/local/Person</ejb-ref-name>
<ejb-ref-type>Entity</ejb-ref-type>
<local-home>
com.elucify.tips.jul2003.PersonLocalHome
</local-home>
<local>
com.elucify.tips.jul2003.PersonLocal
</local>
<ejb-link>
jul2003-ejb.jar#PersonBean
</ejb-link>
</ejb-local-ref>
</message-driven>
The <message-driven> tag declares an MDB. It has a description,
display-name, ejb-name, and other elements, just like any other
enterprise bean. Unlike other enterprise bean deployment
descriptors, it doesn't define home or component interfaces.
A component developer can use the <message-driven-destination>
tag to tell the application deployer what kind of JMS destination
to use. In this case, the LoaderMDB uses a javax.jms.Queue.
See the section "Running the Sample Code" for instructions on
how to run the sample program.
For more information about MDBs, see the section "What Is a Message-Driven Bean?" and "A Message-Driven Bean Example" in the J2EE Tutorial.
ITERATING NodeS IN DOM TREES
The February 11, 2003 Tech Tip titled "Creating Parsers with
JAXP" showed how to build a XML DOM parser using the Java API for XML
Processing (JAXP). The following tip explains the structure of a DOM tree, and provides some hints for effectively using DOM interface methods.
A DOM tree is a tree of document nodes. It represents the
lexical structure of an XML document as a tree of objects.
A parser that reads an XML file and produces a tree of these DOM
objects is called a DOM parser. In Enterprise Java, DOM parsers
implement interface javax.xml.parsers.DocumentBuilder. A program
acquires a reference to a DocumentBuilder using class
javax.xml.parsers.DocumentBuilderFactory.
The following figure shows an XML document that is parsed by an
XML parser (of type DocumentBuilder) into a tree of nodes. The
root node is of type org.w3c.dom.Document.
Each element in a DOM tree implements one of the interfaces in
package org.w3c.dom. This package was defined by the World Wide
Web Consortium, or W3C.
The following figure shows an inheritance diagram of the
interfaces that can be found in a DOM document tree.
Every object in a DOM document is a subtype of interface Node.
The top-level item in a document tree is always either a Document
or a DocumentFragment (which can represent a part of a Document).
Note that the Document and DocumentFragment interfaces are
subinterfaces of Node, even though they always appear at the top
of a Document. Below the top-level Node is an Element (which is
also a Node) called the document element. (A Document can have
other children, such as entities, entity references, and processing
instructions.) The Document element contains the top-level tag that
encloses the entire document.
In the DOM tree illustrated above, the document element is the one
labeled "A".
The methods used to iterate a tree of Nodes always return
references of type Node, but these objects have a "real" type, as
well. To work effectively with DOM trees, you have to understand
what structures may appear in a DOM document. You also need to
know how to cast the Node reference down to the object's real
type, so you can use its API.
The following examples from the sample code demonstrate how to do
several common read-only operations on a DOM tree.
GETTING THE DOCUMENT ELEMENT
Often, the first thing you want to do with a Document is get its
document element, so that you can traverse the document tree.
In the sample code, the onMessage method in the EJBLoader
receives an XML document from a JMS client and converts it to a
String. The method then parses the String and gets the document
element, like this:
Document doc = parseMessage(msg);
Element root = doc.getDocumentElement();
if (root.getTagName().equalsIgnoreCase("LOAD")) {
loadDocument(root);
}
Method getDocumentElement returns an Element, so there's no
need to typecast it. In the next line, method getTagName returns
the name of the tag. If the tag name is "LOAD", the code passes
the document element to loadDocument.
ITERATING CHILD NodeS
Every object in a DOM tree implements interface Node, and every
object has zero or more child nodes. The child nodes of an
Element correspond to the Elements it contains, plus other Node
subtypes, such as EntityReferences and CharacterData subtypes.
When you iterate over the children of an Element, be sure that
you only subcast Node to Element when the Node really is an
Element. If you subcast a Text Node to Element, for example, you
get a ClassCastException. Method loadDocument below iterates over
the child nodes of document element of the DOM tree. Each time it
encounters an Element with tag name PERSON, it calls loadPerson
on that Node. Other Nodes are ignored.
protected void loadDocument(Element doc) {
NodeList kids = doc.getChildNodes();
Node kid;
_logger.info("Loading document");
for (int i = 0; i < kids.getLength(); i++) {
kid = kids.item(i);
if (kid instanceof Element &&
((Element)kid).getTagName().equalsIgnoreCase(
"PERSON")) {
loadPerson((Element)kid);
}
}
_logger.info("Done loading document");
}
Node method getChildNodes returns a NodeList. NodeList is an
interface with two methods. One method of NodeList is getItem(i),
which returns the "ith" item in the list. List items are numbered
from 0 to n-1, where n is the length of the list. The other
NodeList method is getLength, which returns the list length.
The for loop in the code above iterates over all of the child
nodes of the document element. The instanceof keyword checks if
the Node can be safely typecast to Element. If the element tag
name is "PERSON", a Person bean is loaded from the data in the
Element. DOM API methods in the Java platform often require
typecasting from Node to a subinterface of Node, as this example
demonstrates. In such cases, it's best to use instanceof to check
type compatibility before a typecast.
An equivalent way to iterate over the child Nodes of a Node
appears in the entity bean method Person.load, shown below:
public void load(Element person)
throws CreateException {
Node node = person.getFirstChild();
while (node != null) {
if (node instanceof Element) {
Element e = (Element)node;
String tagname = e.getTagName();
if (tagname.equalsIgnoreCase("ADDRESS")) {
loadAddress(e);
} else {
// Get text contents of node
String nodevalue = null;
nodevalue = Util.getElementTextContents(e);
set(tagname, nodevalue);
}
}
node = node.getNextSibling();
}
}
Method getFirstChild returns the first child Node of a given
Node. Method getNextSibling returns the next "sibling" Node of
a given Node. Another pair of methods, getLastChild and
getPreviousSibling, can be used to traverse child Nodes in reverse order.
GETTING ELEMENTS BY TAG NAME
A somewhat easier way to code the loadDocument example above is
to use method Element.getElementsByTagName, which returns a
NodeList of all of the subelements of an Element that have a
given tag name. Because each Node returned by this method is
guaranteed to be an Element, there's no need to check the type
with instanceof. Method loadDocument could be also be written as
follows:
protected void loadDocument(Element doc) {
NodeList kids = doc.getElementsByTagName("PERSON");
Element kid;
_logger.info("Loading document");
for (int i = 0; i < kids.getLength(); i++) {
kid = (Element)kids.item(i);
loadPerson((Element)kid);
}
_logger.info("Done loading document");
}
The typecast is still necessary because NodeList.item returns
Node, not Element. The problem with this approach is that
getElementsByTagName returns all subelements with the given name,
at any depth in the tree. If the input XML were constrained by
a DTD to have PERSON elements only immediately within a LOAD
element, this problem would be overcome.
For more information about the Document Object Model see the
"Document Object Model" chapter in the Java Web Services Tutorial.
RUNNING THE SAMPLE CODE
Download the sample archive for these tips. The application's
context root is ttjul2003. The downloaded EAR file also contains the
complete source code for the sample.
You can deploy the application archive (ttjul003.ear) in the J2EE Reference Implementation using the deploytool program:
$J2EE_HOME/deploytool -deploy ttjul2003.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/ttjul2003.
It is highly recommended that you use the J2EE Reference
Implementation to run this example. To run the sample program,
follow these steps:
- Be sure that both the j2ee server and database server are running. For the J2EE Reference Implementation, execute both:
j2ee -verbose
and
cloudscape -start
- Download the ear file as described above.
- Deploy the ear file to your server using deploytool.
- Extract the test data file from the archive:
jar xvf ttjul2003.ear data.xml
- Create a JMS
Queue called "jms/queue/LoaderQueue":
j2eeadmin -addJmsDestination jms/queue/LoaderQueue queue
- Run the "
TestQueue" sample program as follows (all on
one line):
java -classpath "ttjul2003.ear:$CLASSPATH" TestQueue
send jms/queue/LoaderQueue -f data.xml
You might need to adjust the syntax for your command
interpreter.
- Check the output log of your server to see the messages produced by the MDB.
The sample code also includes a Web application that allows you
to send JMS messages to the MDB, print a list of all Persons in
the database, and find an individual Person in the database. To
use the form, deploy the application and then visit
http://localhost:8000/ttjul2003.
For a J2EE-compliant implementation other than the Reference
Implementation, use your J2EE product's deployment tools to
deploy the application on your platform. You might need to
perform additional tasks, such as generating SQL for CMP, and
defining database names.
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
Comments? Send your feedback on the Enterprise Java Technologies
Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=sdn
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,
choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".
ARCHIVES: You'll find the Enterprise Java Technologies Tech Tips archives at:
http://developer.java.sun.com/developer/EJTechTips/
Copyright 2003 Sun Microsystems, Inc. All rights reserved. 4150 Network Circle, Santa Clara, CA 95054 USA.
This document is protected by copyright.
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.
|