While J2EE technology is designed to support portability,
it's always possible to write nonportable code. In Part 2 of this
series, we (the BluePrints development team) offer recommendations on
how to avoid specific design, implementation, and deployment pitfalls
that can compromise portability.
A comprehensive collection of the J2EE BluePrints application
programming model can be found at
http://java.sun.com/j2ee/blueprints.
This month, our recommendations are:
- Deploy each Web application with a unique context root to
avoid colliding with other applications.
- Use handles or remote object references to store session
state in HttpSession.
- Be aware of changes in taglib DTD from J2EE 1.2 (JSP 1.1) to
J2EE 1.3 (JSP 1.2).
- Always import all classes used in a JSP.
- Never throw resource-specific exceptions from BMP code.
- Wrap authentication code in helper classes.
- Always completely specify security roles.
- Uses of EJBContext.getCallerPrincipal
().getName().
Unique Context Roots
Always specify a unique context root for every Web
application you deploy to avoid naming collisions with applications
already deployed.
The context root is the base path of a Web application,
relative to the server's base URL. For example, if your server's base
URL is http://j2eeserver.com:8000 and the context root is apps/myapp,
then all components of the Web application will be accessed relative to
http://j2eeserver.com:8000/apps/myapp. The <context-root> element of
the Web application's deployment descriptor (web.xml) specifies the
application context root.
Server behavior can be unpredictable if two applications are
deployed to the same context root since it isn't clear which
application should receive an incoming request. Servers and
deployment tools will differ in how they handle context root
collisions. A good deployment tool will detect context root
collisions at deployment time and allow the deployer to choose a
different context root. Selecting a unique context root for every
application will prevent this problem from ever occurring. For
example:
App1.ear: war_runtime.xml
<web>
<display-name>WebTier</display-name>
<context-root>coolpetsestore</context-root>
</web>
App2.ear: war_runtime.xml
<web>
<display-name>WebTier</display-name>
<context-root>exoticpetsestore</context-root>
</web>
The code snippets in the preceding example show how App1 and
App2 specify unique <context-root> entries so that requests can be
clearly channeled to the appropriate application Web page.
Store Enterprise Bean Handles or Remote References in HttpSession
It's perfectly legal to store either handles or remote
references to enterprise beans in HttpSession.
Every enterprise bean remote reference extends EJBObject. A
remote reference's method EJBObject.getHandle() returns a handle,
which is a persistable reference to the object. A handle can be
serialized and persisted, and then at some later time deserialized
and converted back into a remote reference to the original bean (if
the bean still exists).
Some application developers believe that a handle is the only
way to store a reference to an enterprise bean in HttpSession, but
this isn't the case. In fact, an enterprise bean reference (acquired
from a home interface create or finder method) can be stored directly
in HttpSession without first converting it to a handle. A compatible
application server that supports distributable servlets is required
by the specification to correctly manage enterprise bean references
stored in HttpSession. Therefore, remote references (that extend
EJBObject) can be stored directly in HttpSession with no loss of
portability.
For more information refer to the J2EE Platform Specification
1.2, section 6.6.
New TagLibrary Format: J2EE 1.2
If you're migrating applications from J2EE 1.2 to J2EE 1.3,
you may need to update any TagLibrary descriptor (TLD) files in your
application to the new format. The TLD file DTD has changed from J2EE
1.2 (using JSP 1.1) to J2EE 1.3 (JSP 1.2 ).
Here's an excerpt from a JSP 1.1 taglib.tld file:
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>j2ee</shortname>
Here is the same snippet for JSP 1.2:
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>j2ee</short-name>
Your application server will hopefully provide good migration
tools that will update these files for you; otherwise, you may have
to check and edit them manually.
The samples used in this example are just a few of the JSP
DTD tag name changes. See the relevant sections of the JSP 1.1
(section 5.3.5) and JSP 1.2 (Appendix C) specifications for details.
Always Import Classes Used in JSP Pages
With the exception of classes that are guaranteed to be
imported, always import any class you use in a JSP page. This may
seem like an obvious point, but this item addresses a subtlety of
Tomcat - and possibly other JSP containers. Tomcat (version 3.0)
found in the J2EE 1.2 Reference Implementation imported more packages
than those required by the specification (this has been fixed in the
latest release). In particular, it was possible to use
java.io.PrintWriter without importing it. Pages that did so would
work in Tomcat but not in other JSP page environments. Importing all
classes used by a JSP page will prevent this problem from occurring.
The only packages that can portably be used without importing
are java.lang.*, javax.servlet.*, javax.http.*, and
javax.servlet.jsp.*. All other packages should be imported in the JSP
page.
JSP containers other than Tomcat may import classes beyond
those required by the specification, but relying on the presence of
those classes may make your JSP pages nonportable. See the section on
the import keyword in the JSP 1.2 pfd 2 specification, section
2.10.1.1.
Never Throw Resource-Specific Exceptions from BMP Code
Entity beans that manage their own persistence should never
throw exceptions (such as SQLException) from BMP code; instead, they
should define and use application exceptions to report persistence
errors.
Bean providers should be especially careful to avoid
including back-end resource-specific details in their components'
interfaces since doing so may limit where the components might be
used. One easily overlooked form of resource dependence is the set of
exceptions a method may throw. BMP methods don't necessarily use a
SQL database to manage their persistence, so SQLException doesn't
belong in the BMP method signatures.
Instead of throwing SQLException, define system- and
application-level exceptions for the class and throw those exceptions
in response to error conditions.
The BluePrints program recommends the Data Access Object
(DAO) design pattern (
http://java.sun.com/j2ee/blueprints/design_catalog/dao)
to encapsulate a back-end
resource, such as a SQL database. A DAO provides access to a system
resource without reference to how that resource is implemented. For
example, a DAO might offer methods to load, update, and delete a
persistent object, and methods to set and get that object's
properties, while hiding the implementation details of how those
operations occur.
The DAO can catch resource-specific exceptions and translate
those exceptions to the custom system- or application-level
exceptions described above.
See Listing 1 for an example from the Java Pet Store. In
method deleteAccount(), a SQLException is translated to an
AccountDAOSysException, which extends java.
lang.RuntimeException to indicate a system-level exception.
It's worth noting that the Account bean indicates
application-level errors with subclasses of AccountDAOAppException
(such as AccountDAODBUpdateException). An application-level exception is
typically thrown when a recoverable error has occurred, such as an
incorrect user password. A system-level exception (like
AccountDAOSysException above) is typically thrown when a
nonrecoverable error has occurred, such as a database crash. Learn
more about the differences between system- and application-level
exceptions in Chapter 12 of the EJB specification.
Security: User Authentication
Keep all user login code in classes separate from your
application classes so you can reimplement them if you port the
application or change your user authentication mechanism. The J2EE
platform's long-term expectations are that developers won't be
writing authentication functionality directly into their
applications; however, developers will do so as long as the
container-provided mechanisms aren't adequate to suit the needs of an
application. If this is done, it would be wise for the developer to
isolate the code so that it can be easily removed as containers
become more capable.
Isolating the code specific to an authentication mechanism
makes porting easier since just that part of the code will need to be
rewritten. See Scenario #3 on packaging from the JDJ "Build to Spec!"
article (Vol 6. issue 7) for more information.
For managing user authentication, consider using Java
Authentication and Authorization Service. JAAS allows services to
authenticate and enforce access controls on users. To learn more
about JAAS, see the resources section at the end of this article.
See also Table 6.2 of the J2EE 1.2 platform specification,
where the J2EE security permission set for each type of component
(application clients, applet clients, servlets/JSPs, EJBs, and so on)
are defined.
Define Method Permissions When Security Roles Are Used in EJBs
If your application uses security roles, always specify
method permissions with the <method-permission> element in the EJB
deployment descriptor (ejb-jar.xml) for all of the EJB client-view
methods.
There is a gray area in the J2EE 1.2 specification when it
comes to defining the behavior of an unspecified method permission if
security roles are used. The gray area has, legitimately, been
interpreted inconsistently by container vendors. This means that EJB
containers may not behave consistently for EJB methods with an
unspecified method permission when security roles are used.
For example, some container vendors may interpret unspecified
method permissions to mean none, essentially an uncallable method for
no user access; others may interpret unspecified method permissions
to mean everybody for wide-open user access. In any case, if you use
security roles and want the security behavior to be portable across
container vendors, specify your intentions clearly with well-defined
method permissions.
The application developer may choose to create a role named
everybody and then map this role to wide-open user access. This
assumes the container has the ability to map the everybody role to a
set of users.
The everybody role could then be used in the ejb-jar.xml
files for those methods where wide-open user access is allowed. For
an example, see Listing 2.
Refer to the J2EE 1.3 specification for the latest changes
for using security roles and EJB method permissions.
Use of Security APIs:
EJBContext.getCallerPrincipal().getName()
The J2EE 1.2 specification leaves the result of
EJBContext.getCallerPrincipal().getName() as unspecified in terms of
the requirements of the returned value. An application developer
shouldn't make any assumptions about the returned value when
designing an application or component. Why?
The main reason is that there's no standard format for the
return value of EJBContext.getCallerPrincipal().getName().
As of proposed final draft2,
the EJB 2.0 specification mentions that calling methods "might, for
example, use the name as a key to information in a database." In
general this will work; however, there's a caveat that in complex
security infrastructures this value might change. Here are some
scenarios where this might be a problem:
- When running a server from one vendor and then switching to a
server from another vendor while keeping the same database (e.g.,
using BMP)
- When running two servers from different vendors where a bean
on each server needs access to that row
In short, the call itself is portable but the return value isn't.
One portable example might be simply as a display value in
the Web tier, for example:
"Hello <%= request.getUserPrincipal().getName() %>"
Another possibility is as a log message in the EJB tier for
auditing, for example:
"8:15 AM: User " + ejbContext.getCallerPrincipal().getName() + "
withdrew money."
Until the return values are standardized from this API, its
use to produce values that must be compatible with values that may be
produced by other container vendors isn't a recommended portability
practice. Look for this item to be addressed in future versions of
the J2EE platform specification.
We Want to Hear from You
The J2EE BluePrints team is interested in hearing about your
experiences developing applications for the J2EE platform and your
experiences with BluePrints. Send feedback and comments to
j2eeblueprintsinterest@sun.
com. Special thanks goes to the J2EE development community for their
continued commitment to the J2EE platform.
Resources
- Check out the J2EE BluePrints Web site at
http://java.sun.com/j2ee/blueprints,
where you can download the Java Pet Store sample
application, the BluePrints book Designing Enterprise Applications
with J2EE, and view the complete BluePrints design pattern catalog -
all for free.
- Download the J2EE Platform Specification, as well as other
J2EE developer support documentation and software (including the free
J2EE Reference Implementation), from
http://java.sun.com/j2ee.
- View the current list of J2EE licensees who have passed the
Compatibility Test Suite at
http://java.sun.com/j2ee/compatibility.
- Learn more about JAAS, the Java Authentication and
Authorization Service, at
http://java.sun.com/products/jaas.
Author Bio
Elizabeth Blair is a staff engineer with Sun Microsystems, where she
leads the Java 2 Platform, Enterprise Edition BluePrints
development team. Liz contributed to the recent Java Pet Store sample application, with an
emphasis on the tests for EIS and EJB architecture. In the past she
has worked on the Compatibility Test suite (CTS) for the J2EE
platform, the J2SE platform, and the WABI (Windows Applications
Binary Interface) projects. liz.blair@sun.com
Listing 1
public class AccountDAOImpl implements AccountDAO {
public void remove(String id) throws AccountDAODBUpdateException,
AccountDAOSysException {
deleteAccount(id);
}
private void deleteAccount (String userId) throws
AccountDAODBUpdateException,
AccountDAOSysException {
String queryStr = "DELETE FROM ·";
PreparedStatement stmt = null;
Debug.println("queryString is: "+ queryStr);
try {
getDBConnection();
//stmt = dbConnection.createStatement();
//int resultCount = stmt.executeUpdate(queryStr);
stmt = createPreparedStatement(dbConnection, queryStr);
int resultCount = stmt.executeUpdate();
if (resultCount != 1)
throw new AccountDAODBUpdateException
("ERROR deleteing account from ACCOUNT_TABLE!! resultCount = "+
resultCount);
} catch(SQLException se) {
throw new AccountDAOSysException("SQLException while removing " +
"account; id = " + userId + " :\n" + se);
} finally {
closeStatement(stmt);
closeConnection();
}
}
}
Listing 2
<security-role>
<role-name>manager</role-name>
</security-role>
<security-role>
<role-name>everybody</role-name>
</security-role>
<method-permission>
<role-name>manager</role-name>
<method>
<ejb-name>EmployeeService</ejb-name>
<method-intf>Remote</method-intf>
<method-name>setPerformanceRating</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</method>
</method-permission>
<method-permission>
<role-name>everybody</role-name>
<method>
<ejb-name>EmployeeService</ejb-name>
<method-intf>Remote</method-intf>
<method-name>getPerformanceRatingForm</method-name>
<method-params/>
</method>
</method-permission>