Sun Java Solaris Communities My SDN Account Join SDN
 
Technical Articles and Tips

JavaServer Pages (JSP) Error Pages and Preventing Repeated Operations

 
View this issue as simple text January 14, 2003    

WELCOME to the Java 2, Enterprise Edition (J2EE) Tech Tips, January 14, 2003. 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:

.JavaServer Pages (JSP) Error Pages
.Preventing Repeated Operations

These tips were developed using Java 2 SDK, Standard Edition, v 1.4 and Java 2 SDK, Enterprise Edition, v 1.3.1 (Reference Implementation).

This issue of the J2EE Tech Tips is written by Mark Johnson, president of elucify technical communications, and co-author of Designing Enterprise Applications with the Java 2, Enterprise Edition, 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 ttjan2003, 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: http://developer.java.sun.com/berkeley_license.html

Pixel

JavaServer Pages (JSP) Error Pages

Few things make an application look less polished and professional than a server's default exception page. Even the most well-designed page usually shows the stack trace and exception name, and makes your application look broken. Your application might indeed have a bug, but that's no reason for it to look bad, as well.

The J2EE platform offers a great deal of control over how to format error information when a Web component throws an unexpected exception. Ideally, every Web component properly catches and handles all application and system exceptions that can occur. But in the real world, accidents happen. The JSP error page lets you catch, present, and report exceptions gracefully, instead of presenting the user with a technical, and possibly confusing, stack trace.

Defining An HTML Error Page

Web components such as servlets and JSP pages may throw exceptions to indicate error conditions. Usually, a Web component catches and handles its own exceptions, as well as exceptions from the other components it uses. When a Web component receives or generates an exception that it doesn't catch, the exception is passed to the Web container (part of the J2EE server). If an error page is defined for that exception, the request is directed to the error page's URL.

The Web application deployment descriptor uses the <error-page> tag to define Web components that handle errors. The simplest way to handle an error is to define an HTML page for the exception, and associate that HTML page with the exception it reports. This example, from this month's sample code, shows how to define an HTML page that reports a NullPointerException:

  <!-- Catch a system error using an HTML page -->
  <error-page>
    <exception-type>java.lang.NullPointerException
    </exception-type>
    <location>/NPEerror.html</location>
  </error-page>

This deployment descriptor entry means that whenever a Web component throws a NullPointerException, the Web container serves the document NPerror.html, which simply reports the error. The page can be designed to have the same look and feel as the rest of your application, so the user isn't confused or annoyed by a sudden change in the interface.

The Web application isn't limited to system exceptions such as NullPointerException, though. The next example in the sample Web application deployment descriptor defines an error page for a developer-defined application exception:

   <!-- Catch a system error using an HTML page -->
   <error-page>
     <exception-type>
     com.elucify.tips.jan2003.AppException1a
     </exception-type>
     <location>/AE1Aerror.html</location>
   </error-page>

Note that in the Web deployment descriptor, you have to provide a fully-specified class name for the exception. (In the sample code, the application exception classes are all subclasses of class "ApplicationException1", which extends java.lang.Exception.) The source code for ApplicationException1a used above looks like this:

    package com.elucify.tips.jan2003;
    public class AppException1a extends AppException1 {
        public AppException1a() {
            super("AppException1a");
        }
    }

A simple HTML page is fine for simple errors, but most of the time, you want to provide information about what the problem actually was. The way to do that is to report the error using a JSP page.

Using JSP Pages As Error Pages

JSP pages, being Web components, allow you to report not only that an error occurred, but also what went wrong. The deployment descriptor definition for a JSP error page looks like this:

   <!-- Report an error using a JSP page -->
   <error-page>
     <exception-type>
     com.elucify.tips.jan2003.AppException1b
     </exception-type>
      <location>/AE1Berror.jsp</location>
  </error-page>

The definition of the page is identical to the definition for an HTML error page, except that the <location> defines a JSP page instead. The JSP page that formats the error uses the <% @page %> shown below:

    <%@page isErrorPage="true" %>
    <HTML>
    <HEAD>
    <TITLE>Application Error 1b</TITLE>
    ...

