By Liz Blair; Reprinted from
Java Developer's Journal
July 2001
Many J2EE 1.2-based applications and components are emerging in
the marketplace as the J2EE platform matures. Application portability
is one of the most important benefits offered by the J2EE platform.
Through the J2EE Java Pet Store sample application, the J2EE Blueprints
team has developed a set of best practices for ensuring application
portability across J2EE-compatible application servers.
This article is the first in a series of J2EE application and component
portability recommendations for the J2EE development community. This series
will focus J2EE application portability in light of maturing J2EE technologies,
especially J2EE 1.3 and EJB 2.0.
This first article presents several portability guidelines for the J2EE platform,
version 1.2, but includes references to the upcoming version 1.3 where appropriate. This article assumes that the
reader is familiar with basic J2EE 1.2 platform terminology.
What Is Portability?
Portability is an important goal for application developers who want to maintain
freedom of choice in technology. It's important to understand how J2EE application
portability fits into the Java portability picture. Java technology provides
several levels of portability, each supported by a particular specification:
- Source-code: Allows a single source base to provide identical results,
regardless of CPU, operating system, runtime system, or compiler. The Java language
is highly source-code portable because its definition (the Java Language Specification)
clearly spells out such details as byte order, memory management behavior, and the size
and format of its primitive types.
- CPU: Allows a compiled program to execute identically on
computers with different CPUs. The Java
Virtual Machine1 provides CPU portability
by specifying a virtual instruction set to which Java (or other programming language) source
code compiles, isolating the compiled code's behavior from the underlying CPU.
- OS: Allows a developer to write a program that accesses
system-level resources in a uniform way, regardless of the underlying operating
system. The Java Platform provides OS portability by specifying a "virtual
operating system" that gives developers a unified model of system services.
- J2EE application: Allows J2EE application developers to
write client/server components and applications that deploy and execute identically
regardless of the underlying server implementation or vendor. The J2EE Platform
Specification defines how the various J2EE platform technologies (servlet containers,
EJB containers, transactions, security, and so on) must behave, so J2EE application
developers can depend on consistent behavior across vendor implementations.
This series focuses on J2EE application portability tips and guidelines. The
J2EE Platform Specification defines a contract between J2EE product providers,
who create J2EE servers, APIs, and so on, and J2EE component and application
developers, who use those J2EE products to create solutions. Both sides of the
contract must understand their responsibilities in order to realize the portability
benefits of the J2EE platform. This article provides some detailed tips for
ensuring that your J2EE components and applications will be portable between
different J2EE vendors' products.
J2EE Product Compatibility
J2EE product providers produce Web and application servers, servlet and EJB
containers, databases, platform APIs, and development tools. The J2EE Platform
Specification defines the behavior of each type of J2EE product, so J2EE component
and application developers can depend on consistent results. Several tools are
available to help J2EE product providers create products that meet the platform
specification requirements.
The J2EE Compatibility Test Suite (CTS) helps J2EE product providers check that
their products meet the requirements of the J2EE platform specification. The J2EE
Compatibility Test Suite is a standard set of over 5,000 test cases derived from
the J2EE specification requirements. Products that pass the CTS are considered
J2EE-compatible. The tests check specific requirements for each of the J2EE
technologies. For example, the EJB container tests check that the EJB instance
life cycle behaves as defined in the EJB specification.
The J2EE Reference Implementation (RI) provides J2EE server developers with an
example implementation of a J2EE application server. The J2EE RI demonstrates
that the specifications can indeed be implemented. The RI also gives application
server vendors a working server they can use to check the correctness of their
implementation. The J2EE RI is required to pass 100% of the CTS, since its purpose
is to demonstrate the complete J2EE specification.
Application Developer Tools
Good tools play an important role in helping the J2EE application developer
write portable applications. Providing powerful, easy-to-use tools is one of
the ways that product providers can serve their customers, and thereby gain a
competitive advantage in the marketplace.
Several kinds of application development tools provide support for application
portability. Code generation tools generate code for various parts of an application,
based on higher-level specifications provided by the developer. Verification tools
ensure the correctness of deployment descriptors. Sample code and references help
developers understand how to use application components. Let's look at an example of
each of these types of tools.
Code generation tools automatically generate source code based on what the developer
has specified or has already coded. Such tools improve portability by creating code
that adheres to J2EE specifications. For example, a tool called EJBGen (version 1.20 - an EJB 2.0 code
generator) automatically generates the all of the needed files (Home, Remote, Local
interfaces, and the deployment descriptor) based on an existing Bean class. In
addition to eliminating mindless coding, EJBGen improves portability and code
quality by ensuring that the methods of the Bean class and its associated interfaces throw
the proper set of exceptions as required by the EJB 2.0 specification. See the list of resources at the end of the article for more on EJBGen.
Verification tools ensure that the contents of a deployment descriptor (such as web.xml or ejb-jar.xml) are both well formed (meaning that the descriptor meets the basic requirements of XML) and valid (meaning that the descriptor matches the structure defined by its DTD). A deployment tool may choose not to deploy an application if it detects a problem in a deployment descriptor. The Sun RI provides a tool known as the "verifier" that verifies the contents of a war, jar, or ear file. The verifier runs a series of checks (based on requirements of the J2EE specifications) against the J2EE component or application provided. The verifier allows the developer or deployer to fix errors at, or even before, deploy time.
A good code example is worth a thousand words. Samples and references provide good
examples to demonstrate how individual J2EE components, applications, and servers all come together. Many code snippets and tutorials for particular technologies are only a Web search away, but developers need to understand how to use these technologies together: they need to be able to see "the big picture".
The Sun J2EE Blueprints program provides developers with a free sample application, the Java Pet Store. The Java Pet Store is an end-to-end, B2C, e-commerce application that gives the J2EE application developer an example from which to learn. The J2EE Blueprints program also offers design discussion explaining best practices for J2EE application design, and provides a set of design patterns for building robust, scalable,
portable J2EE applications. The Java Pet Store sample application can be downloaded for
free from Java 2 Platform, Enterprise Edition Blueprints.
J2EE Application Portability Tips
The rest of this article presents several specific technology usage tips for maximizing
J2EE application portability. Each tip may also have other benefits, but the focus is
on portability between J2EE product providers' products. This section assumes a working
knowledge of J2EE 1.2 technology.
Know When (and When Not) to Use Serializable Fields
As an Enterprise JavaBean provider, you are responsible for ensuring that all of a
bean's instance fields are serializable, so that the state of the bean can be preserved
when the bean is passivated.
While fields of primitive types like String
and int are serializable, reference fields (fields whose value is a reference to a class
instance) are serializable only if the reference's class implements java.io.Serializable
and all of that class's nontransient fields are serializable.
For example, consider an entity bean called AccountEJB, which has a
field identifying
the account owner, and another fieldrecording the account contact
information:
public class AccountEJB implements
EntityBean {
private String userId;
private ContactInformation info;
// ...
}
public class ContactInformation
implements Serializable {
private String telephone;
private String address;
// ... etc ...
}
|
Since the ContactInformation class is serializable, and all of its
fields are
serializable, then AccountEJB's info field is also serializable, and the
requirements
are met.
You must mark all fields of nonserializable types as transient. Fields
marked as
transient are ignored by the JVM's serialization machinery.
For example, a database connection, represented by java.sql.Connection,
is not
serializable, so it is marked transient:
public class AccountEJB {
// ...
private transient Connection
dbConnection = null;
// ...
}
|
For more information on serialization and passivation, refer to Section 6.4.1 of
the EJB 1.1 Specification (available for download from the resource list).
Close Database Connections Cleanly
When retrieving items from a database, open a connection through a Connection instance,
and then execute an SQL statement through a Statement instance. When you're done,
remember to close your Statement before closing your Connection:
Connection c;
PreparedStatement ps;
ResultSet rs;
try {
c = dataSource.getConnection();
ps = c.prepareStatement(statement);
rs = ps.executeQuery();
// Do stuff with 'rs'.
}
catch (SQLException se) {
// Handle this exception.
}
finally {
if (rs != null)
rs.close();
// Close statement before connection.
if (ps != null)
ps.close();
if (c != null)
c.close();
}
|
What happens if you try to do this the other way around? Some JDBC 2.0 driver implementations
will throw an exception if you try to close a Connection with open Statement instances.
This is a portability issue because different JDBC implementations handle this detail
differently.
Be careful When Writing Deployment Descriptors
If your J2EE-compatible application server provides tools to construct deployment
descriptors for you, you should use those tools. Hand-written deployment descriptors
are prone to errors such as misordering of elements, duplicate element entries, and
simple typos.
If you still want (or need) to write your own descriptors, many application servers
provide tools that validate a deployment descriptor against a DTD, ensuring that the
descriptor's contents are both well formed and valid.
The verifier tool shipped with the J2EE Reference Implementation will catch only some
errors in a descriptor, but not all of them. You can supplement the verifier with a
small program that uses a validating parser to check a descriptor's validity against
its DTD:
import java.io.File;
import java.io.StringWriter;
import java.io.PrintStream;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import
javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import
javax.xml.parsers.ParserConfigurationException;
public class XMLChecker {
public static boolean parseFile(
File file, PrintStream out, boolean validate) {
try {
Document document = null;
DocumentBuilderFactory docBuilderFactory
= null;
DocumentBuilder docBuilder = null;
try {
docBuilderFactory =
DocumentBuilderFactory.newInstance(
);
docBuilderFactory.setValidating(
validate);
if (docBuilderFactory != null) {
docBuilder =
docBuilderFactory.newDocumentBuilder();
}
} catch (ParserConfigurationException pce) {
out.println(pce);
}
if (docBuilder != null) {
document = docBuilder.parse(file);
}
return true;
} catch (java.io.IOException ioe) {
out.println("Caught IO exception" + ioe);
} catch (org.xml.sax.SAXParseException spe) {
out.println(spe);
} catch ( org.xml.sax.SAXException se) {
System.out.println(se);
}
return false;
}
}
|
Use Valid Encodings for Deployment Descriptors
Ensure that the static deployment descriptors for your Web and EJB components include
a valid encoding declaration. For example, if you're using encoding ISO-8859-1, the
deployment descriptor should begin with the following line:
<?xml version="1.0" encoding="ISO-8859-1"?>
In an encoding declaration, the value ISO_8859_1 is not an acceptable substitute for
ISO-8859-1.
Refer to Section 4.3.3, "Character Encoding in Entities," of the W3C XML 1.0
Recommendation for more details on XML character encodings.
Using Methods Correctly Is Vital
When an entity bean is activated through a callback to ejbActivate(), it is
returned to the ready state so that business methods can be called on it. The activation
method, however, is not responsible for loading the bean's fields from a resource; that
is the job of ejbLoad().
Similarly, when an entity bean is passivated through a callback to ejbPassivate(),
it's returned to the pooled state. The passivation method, however, is not responsible
for storing the bean's fields into a resource; that is the job of ejbStore().
Even if you tried accessing a resource from ejbActivate() or ejbPassivate(),
a container implementation may reject the access, on the grounds that the EJB 1.1
Specification does not allow such access from either of these methods. Using these methods
correctly is vital for application portability.
For more information on these methods, refer to Sections 9.1.5 and 9.1.6 of the
EJB 1.1 Specification.
Throwing Exceptions from Entity Beans
The EJBGen tool helps developers write EJBs, and in particular ensures that EJB method
signatures express the appropriate thrown exceptions. If you do not use such a tool,
then you should double check the EJB method signatures, especially the thrown exceptions,
in your component's EJB class, Home and Remote interfaces.
Here is a summary of the EJB 1.1 Specification exception requirements for EJB Home,
Remote, and EJB class method signatures:
Remote Interface:
- Throws clause must include
RemoteException.
- All exceptions defined in the throws clause of the matching method of the
EJB
class must be defined in the throws clause of the method of the remote interface.
Home Interface - Session:
- All exceptions defined in the throws clause of the
ejbCreate method
must be defined in the throws clause of the matching create method in the Home interface.
The throws clause must include CreateException.
Home Interface - Entity:
- All exceptions defined in the throws clause of the
ejbCreate/ejbPostCreate methods
must be defined in the throws clause of the matching create method (the set of exceptions
must be a superset of the union of exceptions defined for the ejbCreate/ejbPostCreate methods).
- All exceptions defined in the throws clause of an
ejbFind method of the EJB class must be
included in the throws clause of the matching find method of the Home interface.
The throws clause of the finder method must include FinderException.
The J2EE RI 1.2.1 verifier tool checks for method signature exceptions.
Packaging
How to package an application seems to be an area of confusion among developers in
general.
Packaging is an important portability consideration. Packaging requirements and options
are described in the Servlet 2.2, EJB 1.1, and J2EE 1.2 specifications. The Servlet and
EJB specifications do a good job of describing what each application component package
(war, jar) should do and the J2EE specification describes how to pull it all together
into an application package (ear).
However, some concrete examples describing how you can package your application make
the specifications more comprehensible. If you don't understand the specifications
regarding application packaging, your application might not be portable.
The following scenarios present increasingly complex deployment situations, describe
options for packaging them, and provide some considerations and notes on deployment for
each scenario. Keep in mind that war file and ear file formats are different.
Scenario #1:
Single ear, war references EJB
This first, simple scenario shows how to reference an Enterprise JavaBean in a single
ear file from a single war file.
appA1.ear contains:
x.war References client view of ejbA1.
ejbA1.jar
Packaging Options:
- Include
ejbA1_clientview.jar in the WEB-INF/lib directory of x.war
- Expand contents of
ejbA1_clientview.jar into the WEB-INF/classes directory of x.war
- Include
ejbA1_clientview.jar in your own specific war directory (for example,
"clientviews"). Then the war can refer to the clientviews directory jar file via its
Class-Path entry in the manifest.mf.
War File Notes:
- The Web server should automatically recognize jar files in the war
file's
WEB-INF/lib directory. A bug in the J2EE RI 1.2.1 causes jar files
located in the WEB-INF/lib directory not to be recognized.
- If a war file needs to reference an EJB, the war should do so via the client view
of that EJB. A client view of an EJB contains all of the classes that a client program
needs to access a referenced EJB.
- A packaging don't: don't try to access an EJB client jar file from the war's
manifest.mf Class-Path entry when that EJB client jar does not exist in the war file.
Remember, a war file has a different file format than a jar file. You can't access EJB
client view jar files that are not local to the x.war file via the manifest.mf
Class-Path entry.
- When a Web application's
web.xml file references ejbA1 via
an <ejb-ref> element, the Web container is required to be able to find the
ejbA1_clientview.jar file automatically, assuming ejbA1.jar's
deployment descriptor includes an appropriate <ejb-client-jar> element. See EJB 2.0
specification, section 23.4.
Scenario #2:
Single ear, EJB references another EJB
This scenario shows how to reference one EJB from another within a single ear file.
appA1.ear:
x.war
ejbA1.jar References client view of ejbA2
ejbA2.jar
Packaging Options:
- Include
ejbA2_clientview.jar in ejbA1.jar's Class-Path
manifest.mf entry
- Include
ejbA2_clientview.jar expanded in ejbA1.jar file.
EJB Jar File Notes:
- If an EJB has a client view available, it should specify the name of the client view
jar file in the <ejb-client-jar> entry of its
ejb-jar.xml file.
- A packaging don't: EJB jar files cannot reference a war file using a
manifest.mf
Class-Path entry. Remember, a war file has a different file format than a jar file.
Scenario #3:
Single ear, EJB references util classes
This scenario shows how to reference utility classes from within a single ear file.
appA1.ear:
x.war
ejbA1.jar References classes in yUtil.jar
yUtil.jar
Packaging Options:
- Include
yUtil.jar in ejbA1.jar's Class-Path manifest.mf entry
- Expand
yUtil.jar's contents into ejbA1.jar file.
Utility and Implementation-Specific Notes:
- A packaging don't: If you have common utility classes, don't duplicate the code in
your application components. Instead, create a simple
util.jar file and
include the jar file as needed (see above options for wars and EJB jars).
- If you've got implementation-specific code (that is,
deploy/undeploy), but want to
provide a way to minimize the impact to different application servers, following these
steps will help to minimize the effort to deploy the application on different servers:
- Provide an interface for a common look and feel.
- Provide your own server specific implementation classes.
- Document how to use and rebuild this piece. Important!
- Put it in its own jar file, for example,
xAdapter.jar
- Include
xAdapter.jar file as needed (see above options for wars and
EJB jars).
For example:
public interface Foo {
public doBar();
}
public class FooImpl implements Foo {
public doBar() {
// Your server-specific code here
}
}
|
Scenario #4:
Inter-ear References
In this case, there are several ear files, and an EJB references another EJB in a
different ear file:
appA1.ear:
x.war References classes in ejbA2.jar
ejbA1.jar
yUtil.jar
appA2.ear:
z.war
ejbA2.jar
zUtil.jar
|
Packaging Options:
- Include
ejbA2_clientview.jar in x.war WEB-INF/lib directory
- Include
ejbA2_clientview.jar expanded in x.war WEB-INF/classes directory
- Include
ejbA2_clientview.jar in your own specific war directory ("clientviews", for
example). Then the war can refer to the clientviews directory jar file via its Class-Path
entry in the manifest.mf.
Scenario #5:
multi-ear, utility classes referenced from ear and war
In this scenario, several ears exist, and reference utility classes from both ear and
war files.
appA1.ear:
x.war References classes in util.jar
appA2.ear:
ejbZ.jar References classes in util.jar
util.jar
The util.jar file must be present in both application ear files. The
appA1.ear
application includes util.jar in its WEB-INF/lib directory. The
appA2.ear application
includes util.jar and is referenced by ejbXZ.jar.
Conclusion
This article has presented some tips that should help you create more portable J2EE
applications. The J2EE Blueprints team is interested in hearing about your experiences
with the J2EE platform. Please send feedback and comments to
j2eeblueprints-interest@sun.com.
Special thanks to the J2EE development community for their continued commitment to the
J2EE platform and for developing J2EE applications and components.
RESOURCES:
About the Author
Elizabeth Blair is a staff engineer with Sun Microsystems
where
she leads the Java 2 Platform, Enterprise Edition Blueprints implementation
team.
She contributed to the recent Java Pet Store sample application with an
emphasis on
the tiers for EIS and EJB architecture. In the past, Liz has worked on the
Compatibility Test (CTS) suite for the J2EE platform, the J2SE platform, and
the
WABI (Windows Applications Binary Interface) projects.
1 As used on this web site, the terms Java virtual
machine or Java VM mean a virtual machine for the Java platform.
Back
|