|
Welcome to the Enterprise Java Technologies Tech Tips for December 21, 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:
Object Serialization with the JAXB Libraries
Revisiting Timers with Enterprise Beans
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.html.
This issue of the Tech Tips is written by Robert Eckstein, a Java technology developer, editor, and author. Robert is owner of Nexes Consulting, a Java consulting firm based in Austin, Texas.
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. Any use of this code and/or information below is subject to the license terms.
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.
OBJECT SERIALIZATION WITH THE JAXB LIBRARIES
XML is now a ubiquitous standard for representing data, and there are a number of XML technologies that you can use to serialize Java objects to XML. The two primary technologies that Java developers are familiar with are the Simple API for XML (SAX) and the Document Object Model (DOM) APIs. However, often, programmers need technologies that are more targeted to the immediate task. In this case, programmers might find themselves wishing for simpler XML technologies for serializing object data.
Enter the Java API for XML Binding (JAXB). JAXB now comes standard with the Java Web Services Development Pack. JAXB allows you to perform a number of tasks, such as bind class data from XML schema, and serialize object data to and from XML.
This Tech Tip introduces the JAXB APIs. It shows you how to generate a set of Java classes from an XML Schema document using the JAXB binding compiler. This tip also shows you how to serialize an object to an XML file -- this is called marshalling -- and how to do the opposite: serialize an object from an XML file -- this is called unmarshalling.
You can run the examples as shown in this tip. However, before you do that, you need to download the Java Web Services Developer Pack. Then look in both the jaxb/lib and the jwsdp-shared/lib directory of the distribution, and ensure that each of the JAR files are explicitly included in your Java classpath.
Alternatively, you can run the web application provided in the WAR file that accompanies this tip. The source code shown in the tip is included in the WAR file, however the source code is slightly modified to use a servlet. To use the WAR file, you need to download the Sun Java System Application Server. Then download and install the Java Web Services Developer Pack into it. Each of the necessary libraries will then be present in the J2EE environment. An Ant file is included to assist with compilation and packaging.
The Binding Compiler
One of the first things to learn about JAXB is how to bind XML schema to Java classes. Binding takes an XML schema and generates a set of a Java interfaces that are capable of representing the data model defined by the schema. The tool to do this is called a binding compiler. The binding compiler accepts a W3C XML schema and derives the proper interfaces and classes from it.
Here is a simple schema that can be used to generate a JAXB class.
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2001/xml.xsd" />
<xs:element name="birthdate">
<xs:complexType>
<xs:sequence>
<xs:element name="month" type="xs:string" />
<xs:element name="day" type="xs:int" />
<xs:element name="year" type="xs:int" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
You can find the schema, Birthdate.xsd, in the source code that accompanies this tip. The schema defines a simple element named Birthdate. The Birthdate element has an anonymous complexType that defines three properties: month (a String), day (an integer), and year, (another integer).
To bind this to a Java interface, invoke the XML binding compiler. To do this you run the xjc command. You can find it in the jaxb/bin directory of the Java Web Services Developer Pack installation (you invoke the xjc.bat file in the Windows environment, or the xjc.sh file in the Solaris Operating System or other Unix environment). Use the following command-line syntax:
xjc -p <target_package> -d <dir> schema.xsd
where <target_package> is the name of the package for the generated files, <dir> is the directory in which the generated files will be placed, and schema.xsd is the input XML schema. So, for example, run the following command in the directory that contains Birthdate.xsd:
xjc -p com.nexes.jaxb.test -d . Birthdate.xsd
In response, the binding compiler generates a set of Java source classes, all of which need to be compiled with javac. The sample code that accompanies this tip includes the source classes. Here, for example, is the BirthdateType interface (comments have been removed):
package com.nexes.jaxb.test;
public interface BirthdateType {
int getDay();
void setDay(int value);
int getYear();
void setYear(int value);
java.lang.String getMonth();
void setMonth(java.lang.String value);
}
The schema compiler, however, generates more than this. You can divide the generated classes into three categories:
- A set of portable JavaBean-style getter/setter interfaces that represent the data model defined by the schema.
- A set of concrete vendor-specific implementations of those interfaces.
- Other supporting runtime classes.
Each of these classes assists with the serialized XML representation of instance data for a Birthdate object. Here, for example, is what a sample listing of an XML serialization of a Birthdate object looks like:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<birthdate>
<month>January</month>
<day>1</day>
<year>1900</year>
</birthdate>
Typically, an XML document serializes (or unmarshals) directly to a tree of classes known as a content tree. It's a tree because each element in the XML contains one or more elements that are themselves objects. In this example, the <birthdate> element is a class, as is the <month> element (a String). Because String is a Java built-in type, JAXB does not have to generate a class for it. For a more complex object, however, many classes can be generated. After the XML instance document has been turned into a class instance, a Java application can recursively move down the content tree to any object that has been created and access its value.
After the binding compiler generates the necessary classes, the next step is to compile the generated classes with javac. Then you can start working with JAXB serialization. As mentioned earlier, the process of translating a Java object to a serialized XML representation is called marshalling. The reverse process, that is, translating from a serialized XML representation to a Java object, is called unmarshalling. Let's first look at unmarshalling.
Unmarshalling
Here is a code example that unmarshals data from an XML file to a JAXB-generated instance:
import javax.xml.bind.*;
JAXBContext jc = JAXBContext.newInstance(
"com.nexes.jaxb.test");
Unmarshaller unmarshaller = jc.createUnmarshaller();
unmarshaller.setValidating(true);
Birthdate instance = (Birthdate)unmarshaller.unmarshal(
new File("birthdateInstance.xml"));
System.out.println("Month is: " + instance.getMonth());
System.out.println("Day is: " + instance.getDay());
System.out.println("Year is: " + instance.getYear());
The code creates a JAXBContext instance using the com.nexes.jaxb.test package:
JAXBContext jc = JAXBContext.newInstance(
"com.nexes.jaxb.test");
This package contains the interfaces and classes that were generated in the earlier example by the binding compiler from Birthdate.xsd schema. The context object is then used to create an Unmarshaller object:
Unmarshaller unmarshaller = jc.createUnmarshaller();
The Unmarshaller object performs most of the tasks associated with translating the XML to a Java content tree. The code calls the unmarshal() method of the Unmarshaller object, passing in the name of the XML file that contains the instance data. Note that you can use data sources and sinks other than files. The example that comes with this tip saves the result in a String and displays output to a servlet:
Birthdate instance = (Birthdate)unmarshaller.unmarshal(
new File("birthdateInstance.xml"));
The unmarshal() method returns an object that is an instance of the class that was generated by the Schema compiler.
Notice the setValidating() method for the unmarshaller object:
unmarshaller.setValidating(true);
If setValidating() is set to true, the JAXB runtime API validates the XML that contains the instance data against the constraints specified by the XML schema that was submitted to the binding compiler. If the data is invalid, the validation fails and generates a runtime exception. JAXB providers do not necessarily have to stop processing the data if a validation error is found. However, they do have to report the runtime exception to the invoker.
Marshalling
The opposite of unmarshalling is marshalling. Here is a code example that marshals a Java content tree to an xml file:
import javax.xml.bind.*;
ObjectFactory objFactory = new ObjectFactory();
Birthdate birthdate = objFactory.createBirthdate();
birthdate.setMonth("January");
birthdate.setDay(1);
birthdate.setYear(1900);
JAXBContext jaxbContext = JAXBContext.newInstance(
"com.nexes.jaxb.test");
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
new Boolean(true));
marshaller.marshal(birthdate, new FileOutputStream(
"birthdateInstance.xml"));
This code in this example is very similar to the source code for the unmarshalling example. First, the code creates a JAXBContext object, passing in the name of the package that has the binding compiler generated files:
JAXBContext jaxbContext = JAXBContext.newInstance(
"com.nexes.jaxb.test");
Next, a Marshaller object is created using the createMarshaller() method:
Marshaller marshaller = jaxbContext.createMarshaller();
The code then sets a property specifying that JAXB_FORMATTED_OUTPUT should be written to the output file:
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
new Boolean(true));
Finally, the marshaller takes the current class instance and writes it to a FileOutputStream. The FileOuputStream is mapped to the file birthdateInstance.xml:
marshaller.marshal(birthdate, new FileOutputStream(
"birthdateInstance.xml"));
Optionally, you can validate an object you are serializing against a JAXB-generated content tree. To do that, use the Validator object as follows:
Validator validator = jaxbContext.createValidator();
Validator.validate(birthdate);
The JAXB specification does not require that a class instance be validated before being marshalled to an XML representation.
You can try out marshalling and unmarshalling by running the JAXB test servlet that accompanies this tip. For details, see the section Running the Sample Code.
For more information about JAXB, see the Java Architecture for
XML Binding (JAXB) page. It includes links to detailed specifications and articles on using JAXB. Also see the document, Java Architecture for XML Binding (JAXB) Bindings Schema for JAXB to learn more about the proper schema for XML binding. Also visit the jaxb project on java.net.
REVISITING TIMERS WITH ENTERPRISE BEANS
It's sometimes the case that a J2EE application needs to perform a regularly scheduled back-end task. This naturally calls for a timer. Prior to J2EE 1.4, creating timers for enterprise beans typically meant creating a custom solution, reusing the J2SE timer functionality, or going to a third-party product. However J2EE 1.4 provides for a timer service -- one that containers manage. This service maintains each of the registered timers, and notifies Enterprise JavaBeans (EJB) technology components (also known as enterprise beans) when the timer expires. If the container crashes, the timers are recreated when the server restarts. Any timers that have expired then notify the affected enterprise beans.
The May 27, 2004 Enterprise Java Technologies Tech Tip titled The Enterprise Bean Timer Interface presented a simple timer bean that cleans a log when the timer expires. The term "expires" here means that the timer counted down to zero and notified the enterprise bean that it needs to do something. It does not mean that the timer has been invalidated and discarded from the timer service -- this is known as canceling the timer.
The following tip reviews the source code necessary to implement a simple timer in a session bean. The tip builds on the previous tip on timers by discussing some considerations when working with timers. It assumes that you are familiar with the EJB architecture, including both deployment and locally exposing bean methods.
Reviewing the Timer Architecture
Let's review the basics: An enterprise bean can create any number of timers, provided that the EJB environment in which the bean is deployed allows it. Timers that are created by entity beans are specific to the primary key identity of the bean. As a result, there is one timer for each unique instance of the entity bean. Timers that are created by stateless session beans and message-driven beans are shared between instances in a container pool.
Here are the four primary classes or interfaces used to create timers in the EJB 2.1 architecture:
javax.ejb.TimedObject. This interface specifies a method, ejbTimeout(), that must be implemented by the enterprise bean designer. The timer service calls the ejbTimeout() method when each timer expires.
javax.ejb.TimerService. This class is the container's timer service object. A TimerService is obtained through the getTimerService() method of the SessionContext.
javax.ejb.Timer. This class allows the user to create timers (as well as cancel them) pertaining to the current enterprise bean. A Timer is created through the createTimer() method of the TimerService object.
javax.ejb.TimerHandle. This object provides a serializable copy of the Timer. The copy can be used to store and retrieve persistent timers. A TimerHandle is returned by the getHandle() method of a Timer object.
Creating an EJB timer is quite simple, and consists of only two steps:
- Create an enterprise bean (that is, an entity, stateless session, or message-driven bean) that extends the
javax.ejb.TimedObject interface. This interface requires that a method called ejbTimeout() will be called by the timer service when the timer expires.
- Create the timer using the
TimerService object. After you have an instance of the TimerService object, use one of the four createTimer() methods to put the timer into service. The methods instantiate a timer inside the EJB container.
These four createTimer() methods are as follows:
createTimer(long timeoutDuration, Serializable info). This method creates a single-event timer. The timeoutDuration parameter specifies the amount of time, in milliseconds, that is allowed to pass before the timer expires and the TimedObject's ejbTimeout() callback method is invoked.
createTimer(Date firstDate, Serializable info). This method works in a similar manner to the previous method. However, instead of specifying a timeout, you provide a Date object that specifies when the timer should fire.
createTimer(Date firstDate, long timeoutInterval, Serializable info). This method specifies an initial date on which the timer should fire. It also specifies the interval of time before each repeated firing of the timer.
createTimer(long timeoutDuration, long timeoutInterval, Serializable info). This method is similar to the previous interval method, except that the initial firing is based on a specified time duration, in milliseconds, from the current system time.
Use the interval timer versions of the createTimer() method for events that occur repeatedly at constant intervals. Use the single event (that is, non-interval) versions of the method for a timer that only needs to be fired once, and then will be discarded. Notice that each createTimer() method includes a Serializable parameter called info. This is used to pass in an identification tag for the timer, typically a String, that can be used to locate a timer instance at a later point if needed. It can also be used to pass a reference to an object that might be needed during timer callback processing. You can set the parameter to null if it is not needed.
If you need to cancel a timer, you can use the cancel() method on the target Timer object. The method cancels the current timer.
Here are is an example of a stateless session bean that includes methods that create and kill a timer.
public class TimerBean implements SessionBean, TimedObject
{
// EJB Framework Code Omitted
public void createTimer(
long startTime, long timeout, String id)
throws javax.ejb.EJBException, java.rmi.RemoteException
{
TimerService timerService =
sessionContext.getTimerService();
Timer timer =
timerService.createTimer(startTime, timeout, id);
}
public void killTimer(String id)
throws javax.ejb.EJBException, java.rmi.RemoteException
{
TimerService timerService =
sessionContext.getTimerService();
Collection timers = timerService.getTimers();
Iterator iterator = timers.iterator();
// Search through the collection, and find the timer
// that matches the given ID. For each instance
// that is found, call the cancel() method on it.
while (iterator.hasNext()){
Timer t = (Timer)iterator.next();
if (t.getInfo().equals(id))
t.cancel();
}
}
}
Considerations in Working With Timers
Here are some important things to consider when designing enterprise beans that use timers.
- Entity beans, stateless session beans, and message-driven beans can each have their own timer service. Stateful session beans, however, cannot have a timer service. (However, if a stateful session bean is passed an EJB timer from another enterprise bean, it could invoke the
javax.ejb.Timer operations.)
- For entity beans, create timers in the
ejbPostCreate() method. This way, the timers can be attached to the specific bean instance. For stateless-session beans, create timers in a business method of the bean. For message-driven beans, timers should be created whenever a message is received.
- Remember the serializable info parameter in each of the
createTimer() methods. The parameter is useful if you need to create more than one timer in a bean (among other uses).
- You can use the
getHandle() method of the Timer object to serialize and restore a TimerHandle instance for later use. After you deserialize a TimerHandle, use the getTimer() method to reobtain an instance of the timer.
- It's often helpful to provide some means of canceling timers. However, be aware that single-action timers are removed after they successfully expire. Many applications rely on periodic timers to continue firing. EJB timers associated with entity beans are removed automatically when the entity bean (primary key) is removed. In addition, all EJB timers are removed when the application is undeployed.
- Entity beans typically have a timer for each of their instances. Message-driven beans and stateless session beans are associated with the type of bean, not the instance of a particular bean. When the timer expires for a message-driven bean or stateless session bean, the container calls the
ejbTimeout() method on a single TimedObject bean instance in the instance pool. The container can pick any instance.
- Each call to
createTimer() creates a new timer object inside of the container. Be sure not to flood the application server with timers if you can reuse one that is already processing. If you want to guarantee exclusivity by checking for existing timers, use the getTimers() method of the timer service. This method is shown in the killTimer() method in the example above.
- If you use a timer with a transaction, you should give the
ejbTimeout() method a transaction attribute of RequiresNew. Creating and canceling the timer should be done in the context of the current bean. In that case, if the transaction needs to be rolled back, the timer creation and cancellation is also rolled back.
- A number of vendors now provide JMX MBeans for managing EJB timers.
For more information on EJB Timer best practices, see the section Using the J2EE 1.4 Timer Service for Scheduling Actions in the Java BluePrints Solutions Catalog. This is an excellent resource on timer design considerations.
If you want to experiment with timers, download the sample
archive for the May, 27, 2004 Enterprise Java Technologies Tech Tip, The Enterprise Bean Timer Interface.
Also, see the EJB 2.1 specification.
Running the Sample Code
Download the sample archive for these tips.
The application's context root is ttdec2004-jaxb. The downloaded war file also contains the complete source code for the sample.
You can deploy the sample archive 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/ttdec2004-jaxb.war
Replace install_dir with the directory in which you installed the war file.
You can access the application at http://localhost:8080/ttdec2004-jaxb
For a J2EE 1.4-compliant implementation other than the J2EE 1.4 Application Server, use your J2EE product's deployment tools to deploy the application on your platform.
When you start the application, you should see a page that looks like this (not all of the page is shown):
Click on the link that says "Click here to see the example servlet." This runs JAXB test servlet. You should see the following:
Additional Resources
NetBeans IDE 4.0 is the first, free open source IDE to support J2SE 5.0. NetBeans IDE 4.0 has the Tomcat server built in, provides integrated profiling (JFluid), and a project system based on Ant to meet all your development needs. You can download NetBeans IDE 4.0 from the NetBeans IDE downloads page.
 |
IMPORTANT: Please read our Licensing, Terms of Use, and Privacy policies:
http://developer.java.sun.com/berkeley_license.html
http://www.sun.com/share/text/termsofuse.html
Privacy Statement: Sun respects your online time and privacy (http://sun.com/privacy). You have received this based on your email preferences. If you would prefer not to receive this information, please follow the steps at the bottom of this message to unsubscribe.
Comments? Send your feedback on the Enterprise Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=newslet
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 Sun Developer Network - Subscriptions page, choose the newsletters you want to subscribe to and click "Submit".
- To unsubscribe, go to the Subscriptions page, uncheck the appropriate checkbox, and click "Submit".
ARCHIVES: You'll find the Enterprise Java Technologies Tech Tips archives at:
http://java.sun.com/developer/EJTechTips/index.html
Copyright 1994-2004 Sun Microsystems, Inc. All
rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.
This document is protected by Copyright 1994-2004 Sun Microsystems, Inc. in the United States and other countries.
|