The string isErrorPage="true" causes the Web container to define a new implicit variable, "exception", that the JSP page can use in scriptlets or in custom tags. For example, the error page for the current example uses the "exception" object to report the name of the class that caused the exception:

    <%= exception.getClass().getName() %>

Servlets and custom tags may access the exception object by calling PageContext.getException(). (You'll learn more about directing errors to servlets later in this tip.)

Letting A Page Define Its Error Page

Sometimes you want to take complete control of a page's exception handling. The errorPage attribute of the @page directive provides a URL to a Web component that handles the exception reporting for an individual page. If a JSP page defines errorPage, all exceptions thrown are directed to that page, even if web.xml specifically defines an error page for that page. In the example, the JSP page ThrowAppException1c.jsp defines the file "AE1Cerror.jsp" as its error page, as shown below. Keep in mind that the page shown here throws the exception. The page that reports the exception is AE1Cerror.html. (For the purposes of formatting this tip, the <%@page ...> tag below is shown on two lines. For JSP versions before 2.0, it needs to be on one line.)

    <%@page import="com.elucify.tips.jan2003.AppException1c" 
        errorPage="AE1Cerror.html" %>
    <%
            if (true) {
              throw new AppException1c();
            }
    %>

The @page attribute overrides all other exception handling except explicit try/catch clauses in the source page.

Reporting Exception Superclasses

The J2EE platform documentation defines a large number of exceptions. Writing a Web page to handle each one would quickly become tedious. Fortunately, the J2EE platform allows you define a single error page for a superclass of a group of extensions. When the Web container receives an exception, it looks for error page for that specific exception. If the Web container doesn't find that error page, it looks for an error page for the exception's superclass. It continues looking up the inheritance tree until it finds an error page, or until it reaches Throwable.

In the the sample code, the Web deployment descriptor doesn't define an error page for ApplicationException1d, but it does define an error page for ApplicationException1:

    <!-- Note absence of an error page -->
    <!-- for Application Errors 1c and 1d -->

    <!-- Catch a whole class of errors -->
    <error-page>
      <exception-type>
      com.elucify.tips.jan2003.AppException1
      </exception-type>
      <location>/AE1error.jsp</location>
    </error-page>

The code above defines the error page /AE1error.jsp for any exception of type AppException1. This includes all subclasses of AppException1 (such as AppException1d) that do not define an error page elsewhere. The error page here simply reports the exception and exception type that occurred:

    <%@page isErrorPage="true"%>
    <HTML>
    ...
    An Application Error 1 has occurred.<br>
    The error class is
    
    <%= exception.getMessage(); %>
    ...

Using Servlets As Error Pages

For maximum flexibility, even a servlet can serve as an error page. A servlet error page has the same syntax as a JSP error page. The <location> is just the servlet's <servlet-path>, as defined in web.xml:

   <!-- Catch errors by error code, -->
   <!-- redirecting to a servlet -->
   <error-page>
     <error-code>404</error-code>
     <location>/errorServlet</location>
   </error-page>

Notice above that instead of <exception-type>, this tag defines <error-code>. The Web can identify system errors in the Web tier either by Java exception type, as you've seen before, or by HTTP error code. The example above defines servlet /errorServlet as the exception handler for 404 ("File Not Found") errors. Both the <exception-type> and the <error-code> tags can be used with any Web resource in the application.

When the Web container receives or generates an exception or system error, it initializes several variables as request attributes. The sample code exception handler servlet ErrorServlet reports the values of these attributes:

    public class ErrorServlet extends HttpServlet {
        ...
        protected static String[] vars = {
                "javax.servlet.error.status_code",
                "javax.servlet.error.exception_type",
                "javax.servlet.error.message",
                "javax.servlet.error.exception",
                "javax.servlet.error.request_uri"
            };

        // Handle post request
        public void doPost(HttpServletRequest req, 
            HttpServletResponse res)
            throws IOException, ServletException {
            ...
            // Print vars
            for (int i = 0; i < vars.length; i++) {
                out.println(
                    "<TR><TD>" + vars[i] + "</TD><TD>" +
                    req.getAttribute(vars[i]) + 
                    "</TD></TR>");
            }
            ...
        }
    }

The meanings of these attributes are defined in the Servlet 2.3 specification. If the source of the exception was a JSP page, the Web container also stores the exception as a request attribute called "javax.servlet.jsp.jspException" (ErrorServlet reports these, too.)

The ErrorServlet shown here might be better implemented as a JSP page that uses custom tags instead of scriptlets. A more common use of an error servlet is as an "error controller". An error controller servlet could examine the exception or error information it receives, log the information, send an email or page to maintenance staff, and then forward the request to a logical place in the application.

Using Error Pages From Servlets

So far, all of the examples shown demonstrate how to use error pages when the source of the exception is a JSP page. However, servlets can use existing error pages to format their exceptions and errors, as well. The trick is to set the request attribute "javax.servlet.jsp.jspException" to the exception you want reported, and then forward the HTTP request to the URL for the error page. The sample code example below forwards an exception from one servlet (called ForwardingErrorServlet) to the ErrorServlet from the previous example:

    public class ForwardingErrorServlet 
        extends HttpServlet {
           ...

        // Handle post request
        public void doPost(HttpServletRequest req, 
            HttpServletResponse res)
            throws IOException, ServletException {

            // Create an exception to forward 
            // to an error page
            try {
                Object o = null;
                Class c = o.getClass();
            } catch (NullPointerException npe) {
                req.setAttribute(
                    "javax.servlet.jsp.jspException",
                         npe);
                RequestDispatcher rd = 
                    req.getRequestDispatcher(
                        "/errorServlet");
                rd.forward(req, res);
            }
        }
    }

This example could just as easily forward to an HTML or JSP page. You need to be careful when forwarding, though. If you open a PrintWriter in a servlet and then try to forward, the forward will fail.

There are several key points to remember when using the JSP error page. The Web container always uses the most specific error page defined for the page that caused the error. The error page may be any Web component (not just HTML or JSP pages). Consider using a servlet to log and/or report errors, and then forward to a JSP page that formats the exceptions or errors.

Pixel
Pixel

Preventing Repeated Operations

Many online operations, especially those involving money, must always happen either zero or one times. It's common for naive users to click things in the user interface twice. For example, an impatient user might click a Purchase button repeatedly. This does not mean that the user wants to make the same purchase multiple times. And consider the case where a user opens a new browser window. To which window should the session belong?

This tip presents a simple strategy for ensuring that once the server receives a POST request from an HTML form, it processes that request exactly once. The key is to generate a token, that is, a unique symbol that can be embedded as a hidden input in the form being processed. The token may be a string or a simple random number (as in the case shown in the sample code), it may also include information such as the originating URL and the name of the form. The servlet puts a copy of the token in a hidden field in the form, and keeps a copy on the server. Later, when the user POSTs the form to the server, the servlet tries to look up the token. If it has a copy of the token, it deletes the server-side copy of the token and performs the operation. If the servlet can't find the token, then some other invocation of the servlet must already have serviced that request, and the servlet produces an error message.

The sample code, OneShotServlet.java, demonstrates this technique. Its doGet method generates a token (a random number), saves the token as a session attribute, and serves a simple form.

    public void doGet(HttpServletRequest req, 
        HttpServletResponse res)
            throws IOException, ServletException {
        PrintWriter pw = res.getWriter();
        res.setContentType("text/html");
        
        int token = _random.nextInt();
        int value = _random.nextInt(1000);
        
        // Store token in session scope
        HttpSession session = req.getSession();
        String stoken = String.valueOf(token);
        session.setAttribute(
            stoken, String.valueOf(value));

        // Create form, putting token in hidden field
        pw.println(
            "<HTML><HEAD><TITLE>One-Shot Servlet</TITLE></HEAD>");
        pw.println("<BODY BACKGROUND=white>");

        pw.println(
            "Please type the following number into the box and " +
                   "click <i>Submit</i>: " + value);
        pw.println(
            "<form action=\"oneShotServlet\" " +
                   "method=\"post\">");
        pw.println(
            "<input type=\"hidden\" name=\"_token\" value=\"" +
                   stoken + "\">");
        pw.println("<B>Input:</B>");
        pw.println(
            "<input type=text name=\"num\" width=\"40\"><br>");
        pw.println("<input type=submit value=Submit>");
        pw.println("</form></BODY></HTML>");
    }

In the form, the servlet includes a hidden field that contains the token string. The value of the session attribute is another random number. The form gives the user a random number between 0 and 999 (the "value") and asks the user to copy the number into a text input element in an HTML form. When the user clicks Submit, the form calls back to the servlet's doPost method, shown below:

    // Handle post request
    public void doPost(HttpServletRequest req, 
        HttpServletResponse res)
            throws IOException, ServletException {

        res.setContentType("text/html");
        PrintWriter out = res.getWriter();
                
        out.println(
            "<HTML><HEAD><title>Demonstrate One Shot Servlet" +
                    "</TITLE></HEAD>");
        out.println("<BODY>");

        // Get the token, if it exists
        String stoken = req.getParameter("_token");
        HttpSession session = req.getSession();
        String stored_value = null;
        String value = req.getParameter("num");

        // Remove stored token from session
        synchronized (this) {
            stored_value = 
                (String)session.getAttribute(stoken);

            // Remove token from session 
            // so others can't use it,
            // then get the number posted by the user
            if (stored_value != null) {
                session.removeAttribute(stoken);
            }
        }

        // If the token is not there, 
        // it has already been "consumed"
        if (stored_value == null) {
            out.println(
                "Sorry, you can't use that form more than once.");
        } else {

            if (stored_value.equals(value)) {
                out.println(
                    "You copied the number correctly");
            } else {
                out.println(
                    "That wasn't the original number!");
            }

            out.println(
                "<p>Now hit the BACK button on your browser and " +
                        "try to re-post the form");
        }

        out.println("</BODY></HTML>");

    }

    }

