Core JSTL[c] Mastering the JSP Standard Tag Library
The javax.servlet.jsp.jstl.fmt.LocaleSupport class provides support for looking up resource bundles and mapping message keys to their corresponding localized messages. That class defines four static methods , all named getLocalizedMessage , that take the following arguments: [21] [21] See page 508 for more information about the LocaleSupport class.
Each of the LocaleSupport.getLocalizedMessage methods returns a localized string. You must specify a key, and optionally you can also specify a bundle base name or an array of parameters for compound message, or both; see "Compound Messages" on page 255 for more information about compound messages. Because the LocaleSupport class resides in the javax.servlet.jsp.jstl.fmt package, you can use its methods to retrieve localized messages from resource bundles. This section discusses how to use those methods in the context of form validation. First, we discuss how to perform form validation without using the LocaleSupport methods; subsequently, in "Validation with a Custom Action That Uses javax.servlet.jsp.jstl.fmt.LocaleSupport" on page 304, we see how to use those methods in a custom action, which substantially reduces the amount of code page authors have to write. Figure 7-8 shows an internationalized Web application localized for English and French. The top picture in Figure 7-8 shows a form that's validated by a servlet; when the form is not filled out correctly, the application displays localized error messages, as shown in the bottom picture in Figure 7-8. Figure 7-8. Form Validation ”English Version
Like the Web application discussed in "An Example of Dynamically Switching Locales" on page 278, the Web application shown in Figure 7-8 lets users switch locales by clicking on an image of a flag. The flags and the corresponding mechanism for switching locales are discussed in "An Example of Dynamically Switching Locales", so that discussion is not repeated here. Figure 7-9 shows the French version of the application, which also displays localized error messages when the form is not filled out correctly. Figure 7-9. Form Validation ”French Version
The JSP page shown in the top picture in Figure 7-9 is listed in Listing 7.24. The preceding JSP page imports set_locale.jsp , which sets the locale according to which flag was last selected and subsequently creates a localization context for a resource bundle whose base name is app , and stores it in session scope with <fmt:setBundle>. See Listing 7.20 on page 287 for a listing of set_locale.jsp . As you can see from Figure 7-9, the flags are only shown before a user submits the form. Once the form is submitted, the Web application assumes that the user has selected an appropriate locale, so the flags are not subsequently shown. The preceding JSP page implements that functionality by importing flags.jsp only if there is no firstName request parameter. See Listing 7.19 on page 287 for a listing of flags.jsp . Listing 7.24 login.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <%@ taglib uri='http://java.sun.com/jstl/core' prefix='c' %> <%@ taglib uri='http://java.sun.com/jstl/fmt' prefix='fmt'%> <%-- If a locale was specified as a request parameter, set_locale.jsp sets the locale for this session --%> <c:import url='set_locale.jsp'/> <fmt:setBundle basename='app' scope='session'/> <%-- Localize the window title --%> <title><fmt:message key='login.window-title'/></title> </head> <body> <%-- If there's no firstName request parameter ... --%> <c:if test='${param.firstName == null}'> <%-- Show the flags so the user can change locale --%> <c:import url='flags.jsp'/><br/> </c:if> <%-- process_validation_errors.jsp validates form fields--%> <c:import url='process_validation_errors.jsp'/> <%-- Import a JSP page that creates the login form --%> <c:import url='login_form.jsp'/> </body> </html> The preceding JSP page also imports two other JSP pages: process_validation_errors.jsp and login_form.jsp . The former displays localized error messages, and the latter displays the login form. Those JSP pages are discussed in the next section. Validation with JSP Pages
This section, which continues the discussion of the application shown in Figure 7-9 on page 295, shows you how to retrieve and display localized error messages in a JSP page. In "Validation with a Custom Action That Uses javax.servlet.jsp.jstl.fmt.LocaleSupport" on page 304, we discuss how to do that validation with a custom action. Listing 7.25 lists login_form.jsp , which is imported by the JSP page listed in Listing 7.24. The preceding JSP page uses a bean that stores form field values, so the user does not have to retype those values when validation errors occur. That bean is not pertinent to our discussion here, and therefore it's not listed or discussed; however, you can download it from this book's Web site; [22] also, form beans are discussed in Advanced JavaServer Pages . [23] [22] See "The Book's Web Site" on page xiv to find out more about this book's Web site. [23] David Geary, Advanced JavaServer Pages. Sun Microsystems Press, 2001. Listing 7.25 login_form.jsp
<%@ taglib uri='http://java.sun.com/jstl/core' prefix='c' %> <%@ taglib uri='http://java.sun.com/jstl/fmt' prefix='fmt'%> <jsp:useBean id='form' scope='request' class='beans.Form'> <jsp:setProperty name='form' property='*'/> </jsp:useBean> <%-- Store the name of the page that imports this page in a session attribute. That attribute is used by the validation servlet. --%> <c:set var='failurePage' value='${pageContext.request.servletPath}' scope='session'/> <%-- Store the name of the successful login page in a session attribute. That attribute is also used by the validation servlet. --%> <c:set var='successPage' value='/loginComplete.jsp' scope='session'/> <p> <form action='ValidationServlet'/> <table><tr> <td><fmt:message key='login.first-name'/>:</td> <td><input type='text' size=15 name='firstName' value='<%= form.getFirstName() %>'/></td></tr><tr> <td><fmt:message key='login.last-name'/>:</td> <td><input type='text' size=15 name='lastName' value='<%= form.getLastName() %>'/></td></tr><tr> <td><fmt:message key='login.email-address'/>:</td> <td><input type='text' size=25 name='emailAddress' value='<%= form.getEmailAddress() %>'/></td></tr> </table> <p><input type=submit value='<fmt:message key="login.login-button"/>'/> </form> There are two things to note about the JSP page listed in Listing 7.25. First, the form's action is a servlet. Second, two values are stored in session scope and are subsequently accessed by that servlet: the name of a JSP page to which the servlet forwards when form validation fails, and the name of another JSP page to which the servlet forwards when the form is filled out correctly. The former is the page that imports login_form.jsp ( login.jsp ), and the latter is /loginComplete.jsp . The JSP page /loginComplete.jsp , which displays a localized greeting after a successful login, is listed in Listing 7.26. Listing 7.26 /loginComplete.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <%@ taglib uri='http://java.sun.com/jstl/core' prefix='c' %> <%@ taglib uri='http://java.sun.com/jstl/fmt' prefix='fmt'%> <%-- Localize the window title --%> <title><fmt:message key='login.thanks.window-title'/></title> </head> <body> <fmt:message key='login.thanks'/> </body> </html> The validation servlet referenced in Listing 7.25 is listed in Listing 7.27. The validation servlet listed above overrides the doGet method and retrieves the value of a context initialization parameter named app.validation.bundle.basename , which specifies the base name of a bundle containing localized validation error messages. That base name is specified as a context initialization parameter so that you can change it without recompiling the servlet. Listing 7.27 WEB-INF/classes/ValidationServlet.java
import java.io.IOException; import java.util.Vector; import javax.servlet.*; import javax.servlet.http.*; import beans.ValidationError; public class ValidationServlet extends HttpServlet { public void doGet (HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { // Obtain a reference to the application ServletContext app = getServletContext(); // Use the application to get the value of the // initialization parameter that specifies the bundle // basename String bundleBasename = app.getInitParameter("app.validation.bundle.basename"); // Validate the form data boolean errorDetected = validate (req, bundleBasename, req.getParameter("firstName"), req.getParameter("lastName"), req.getParameter("emailAddress")); // If the form data was invalid ... if(errorDetected) { // Retrieve the failure page from a session attribute String page = (String)req.getSession(). getAttribute("failurePage"); // Forward to the failure page RequestDispatcher rd = app.getRequestDispatcher(page); rd.forward(req, res); } // If the form data was valid ... else { // Retrieve the success page from a session attribute String page = (String)req.getSession(). getAttribute("successPage"); // Forward to the success page RequestDispatcher rd = app.getRequestDispatcher(page); rd.forward(req, res); } } private boolean validate (HttpServletRequest req, String bundleBasename, String first, String last, String email){ boolean errorDetected = false; Vector errorKeys = new Vector(); // If any of the fields were not filled in... if(first.equals("") last.equals("") email.equals("")){ errorKeys.add("login.fill-in-all-fields"); errorDetected = true; } // If email address is invalid... if(email.indexOf("@") == -1 (!email.endsWith(".com") && !email.endsWith(".edu"))) { errorKeys.add("login.bad-email-address"); errorDetected = true; } // If the form parameters were invalid... if(errorDetected) { // Add message indicating invalid parameters errorKeys.add(0, "login.error-detected"); // Store the validation error in a request attribute req.setAttribute("validateError", new ValidationError(bundleBasename, errorKeys)); } return errorDetected; } } The preceding servlet passes the resource bundle base name, along with a reference to the HTTP request and the values filled out in the form, to a validate method. The validate method checks to see whether the form was filled out correctly; if not, it creates an instance of ValidationError , whose constructor is passed the bundle base name and an array of error message keys specified by the validate method. That validation error is subsequently stored in a request attribute, which is accessed by process_validation_errors.jsp . After the validate method returns, the servlet listed above forwards to one of two JSP pages, depending on whether the validation succeeded or failed. The names of those JSP pages are retrieved from session attributes that are set in login_form.jsp , which is listed in Listing 7.25 on page 297. Listing 7.28 lists the application's deployment descriptor. Listing 7.28 WEB-INF/web.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/j2ee/dtds/web-app_2.3.dtd"> <web-app> <context-param> <param-name> javax.servlet.jsp.jstl.fmt.locale </param-name> <param-value> en-US </param-value> </context-param> <context-param> <param-name> app.validation.bundle.basename </param-name> <param-value> validationErrors </param-value> </context-param> <servlet> <servlet-name>ValidationServlet</servlet-name> <servlet-class>ValidationServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>ValidationServlet</servlet-name> <url-pattern>/ValidationServlet</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>login.jsp</welcome-file> </welcome-file-list> </web-app> The deployment descriptor listed in Listing 7.28 defines a context initialization parameter named javax.servlet.jsp.jstl.fmt.locale , which corresponds to the FMT_LOCALE configuration setting. That context initialization parameter's value is set to en-US , so when the login page is first displayed, it will be localized for U.S. English. See "An Overview of the I18N Actions" on page 264 for more information about the FMT_LOCALE configuration setting. The preceding deployment descriptor also defines a context initialization parameter named app.validation.bundle.basename , which defines the resource bundle base name for validation error messages. That context initialization parameter is used by the validation servlet listed in Listing 7.27. The preceding deployment descriptor also defines a servlet mapping for the validation servlet listed in Listing 7.27 and specifies login.jsp as a welcome file so that the JSP page is displayed when the application is accessed in the browser. The ValidationError class is listed in Listing 7.29. Listing 7.29 WEB-INF/classes/beans/ValidationError.java
package beans; import java.util.Vector; // This class is immutable public class ValidationError { private final String bundleBasename; private final Vector errorKeys; public ValidationError(String bundleBasename, Vector errorKeys) { this.errorKeys = errorKeys; this.bundleBasename = bundleBasename; } public Vector getErrorKeys() { return errorKeys; } public String getBundleBasename() { return bundleBasename; } } ValidationError instances are simple immutable beans that store a bundle base name and a set of error keys. That base name and those error keys are accessed by process_validation_errors.jsp , which is listed in Listing 7.30. The preceding JSP page checks to see if a validation error has been stored in a request attribute. If that request attribute does not exist, the JSP page does nothing. If one or more errors were detected by the validation servlet listed in Listing 7.27 on page 299, then the preceding JSP page uses a scriptlet to retrieve a bundle base name and a set of error keys from the validation error stored in request scope. Subsequently, the preceding JSP page uses <fmt:bundle> to establish a localization context for its nested <fmt:message> actions. The JSP page then iterates over the error keys, extracting localized messages from the (resource bundle stored in) the localization context created by the <fmt:bundle> action. As you can see from the preceding listing, displaying localized error messages can be fairly complicated. You can reduce that complexity to one line of code by implementing a custom action that displays those error messages. "Validation with a Custom Action That Uses javax.servlet.jsp.jstl.fmt.LocaleSupport" on page 304 shows you how to implement such a custom action, which uses the avax.servlet.jsp.jstl.fmt.LocaleSupport class to extract localized validation error messages from a resource bundle. Before we discuss that custom action, let's look at the English and French properties files that contain the localized validation errors, as listed in Listing 7.31 and Listing 7.32, respectively. Listing 7.30 process_validation_errors.jsp
<%@ taglib uri='http://java.sun.com/jstl/core' prefix='c' %> <%@ taglib uri='http://java.sun.com/jstl/fmt' prefix='fmt'%> <%-- If the request attribute "validateError" exists --%> <c:if test='${requestScope.validateError != null}'> <%-- The following scriptlet performs these steps: 1. Obtain a reference to the validation error. 2. Store the error keys in page scope. 3. Store the error's bundle base name in page scope. --%> <% beans.ValidationError error = (beans.ValidationError) request.getAttribute("validateError"); pageContext.setAttribute("errors", error.getErrorKeys()); pageContext.setAttribute("bundleBasename", error.getBundleBasename()); %> <%-- Set the bundle base name for all <fmt:message> actions within the body of the <fmt:bundle> action --%> <fmt:bundle basename='${bundleBasename}'> <%-- For each error key...--%> <c:forEach var='key' items='${errors}' varStatus='status'> <c:choose> <%-- If it's the first key...--%> <c:when test='${status.first}'> <font color='red'> <fmt:message key='${key}'/> <ul> </c:when> <%-- If it's the last key ...--%> <c:when test='${status.last}'> <li><fmt:message key='${key}'/></li> </ul> </font> </c:when> <%-- If it's not the first or last key ...--%> <c:otherwise> <li><fmt:message key='${key}'/></li> </c:otherwise> </c:choose> </c:forEach> </fmt:bundle> </c:if> Listing 7.31 WEB-INF/classes/validationErrors_en.properties
# validation error messages -- English Version login.error-detected=Sorry, you did not fill out the form \ correctly. Please correct the following errors: login.fill-in-all-fields=You must fill in all fields login.bad-email-address=Your email address must contain @ \ and end in .com or .edu Listing 7.32 WEB-INF/classes/validationErrors_fr.properties
# validation error messages -- French Version login.error-detected=Desolee, vous ne remplissez pas cette forme.\ SVP corrigez les erreurs qui suivante. login.fill-in-all-fields=Vous devez remplir toute cette forme. login.bad-email-address=Votre address de courier electronique \ devoir contenir @ and la fin avec .com ou .edu Validation with a Custom Action That Uses javax.servlet.jsp.jstl.fmt.LocaleSupport
Instead of retrieving localized error messages from a resource bundle and subsequently displaying them with a JSP page, as is the case for the JSP page listed in Listing 7.30 on page 303, we can encapsulate that functionality in a custom action. Listing 7.33 lists a revision of the login.jsp JSP page listed in Listing 7.24 on page 296. That revised JSP page uses a custom action to display validation error messages. Implementing the <core-jstl:showValidateError> custom action is a two-step process: create a tag library descriptor in WEB-INF , and implement the tag handler class in WEB-INF/classes/tags . The tag library descriptor is listed in Listing 7.34. Listing 7.33 login.jsp (revised)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <%@ taglib uri='http://java.sun.com/jstl/core' prefix='c' %> <%@ taglib uri='http://java.sun.com/jstl/fmt' prefix='fmt'%> <%@ taglib uri='WEB-INF/core-jstl.tld' prefix='core-jstl' %> <%-- If a locale was specified as a request parameter, set_locale.jsp sets the locale for this session --%> <c:import url='set_locale.jsp'/> <fmt:setBundle basename='app' scope='session'/> <%-- Localize the window title --%> <title><fmt:message key='login.window-title'/></title> </head> <body> <%-- If there's no firstName request parameter ... --%> <c:if test='${param.firstName == null}'> <%-- Show the flags so the user can change locale --%> <c:import url='flags.jsp'/><br/> </c:if> <%-- This custom action takes the place of process_validation_errors.jsp from the previous example --%> <core-jstl:showValidateError/> <%-- Import a JSP page that creates the login form --%> <c:import url='login_form.jsp'/> </body> </html> The tag library descriptor listed above describes a tag library that contains the showValidateError tag with the <taglib> directive, specifying the version of the library, the JSP version required for the library, and a short name for the library. The tag library descriptor also describes the tag itself with the <tag> directive, specifying the tag name, Java class, and the type of body content the tag requires. The tag handler for the <core-jstl:showValidateError> custom action is listed in Listing 7.35. The tag handler listed above implements the same functionality as the JSP page listed in Listing 7.30 on page 303. That tag handler overrides the doEndTag method (because the tag has no body, the tag handler could have overridden doStartTag with identical results) and uses one of the getLocalizedMessage methods from the LocaleSupport class to extract localized messages from the English and French validation error properties files listed in Listing 7.31 on page 304 and Listing 7.32 on page 304, respectively. Because JSTL exposes the LocaleSupport class by placing it in the javax.servlet.jsp.jstl.fmt package, custom actions like the one in the previous listing can extract localized messages in exactly the same manner as the JSTL <fmt:message> action. Listing 7.34 WEB-INF/core-jstl.tld
<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_2.dtd"> <taglib> <tlib-version>1.0</tlib-version> <jsp-version>1.2</jsp-version> <short-name>Core JSTL Validation Example Tag</short-name> <tag> <name>showValidateError</name> <tag-class>tags.ShowValidateErrorTag</tag-class> <body-content>none</body-content> </tag> </taglib> Listing 7.35 WEB-INF/classes/tags/ShowValidateErrorTag.java
package tags; import java.util.Vector; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.TagSupport; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.jstl.fmt.LocaleSupport; import beans.ValidationError; public class ShowValidateErrorTag extends TagSupport { public int doEndTag () throws JspException { // Retrieve the validate-error scoped variable, which // is an instance of ValidationError ValidationError err = (ValidationError)pageContext.getRequest(). getAttribute("validateError"); if(err != null) { // Get error keys from the ValidationError Vector errorKeys = err.getErrorKeys(); // Get the size of the errorKeys vector int numberOfKeys = errorKeys.size(); // Get the bundle basename from the ValidationError String bundleBasename = err.getBundleBasename(); // Write output to this JspWriter JspWriter out = pageContext.getOut(); // For each error key ... for(int i=0; i < errorKeys.size(); ++i) { try { // Retrieve the localized message String msg = LocaleSupport.getLocalizedMessage( pageContext, (String)errorKeys.get(i), bundleBasename); // If it's the first key... if(i == 0) { out.print("<font color='red'>"); out.print(msg + "<br/><ul>"); } else { // All but the first key... out.print("<li>" + msg + "</li>"); } // If it's the last key... if(i == numberOfKeys-1) { pageContext.getOut().print("</font></ul>"); } } catch(java.io.IOException ex) { throw new JspException(ex.getMessage()); } } } return EVAL_PAGE; } } |