|
Welcome to the Enterprise Java Technologies Tech Tips for February 27, 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:
Custom Tag Files
Using Enterprise Beans with JSP Pages
These tips were developed using Java 2, Enterprise Edition, v 1.4 SDK. You can download the SDK at http://java.sun.com/j2ee/1.4/download-dr.html.
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.
You can download the sample archive for these tips. The context root for the application is ttfeb2004, and 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.
CUSTOM TAG FILES
Custom tags in JavaServer Pages (JSP pages) look like HTML tags. However the custom tags are replaced at runtime with text output by a handler class associated with the tag. This month's first tip explains a new, easier way to implement custom tags.
Before JSP 2.0, the only way to create a custom tag was to implement it as a Java tag handler class. The handler class developer also needed to create a TLD file that describes the tag to the web container. While custom tags are powerful, they also require programming skills, and a nontrivial understanding of how JSP pages translate to HTML.
The new tag file feature of JSP 2.0 allows non-programmers to write reusable custom tags. It also makes life easier for programmers. Using JSP 2.0 syntax with the JavaServer Pages Standard Tag Library (JSTL) and its expression language (EL), you can now create custom tags without writing any Java code.
Tag files are reusable components for JSP pages. They provide many advantages:
- They can be used to hide or eliminate scriptlets.
- They promote code reuse by reference, instead of by cutting and pasting.
- They make JSP pages easier to write, more visually consistent, and easier to maintain.
- They can be written by non-programmers.
- Their syntax is closer to HTML than Java, so a JSP page looks more like it's written in a single language.
- They promote high productivity and rapid development because they can form high-level components.
- The TLD files used by custom tags are often automatically generated.
- They can be used to refactor existing pages. Common areas of code can be consolidated into tag files that are shared between application views.
Tag files don't completely replace custom tag handler classes. Tag files are preferable for encapsulating reusable content with associated layout and presentation. Custom tags are better for reusing application logic in JSP pages. For example, page headers and footers are excellent applications for tag files. The custom tags in JSTL, by comparison, are implemented as Java language handler classes.
Tag File Specifics
Tag files are actually translated and compiled into tag handler classes. The only difference between a tag handler and a tag handler class is that a tag handler is written in JSP syntax, while a tag handler class is written in the Java language.
JSP 2.0-compliant containers look for tag files in the Web archive directory WEB-INF/tags. Tag files can also be packaged in JAR files in WEB-INF/lib. When serving a JSP page, any time the Web container encounters a tag that is associated with a tag file, the output of the JSP content in the tag file is evaluated and included in the response stream. A tag file can define attributes, and has full access to the JSP 2.0 expression language (EL). Tag files can also create EL variables that continue to exist after the tag file has finished executing.
A tag file declares its attributes with an attribute directive. Here is an example taken from the sample code that accompanies this tip. The tag file starts with a single attribute directive:
<%@ attribute name="format" required="false" %>
The tag (which formats dates) uses this line to tell the container to expect a possible "format" attribute. The "required" attribute in the directive is set to "true" for mandatory attributes. These directives are what allow the web container to generate its own TLD files at deployment time.
A tag receives input through its attributes. In addition to the output text produced by the tag file, a tag can also "output" data by creating EL variables. A tag file might return a value to the page that called it as follows:
<%@ variable name-given="filesincluded" scope="AT_END" %>
The "name-given" provides the name of the variable to set in the page after the tag has completed. "AT_END" specifies that the variable is set when the tag file completes.
Tag File Example
This sample code for this tip includes a tag file that reimplements the custom tag which formats the date at the server in one of three ways:
- If the format is not set or is empty, the tag prints the date in a default format.
- If the format parameter begins with a dollar sign, the tag uses the remainder of the string as the name of an environment entry. The tag looks up the named environment entry and uses its value as the format.
- If the "format" parameter contains a format string (compatible with
java.text.SimpleDateFormat), then that string is used to format the date.
The tag specified by the tag file works slightly differently. If the format parameter begins with a dollar sign, the tag looks for a servlet context initialization parameter, instead of an environment entry. (The expression language of JSTL 1.0 had no built-in support for accessing environment entries.)
The sample tag file, date.tag, begins with some directives that define the attributes expected by the file. It also identifies the namespaces for the other tag libraries that this tag uses.
<%@ attribute name="format" required="false" %>
<%@ taglib uri=
"http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri=
"http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri=
"http://java.sun.com/jsp/jstl/functions"
prefix="fn" %>
The next block of the date tag file uses a <c:choose> tag to determine the format string. <c:choose> is the tag of choice for if/then/else functionality in JSP pages. The first "when" clause sets the EL variable "format" to a default value if the format is null or empty:
<c:choose>
<%-- If format is blank, set default --%>
<c:when test="${format == null or format == ''}">
<c:set var="format"
value="EEEE, d MMMM yyyy 'at' kk:mm:ss z"/>
</c:when>
...
If "format" is not empty, then it begins with a "$" or it doesn't. In the former case, the <otherwise> clause of the <choose> tag strips off the leading "$", and replaces the value of the variable "format" with the contents of the context parameter with the given name.
<c:otherwise>
<%-- Else if format starts with "$",
look up in context param,
and set "format" to its value. --%>
<c:if test="${fn:substring(format,0,1) == '$'}">
<c:set var="format_name"
value="${fn:substringAfter(format,'$')}"/>
<c:set var="format"
value="${initParam[format_name]}"/>
</c:if>
<%-- Otherwise leave it as it is --%>
</c:otherwise>
</c:choose>
As noted in the comment, if "format" doesn't begin with a "$", its value is left unchanged.
At this point in the page, the value of the EL "format" variable is set to a string that will be used to format the date. The useBean line creates a Date object with the current time. The method fmt:formatDate formats this date in the given "format":
<%-- Now actually create and format the date --%>
<jsp:useBean id="now" class="java.util.Date"/>
<fmt:formatDate value="${now}" pattern="${format}"/>
That completes the tag file.
Using the tag file is even easier. A directive at the top of sample page DatePage.jsp (linked off of index.jsp in this month's sample archive) indicates that all of the tags in the archive directory (/WEB-INF/tags) can be accessed using the prefix "mytags". Here is the directive:
<%@ taglib tagdir="/WEB-INF/tags" prefix="mytags" %>
Now, you can simply use the JSP tag file as if it were any other custom tag. Here's an excerpt from the sample page, DatePage.jsp:
The time and date at the server in the default format
are <b><mytags:date/></b>.<br>
The time zone at the server is
<b><mytags:date format="zzzz"/></b>.<br>
The server date is
<b><mytags:date format="M/d/yyyy"/></b>.<br>
The server time is
<b><mytags:date format="hh:mm:ss a"/></b>.<br>
Each occurrence of <mytags:date> results in a call to the tag file, and the tag is replaced in the output with the corresponding time and date at the server.
This example only scratches the surface. JSP tag files have a lot of options not mentioned here. For more information about JSP tag files, see the chapter "Custom Tags in JSP Pages" in the The J2EE 1.4 Tutorial.
USING ENTERPRISE BEANS WITH JSP PAGES
The January 26, 2004 issue of the Tech Tips covered the JSP 2.0 Expression Language (EL). One of the examples in the tip showed how a JavaBean could be accessed by name using an EL variable. JavaBean properties can also be accessed with simple EL expressions. This feature makes Enterprise JavaBeans components (enterprise beans) very easy to use from within JSP pages. Simply place the enterprise beans required by an application view in named attributes (in the appropriate scope), and let the JSP page access the attribute by name in EL expressions. The best way to show this technique is by example.
Sample Code
The sample code for this tip is a credit card validator application. Credit card companies have a series of rules that can be used to check the validity of a credit card number. A credit card number can be valid and still be overdrawn, expired, or cancelled. The purpose of validity checks is to catch data entry errors before they are transmitted for authorization.
A credit card number is valid if:
- It has the correct prefix for the type of credit card.
- The credit card number has the correct number of digits.
- The credit card number passes a checksum test.
- The card is not expired.
The sample application uses three components:
- A stateful session bean,
CreditCardLocal, that represents the credit card data, and performs the validation.
- A servlet,
Feb2004Servlet, that creates an instance of the bean in session scope, so each user gets one instance.
- A JSP page,
ValidateCard.jsp, that collects the data and prints the validation results.
The code works like this: a link on the index.jsp form points to the servlet. The servlet creates the session bean handle and places it in session context. The servlet then forwards the request to URL jsp/ValidateCard.jsp, which handles the card number input and validation. This form posts back to itself after the user has completed entering data. In a real application, the form would automatically forward to the next step in the purchasing process when the card validates correctly.
The code in the servlet is straightforward. It simply creates a stateful session bean of type CreditCardLocal, and places it in an HttpSession attribute under the name "creditCard". If such a variable already exists when the servlet is called, the servlet removes the previous instance.
CreditCardLocal creditCard = (CreditCardLocal)
req.getSession().getAttribute("creditCard");
// Remove it if it exists--we're starting over.
if (creditCard != null) {
try {
creditCard.remove();
} catch (Exception e) {
System.err.println(
"Exception removing credit card: " + e);
}
System.err.println("INFO: Removed previous card.");
}
// Create new enterprise bean reference in session scope
try {
InitialContext ctx = new InitialContext();
CreditCardLocalHome cclh =
(CreditCardLocalHome)ctx.lookup(
"java:comp/env/ejb/CreditCard");
String cardName = req.getParameter("cardName");
String cardType = req.getParameter("cardType");
String cardNumber = req.getParameter("cardNumber");
String cardDate = req.getParameter("cardDate");
creditCard = cclh.create(
cardName, cardNumber, cardType, cardDate);
req.getSession().setAttribute(
"creditCard", creditCard);
} catch (Exception e) {
throw new ServletException(e);
}
RequestDispatcher rd = req.getRequestDispatcher(
"/jsp/ValidateCard.jsp");
rd.forward(req, res);
Notice the last two lines above. After creating the enterprise bean reference, the servlet forwards the request to the ValidateCard.jsp page, which handles input and validation.
The first section of the page copies any parameters that might have been posted to the page into the corresponding properties of the enterprise bean. Notice how the <c:set> tag uses "target" and "property" to access the enterprise bean property of the given name.
<!-- Copy parameters to creditCard properties -->
<c:set target="${creditCard}" property="name"
value="${param['cardName']}"/>
<c:set target="${creditCard}" property="type"
value="${param['cardType']}"/>
<c:if test="${param.cardNumber != null and
param.cardNumber != ''}">
<c:set target="${creditCard}" property="number"
value="${param.cardNumber}"/>
</c:if>
<c:set target="${creditCard}"
property="expirationDateStr"
value="${param['cardDate']}"/>
If the request parameters are not set (which is true the first time the page is displayed), expressions such as ${param['cardName']} evaluate to null, and so the corresponding bean properties are set to null.
The next section of the ValidateCard.jsp page collects data from the user. The data is posted back to the same page when the user clicks the submit button. The code in this section has several interesting features. The form always posts back to itself, so each of the inputs defined below includes an attribute that sets the input's value to the current value in the credit card.
The first section of the form uses a table to control layout and local background color, and defines an input element for the form parameter "cardName".
<input type="text" name="cardName" size="32"
value="${creditCard.name}"><br>
The value of cardName is set to the EL expression ${creditCard.name}, which is equivalent to the following:
<%= ((CreditCardLocal)request.getSession().
getAttribute("creditCard")).getName() %>
If the form has been submitted before, any name that was entered into this field will appear in the cardName input box.
The next input is a set of radio buttons, one button for each credit card type. The credit card object has an "info" property of type CCInfo. This object is a java.util.HashMap containing information about all of the valid credit card types. The keys of this map are the symbolic names of each credit card, for example, "mc" for MasterCard. The values are of type CCDesc, an object that describes the card. (See the formatted source code in the code example for more detail.) The <c:forEach> loop iterates through the list of accepted credit card types, creating a radio button for each one.
<p>
<b>Type of card:</b><br>
<c:forEach var="item" items="${creditCard.info}">
<input type="radio" name="cardType"
value="${item.key}"
${(item.key eq creditCard.type) ? 'checked' : ''}>
${item.value.description}<br>
</c:forEach>
The expression ${(item.key eq creditCard.type) ? 'checked' : ''} evaluates to the string "checked" if the button being created is currently selected in the creditCard object. This loop offers a hidden benefit in the form of extensibility. To add a new type of credit card, you only need to add a CCDesc description of the type to the CreditCard's CCInfo. The next time ValidatePage.jsp executes, the loop picks up the new type of card and includes it in the list.
The next two inputs contain the credit card number and expiration
date:
<b>Card number:</b>
<input type="text" name="cardNumber" size="24"
value="${creditCard.number}"><p>
<b>Expiration date (mm/yyyy):</b>
<input type="text" name="cardDate" size="10"
value="${creditCard.expirationDateStr}">
Notice again that the values are set to the corresponding enterprise bean property values.
The third part of the form shows an error message, if appropriate, or shows a message indicating that the card is valid. This section contains two <c:when> tags and a <c:otherwise> tag. The first tag prevents an error message from being printed if the card number is null.
<c:choose>
<c:when
test=
"${creditCard.number == null || creditCard.number == ''}">
<!-- No number, so no need to complain
that it's not valid -->
</c:when>
The second <c:when> tag prints a success message when the card is
valid.
<c:when test="${creditCard.valid}">
The following card is valid:<br>
<table border="0">
<tr><th align="left">Name:</th>
<td>${creditCard.name}</td>
</tr>
<!-- and so on... -->
</c:when>
At this point, a real application would probably forward the user's browser to a form that collects billing address information. Notice here that the test for <c:if> is a boolean. So the expression ${creditCard.valid} must be a boolean type. The accessor used for this property is CreditCardLocal.isValid() because the naming convention for boolean get property accessors uses "is" instead of "get".
The final tag <c:otherwise> is for the case where the card is not valid.
<c:otherwise>
<hr>
<font color="red">This number is not valid.</font>
<br>
<i>Problem</i>: ${creditCard.validityMessage}<br>
Please correct the problem and try again.
</c:otherwise>
</c:choose>
The CreditCardLocal.getValidityMessage returns a user-friendly error message that explains why the card is not valid.
The interesting and useful lesson in this example is that enterprise bean interfaces can be stored in session (or other) contexts in an application, and referenced by name in JSP EL expressions. Using this technique improves encapsulation because application logic isn't being implemented in scriptlets. It also makes JSP pages easier to create, understand, and maintain.
RUNNING THE SAMPLE CODE
Download the sample archive for these tips. The application's context root is ttfeb2004. The downloaded ear file also contains the complete source code for the sample.
You can deploy the application archive (ttfeb2004.ear) 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/ttfeb2004.ear
Replace install_dir with the directory in which you installed the war file.
You can access the application at http://localhost:8000/ttfeb2004.
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 (only part of the page is shown):
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://developers.sun.com/dispatcher.jsp?uid=6910008
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 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://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.
|