The doPost method gets the token from the hidden form field (called "_token") and gets the value provided by the user ("num"). It then checks to see if the token named in the hidden form variable still exists as a session attribute. If it does exist, the servlet immediately removes it, and continues with the business operation (comparing the original number with the number the user typed). If the token does not exist, the servlet responds with an error message. Notice that the combined operation of finding and removing the token from session state is synchronized. This avoids race conditions between multiple clients using the same session.

Be aware that this servlet might not operate properly if the servlet is marked "distributed", or if the servlet implements SingleThreadModel. The "synchronized" block only prevents the code from being executed by multiple threads within the same JVM. Distributed servlets can run in multiple JVMs, so in some cases, race conditions can occur that would cause the servlet to operate incorrectly. Implementing SingleThreadModel is discouraged, for reasons explained in the Enterprise Java BluePrints.

This example uses a single servlet to demonstrate the concept in a single source file. A real implementation would probably use JSPs and custom tags, because that solution would be much more maintainable, loggable, and reusable. Keeping the tokens in session scope works well for small applications with low load, that only run on one machine. If more than one machine is involved, this solution requires distributed session state. Another option would be to store the keys as entity beans with exclusive locking.

Pixel
Pixel

Running the sample code

Download the sample archive for these tips. The application's context root is ttjan2003. The downloaded EAR file also contains the complete source code for the sample.

You can deploy the application archive (ttjan2003.ear) in the J2EE Reference Implementation using the deploytool program:

    $J2EE_HOME/deploytool -deploy ttjan2003.ear localhost

Replace localhost with the name of the host on which the server is installed. For a standard installation on a single machine, the hostname typically (and literally) is localhost. You can access the application at http://localhost:8000/ttjan2003.

For a J2EE-compliant implementation other than the Reference Implementation, use your J2EE product's deployment tools to deploy the application on your platform.

See the index.html welcome file for instructions on running the application.

Pixel
Pixel

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://developer.java.sun.com/berkeley_license.html


Comments? Send your feedback on the Enterprise Java Technologies Tech Tips to: jdc-webmaster@sun.com

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://developer.java.sun.com/developer/EJTechTips/index.html


Copyright 2003 Sun Microsystems, Inc. All rights reserved. 4150 Network Circle, Santa Clara, CA 95054 USA.

This document is protected by copyright.

Sun, Sun Microsystems, Java, Java Developer Connection, JavaServer Pages, JSP, J2SE, J2EE, and J2ME are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.