Web User Interface Design

   

In this section, we show you how to use features such as popups, applets, and file upload dialogs in your web pages. We hope that future versions of JSF will include ready-made components to achieve these tasks. Here we show you how to implement and configure the required components.

How do I support file uploads?

The users of your application may want to upload files, such as photos or documents see Figures 12-1 and 12-2.

Figure 12-1. Uploading an Image File

graphics/12fig01.jpg


Figure 12-2. Uploaded Image

graphics/12fig02.jpg


Unfortunately, there is no standard file upload component in JSF 1.0. However, it turns out that it is fairly straightforward to implement one. The hard work has already been done by the folks at the Apache organization in the Commons file upload library see http://jakarta.apache.org/commons/fileupload. We will show you how to incorporate the library into a JSF component.

A file upload is different from all other form requests. When the form data (including the uploaded file) is sent from the client to the server, it is encoded with the "multipart/form-data" encoding instead of the usual "application/x-www-form-urlencoded" encoding. Unfortunately, JSF does not handle this encoding at all. To overcome this issue, we install a servlet filter that intercepts a file upload and turns uploaded files into request attributes and all other form data into request parameters. (We use a utility method in the Commons file upload library for the dirty work of decoding a multipart/form-data request.) The JSF application then processes the request parameters, blissfully unaware that they were not URL encoded. The decode method of the file upload component either places the uploaded data into a disk file or stores it in a value reference.

The code for the servlet filter is in Listing 12-1.

NOTE

graphics/note_icon.gif

You can find general information about servlet filters at http://java.sun.com/products/servlet/Filters.html.


You need to install the filte r in the web-inf.xml file, using this syntax:

 

 <filter>    <filter-name>Upload Filter</filter-name>    <filter-class>com.corejsf.UploadFilter</filter-class>    <init-param>       <param-name>com.corejsf.UploadFilter.sizeThreshold</param-name>       <param-value>1024</param-value>    </init-param> </filter> <filter-mapping>    <filter-name>Upload Filter</filter-name>    <url-pattern>/upload/*</url-pattern> </filter-mapping> 

The filter uses the sizeThreshold initialization parameter to configure the file upload object: files > 1024 bytes are saved to a temporary disk location rather than being held in memory. Our filter supports two additional initialization parameters: com.corejsf.UploadFilter.sizeMax (the maximum permitted size of an uploaded file) and com.corejsf.UploadFilter.repositoryPath (the temporary location for uploaded files before they are moved to a permanent place). The filter simply sets the corresponding properties of the DiskFileUpload object of the Commons file-upload library.

The filter mapping restricts the filter to URLs that start with /upload/. Thus, we avoid unnecessary filtering of other requests.

Figure 12-3 shows the directory structure of the sample application.

Listing 12-1. fileupload/WEB-INF/classes/com/corejsf/UploadFilter.java
   1. package com.corejsf;   2.   3. import java.io.IOException;   4. import java.util.Collections;   5. import java.util.Enumeration;   6. import java.util.HashMap;   7. import java.util.List;   8. import java.util.Map;   9. import javax.servlet.Filter;  10. import javax.servlet.FilterChain;  11. import javax.servlet.FilterConfig;  12. import javax.servlet.ServletException;  13. import javax.servlet.ServletRequest;  14. import javax.servlet.ServletResponse;  15. import javax.servlet.http.HttpServletRequest;  16. import javax.servlet.http.HttpServletRequestWrapper;  17. import org.apache.commons.fileupload.DiskFileUpload;  18. import org.apache.commons.fileupload.FileItem;  19. import org.apache.commons.fileupload.FileUpload;  20. import org.apache.commons.fileupload.FileUploadException;  21.  22.  23. public class UploadFilter implements Filter {  24.    private int sizeThreshold = -1;  25.    private long sizeMax = -1;  26.    private String repositoryPath;  27.  28.    public void init(FilterConfig config) throws ServletException {  29.       repositoryPath = config.getInitParameter(  30.          "com.corejsf.UploadFilter.repositoryPath");  31.       try {  32.          String paramValue = config.getInitParameter(  33.             "com.corejsf.UploadFilter.sizeThreshold");  34.          if (paramValue != null)  35.             sizeThreshold = Integer.parseInt(paramValue);  36.          paramValue = config.getInitParameter(  37.             "com.corejsf.UploadFilter.sizeMax");  38.          if (paramValue != null)  39.             sizeMax = Long.parseLong(paramValue);  40.       }  41.       catch (NumberFormatException ex) {  42.          ServletException servletEx = new ServletException();  43.          servletEx.initCause(ex);  44.          throw servletEx;  45.       }  46.    }  47.  48.     public void destroy() {  49.    }  50.  51.     public void doFilter(ServletRequest request,  52.        ServletResponse response, FilterChain chain)  53.        throws IOException, ServletException {  54.  55.        if (!(request instanceof HttpServletRequest)) {  56.           chain.doFilter(request, response);  57.           return;  58.        }  59.  60.       HttpServletRequest httpRequest = (HttpServletRequest) request;  61.  62.       boolean isMultipartContent = FileUpload.isMultipartContent(httpRequest);  63.       if (!isMultipartContent) {  64.          chain.doFilter(request, response);  65.          return;  66.       }  67.  68.       DiskFileUpload upload = new DiskFileUpload();  69.       if (repositoryPath != null)  70.          upload.setRepositoryPath(repositoryPath);  71.  72.       try {  73.          List list = upload.parseRequest(httpRequest);  74.          final Map map = new HashMap();  75.          for (int i = 0; i < list.size(); i ++) {  76.             FileItem item = (FileItem) list.get(i);  77.             String str = item.getString();  78.             if (item.isFormField())  79.                map.put(item.getFieldName(), new String[] { str });  80.             else  81.                httpRequest.setAttribute(item.getFieldName(), item);  82.          }  83.  84.          chain.doFilter(new  85.             HttpServletRequestWrapper(httpRequest) {  86.                public Map getParameterMap() {  87.                   return map;  88.                }  89.                // busywork follows ... should have been part of the wrapper  90.                public String[] getParameterValues(String name) {  91.                   Map map = getParameterMap();  92.                   return (String[]) map.get(name);  93.                }  94.                public String getParameter(String name) {  95.                   String[] params = getParameterValues(name);  96.                   if (params == null) return null;  97.                   return params[0];  98.                }  99.                public Enumeration getParameterNames() { 100.                   Map map = getParameterMap(); 101.                   return Collections.enumeration(map.keySet()); 102.                } 103.             }, response); 104.       } catch (FileUploadException ex) { 105.          ServletException servletEx = new ServletException(); 106.          servletEx.initCause(ex); 107.          throw servletEx; 108.       } 109.    } 110. } 

Figure 12-3. The Directory Structure of the File Upload Application

graphics/12fig03.jpg


Now let's move on to the upload component. It supports two attributes: value and target. The value simply denotes a value reference into which the file contents are stored. This makes sense for short files. More commonly, you will use the target attribute to specify the target location of the file.

The implementation of the FileUploadRenderer class in Listing 12-2 is straightforward. The encodeBegin method renders the HTML element. The decode method retrieves the file items that the servlet filter placed into the request attributes and disposes of them as directed by the tag attributes. The target attribute denotes a file relative to the server directory containing the root of the web application.

The associated tag handler class, in Listing 12-3, is as dull as ever.

Finally, when using the file upload tag, you need to remember to set the form encoding to "multipart/form-data" see Listing 12-4.

Listing 12-2. fileupload/WEB-INF/classes/com/corejsf/UploadRenderer.java
  1. package com.corejsf;  2.  3. import java.io.File;  4. import java.io.IOException;  5. import java.io.InputStream;  6. import java.io.UnsupportedEncodingException;  7. import javax.faces.FacesException;  8. import javax.faces.component.EditableValueHolder;  9. import javax.faces.component.UIComponent; 10. import javax.faces.context.ExternalContext; 11. import javax.faces.context.FacesContext; 12. import javax.faces.context.ResponseWriter; 13. import javax.faces.el.ValueBinding; 14. import javax.faces.render.Renderer; 15. import javax.servlet.ServletContext; 16. import javax.servlet.http.HttpServletRequest; 17. import org.apache.commons.fileupload.FileItem; 18. 19. public class UploadRenderer extends Renderer { 20.    public void encodeBegin(FacesContext context, UIComponent component) 21.       throws IOException { 22.       if (!component.isRendered()) return; 23.       ResponseWriter writer = context.getResponseWriter(); 24. 25.       String clientId = component.getClientId(context); 26. 27.       writer.startElement("input", component); 28.       writer.writeAttribute("type", "file", "type"); 29.       writer.writeAttribute("name", clientId, "clientId"); 30.       writer.endElement("input"); 31.       writer.flush(); 32.    } 33. 34.    public void decode(FacesContext context, UIComponent component) { 35.       ExternalContext external = context.getExternalContext(); 36.       HttpServletRequest request = (HttpServletRequest) external.getRequest(); 37.       String clientId = component.getClientId(context); 38.       FileItem item = (FileItem) request.getAttribute(clientId); 39. 40.       Object newValue; 41.       ValueBinding binding = component.getValueBinding("value"); 42.       if (binding != null) { 43.          if (binding.getType(context) == byte[].class) { 44.             newValue = item.get(); 45.          } 46.          if (binding.getType(context) == InputStream.class) { 47.             try { 48.                newValue = item.getInputStream(); 49.             } catch (IOException ex) { 50.                throw new FacesException(ex); 51.             } 52.          } 53.          else { 54.             String encoding = request.getCharacterEncoding(); 55.             if (encoding != null) 56.                try { 57.                   newValue = item.getString(encoding); 58.                } catch (UnsupportedEncodingException ex) { 59.                   newValue = item.getString(); 60.                } 61.             else 62.                newValue = item.getString(); 63.          } 64.          ((EditableValueHolder) component).setSubmittedValue(newValue); 65.       } 66. 67.       Object target; 68.       binding = component.getValueBinding("target"); 69.       if (binding != null) target = binding.getValue(context); 70.       else target = component.getAttributes().get("target"); 71. 72.       if (target != null) { 73.          File file; 74.          if (target instanceof File) 75.             file = (File) target; 76.          else { 77.             ServletContext servletContext 78.                = (ServletContext) external.getContext(); 79.             file = new File(servletContext.getRealPath("/"), 80.                target.toString()); 81.          } 82. 83.          try { // ugh--write is declared with "throws Exception" 84.             item.write(file); 85.          } catch (Exception ex) { 86.             throw new FacesException(ex); 87.          } 88.       } 89.    } 90. } 

Listing 12-3. fileupload/WEB-INF/classes/com/corejsf/UploadTag.java
  1. package com.corejsf;  2.  3. import javax.faces.component.UIComponent;  4. import javax.faces.webapp.UIComponentTag;  5.  6.  7. public class UploadTag extends UIComponentTag {  8.    private String value;  9.    private String target; 10. 11.    public void setValue(String newValue) { value = newValue; } 12.    public void setTarget(String newValue) { target = newValue; } 13. 14.    public void setProperties(UIComponent component) { 15.       super.setProperties(component); 16.       com.corejsf.util.Tags.setString(component, "target", target); 17.       com.corejsf.util.Tags.setString(component, "value", value); 18.    } 19. 20.    public void release() { 21.       super.release(); 22.       value = null; 23.       target = null; 24.    } 25. 26.    public String getRendererType() { return "com.corejsf.Upload"; } 27.    public String getComponentType() { return "com.corejsf.Upload"; } 28. } 

Listing 12-4. fileupload/upload/uploadImage.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.    <%@ taglib uri="http://java.sun.com/upload" prefix="corejsf" %>  5.  6.    <f:view>  7.       <head>  8.          <title>A file upload test</title>  9.       </head> 10.       <body> 11.          <h:form enctype="multipart/form-data"> 12.             Upload a photo of yourself: 13.             <corejsf:upload target="upload/#{user.id}_image.jpg"/> 14.             <h:commandButton value="Submit" action="submit"/> 15.          </h:form> 16.       </body> 17.    </f:view> 18. </html> 

How do I show an image map?

To implement a client-side image map, supply the usemap attribute with the h:outputImage element:

 

 <h:outputImage value="image location" usemap="#aLabel"/> 

You can then specify the map in HTML in the JSF page:

 

 <map name="aLabel">    <area shape="polygon" coords="..." href="...">    <area shape="rect" coords="..." href="...">    ... </map> 

However, this approach does not integrate well with JSF navigation. It would be nicer if the map areas acted like command buttons or links.

The JSF reference implementation includes sample map and area tags that overcome this limitation. The implementation is described in detail in the JSF tutorial, which was last seen as Chapters 17 21 of http://java.sun.com/j2ee/1.4/docs/tutorial/doc/index.html.

To see the image map in action, load the web application that displays the sample components. If you use Tomcat, simply point your browser to

 

 http://localhost:8080/manager/deploy?war=file:/jsf/samples/jsf-components.war 

Replace /jsf with the path to your JSF installation directory.

Load

 

 http://localhost:8080/jsf-components 

Then investigate the image map component (see Figure 12-4).

Figure 12-4. Image Map Sample Component

graphics/12fig04.jpg


Alternatively, you can use a technique that we showed in Chapter 7. Put the image inside a command button, and process the x- and y-coordinates on the server side:

 

 <h:commandButton image="..." actionListener="..."/> 

Attach an action listener that gets the client ID of the button, attaches the suffixes .x and .y, and looks up the coordinate values in the request map. Process the values in any desired way. With this technique, the server application needs to know the geometry of the image.

How do I include an applet in my page?

Simply include the applet tag in the usual way see, for example, Listing 12-5. This page displays the chart applet from Horstmann & Cornell, Core Java vol. 1, ch. 10, Sun Microsystems Press 2002 (see Figure 12-5).

Figure 12-5. The Chart Applet

graphics/12fig05.jpg


Just keep a couple of points in mind.

  • If you include the applet tag inside a panel grid (as in our example), then you need to enclose it inside

     

     <f:verbatim escape="false">...</f:verbatim> 

  • You may want to consider using the jsp:plugin tag instead of the applet tag. That tag generates the appropriate markup for the Java Plug-in. For example,

     

     <jsp:plugin type="applet" code="Chart.class"    width="400" height="300">    <jsp:params>       <jsp:param name="title" value="Diameters of the Planets"/>       <jsp:param name="values" value="9"/>       ...    </jsp:params> </jsp:plugin> 

    See http://java.sun.com/products/plugin for more information on the Java Plug-in.

Listing 12-5. applet/index.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.    <f:view>  5.       <head>  6.          <f:loadBundle basename="com.corejsf.messages" var="msgs"/>  7.          <title><h:outputText value="#{msgs.title}"/></title>  8.       </head>  9.       <body> 10.          <h:form> 11.             <h:panelGrid columns="1"> 12.                <h:column> 13.                   <h:outputText value="#{msgs.header}"/> 14.                </h:column> 15. 16.                <h:column> 17.                   <f:verbatim escape="false"> 18.                      <applet code="Chart.class" width="400" height="300"> 19.                         <param name="title" value="Diameters of the Planets"/> 20.                         <param name="values" value="9"/> 21.                         <param name="name.1" value="Mercury"/> 22.                         <param name="name.2" value="Venus"/> 23.                         <param name="name.3" value="Earth"/> 24.                         <param name="name.4" value="Mars"/> 25.                         <param name="name.5" value="Jupiter"/> 26.                         <param name="name.6" value="Saturn"/> 27.                         <param name="name.7" value="Uranus"/> 28.                         <param name="name.8" value="Neptune"/> 29.                         <param name="name.9" value="Pluto"/> 30.                         <param name="value.1" value="3100"/> 31.                         <param name="value.2" value="7500"/> 32.                         <param name="value.3" value="8000"/> 33.                         <param name="value.4" value="4200"/> 34.                         <param name="value.5" value="88000"/> 35.                         <param name="value.6" value="71000"/> 36.                         <param name="value.7" value="32000"/> 37.                         <param name="value.8" value="30600"/> 38.                         <param name="value.9" value="1430"/> 39.                      </applet> 40.                   </f:verbatim> 41.                </h:column> 42.             </h:panelGrid> 43.          </h:form> 44.       </body> 45.    </f:view> 46. </html> 

How do I produce binary data in a JSF page?

It is possible to use JSF to create a binary response instead of a web page. Follow these steps:

  • Take over the creation of the response headers and content by manipulating the response object returned by ExternalContext.getResponse.

  • Suppress the generation of the JSF response by calling the responseComplete method on the current FacesContext.

As an example, we implement a JSF tag that creates a chart image see Figure 12-6. The image contains JPEG formatted data that were dynamically generated.

Figure 12-6. Producing Binary Data

graphics/12fig06.jpg


Listing 12-6 contains an h:graphicImage tag that includes an image generated by the JSF page in Listing 12-7. The chart tag is mapped to an UIOutput component and the renderer in Listing 12-8.

The important activity occurs in the encodeBegin method. First, we get the HttpServletResponse object and set the content type to image/jpeg. Then we get the output stream object.

 

 HttpServletResponse response    = (HttpServletResponse) context.getExternalContext().getResponse(); response.setContentType("image/jpeg"); OutputStream stream = response.getOutputStream(); 

Next, we gather the parameters that describe the chart and call the drawChart method. That method draws the chart on the Graphics2D object of a BufferedImage.

 

 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g2 = (Graphics2D) image.getGraphics(); drawChart(g2, width, height, title, names, values); 

The drawChart method was taken from the chart applet in Horstmann & Cornell, Core Java Vol. 1, Ch. 10, Sun Microsystems Press, 2002.

Next, we use the ImageIO class to get a writer for the JPEG format. The writer sends the image data to the output stream to the stream that we obtained from the HttpServletResponse.

 

 Iterator iter = ImageIO.getImageWritersByFormatName("jpeg"); ImageWriter writer = (ImageWriter) iter.next(); writer.setOutput(ImageIO.createImageOutputStream(stream)); writer.write(image); 

Finally, we terminate the response processing:

 

 context.responseComplete(); 

The JSF implementation simply sends the output data to the browser and terminates this request.

You use the same approach to generate any kind of binary data. The only difference is the code for sending data to the output stream.

Listing 12-6. binary/index.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.    <f:view>  5.       <head>  6.          <title>Generating binary data</title>  7.       </head>  8.       <body>  9.          <h:form> 10.             <p>Here is your image:</p> 11.             <h:graphicImage url="chart.faces"/> 12.          </h:form> 13.       </body> 14.    </f:view> 15. </html> 

Listing 12-7. binary/chart.jsp
 1. <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> 2. <%@ taglib uri="http://corejsf.com/chart" prefix="corejsf" %> 3. <f:view> 4.    <corejsf:chart width="500" height="500" 5.       title="Diameters of the Planets" 6.       names="#{planets.names}" values="#{planets.values}"/> 7. </f:view> 

Listing 12-8. binary/WEB-INF/classes/com/corejsf/ChartRenderer.java
   1. package com.corejsf;   2.   3. import java.awt.Color;   4. import java.awt.Font;   5. import java.awt.Graphics2D;   6. import java.awt.font.FontRenderContext;   7. import java.awt.font.LineMetrics;   8. import java.awt.geom.Rectangle2D;   9. import java.awt.image.BufferedImage;  10. import java.io.IOException;  11. import java.io.OutputStream;  12. import java.util.Iterator;  13. import java.util.Map;  14. import javax.faces.component.UIComponent;  15. import javax.faces.context.FacesContext;  16. import javax.faces.el.ValueBinding;  17. import javax.faces.render.Renderer;  18. import javax.imageio.ImageIO;  19. import javax.imageio.ImageWriter;  20. import javax.servlet.http.HttpServletResponse;  21.  22. public class ChartRenderer extends Renderer {  23.    public void encodeBegin(FacesContext context, UIComponent component)  24.       throws IOException {  25.       if (!component.isRendered()) return;  26.  27.       HttpServletResponse response  28.          = (HttpServletResponse) context.getExternalContext().getResponse();  29.       response.setContentType("image/jpeg");  30.       OutputStream stream = response.getOutputStream();  31.  32.       Map attributes = component.getAttributes();  33.  34.       int width = parseInt(attributes, "width", DEFAULT_WIDTH);  35.       int height = parseInt(attributes, "height", DEFAULT_HEIGHT);  36.       String title = parseString(attributes, "title", "");  37.  38.       ValueBinding vb = component.getValueBinding("names");  39.       String[] names = vb == null ? null : (String[]) vb.getValue(context);  40.       vb = component.getValueBinding("values");  41.       double[] values = vb == null ? null : (double[]) vb.getValue(context);  42.  43.       BufferedImage image = new BufferedImage(width, height,  44.          BufferedImage.TYPE_INT_RGB);  45.       Graphics2D g2 = (Graphics2D) image.getGraphics();  46.       drawChart(g2, width, height, title, names, values);  47.  48.       Iterator iter = ImageIO.getImageWritersByFormatName("jpeg");  49.       ImageWriter writer = (ImageWriter) iter.next();  50.       writer.setOutput(ImageIO.createImageOutputStream(stream));  51.       writer.write(image);  52.  53.       context.responseComplete();  54.    }  55.  56.    private static int parseInt(Map attributes, String name, int defaultValue) {  57.       String value = (String) attributes.get(name);  58.       if (value != null)  59.          try {  60.             return Integer.parseInt(value);  61.          } catch (NumberFormatException ex) {  62.          }  63.       return defaultValue;  64.    }  65.  66.    private static String parseString(Map attributes, String name,  67.       String defaultValue) {  68.       String value = (String) attributes.get(name);  69.       if (value != null) return value;  70.       return defaultValue;  71.    }  72.  73.    private static void drawChart(Graphics2D g2, int width, int height,  74.       String title, String[] names, double[] values)  75.    {  76.       // clear the background  77.       g2.setPaint(Color.WHITE);  78.       g2.fill(new Rectangle2D.Double(0, 0, width, height));  79.       g2.setPaint(Color.BLACK);  80.  81.       if (names == null || values == null || names.length != values.length)  82.          return;  83.  84.       // compute the minimum and maximum values  85.       if (values == null) return;  86.       double minValue = 0;  87.       double maxValue = 0;  88.       for (int i = 0; i < values.length; i++) {  89.          if (minValue > values[i]) minValue = values[i];  90.          if (maxValue < values[i]) maxValue = values[i];  91.       }  92.       if (maxValue == minValue) return;  93.  94.       Font titleFont = new Font("SansSerif", Font.BOLD, 20);  95.       Font labelFont = new Font("SansSerif", Font.PLAIN, 10);  96.  97.       // compute the extent of the title  98.       FontRenderContext context = g2.getFontRenderContext();  99.       Rectangle2D titleBounds 100.          = titleFont.getStringBounds(title, context); 101.       double titleWidth = titleBounds.getWidth(); 102.       double top = titleBounds.getHeight(); 103. 104.       // draw the title 105.       double y = -titleBounds.getY(); // ascent 106.       double x = (width - titleWidth) / 2; 107.       g2.setFont(titleFont); 108.       g2.drawString(title, (float)x, (float)y); 109. 110.       // compute the extent of the bar labels 111.       LineMetrics labelMetrics 112.          = labelFont.getLineMetrics("", context); 113.       double bottom = labelMetrics.getHeight(); 114. 115.       y = height - labelMetrics.getDescent(); 116.       g2.setFont(labelFont); 117. 118.       // get the scale factor and width for the bars 119.       double scale = (height - top - bottom) 120.          / (maxValue - minValue); 121.       int barWidth = width / values.length; 122. 123.       // draw the bars 124.       for (int i = 0; i < values.length; i++) { 125.          // get the coordinates of the bar rectangle 126.          double x1 = i * barWidth + 1; 127.          double y1 = top; 128.          double barHeight = values[i] * scale; 129.          if (values[i] >= 0) 130.             y1 += (maxValue - values[i]) * scale; 131.          else { 132.             y1 += maxValue * scale; 133.             barHeight = -barHeight; 134.          } 135. 136.          // fill the bar and draw the bar outline 137.          Rectangle2D rect = new Rectangle2D.Double(x1, y1, 138.             barWidth - 2, barHeight); 139.          g2.setPaint(Color.RED); 140.          g2.fill(rect); 141.          g2.setPaint(Color.BLACK); 142.          g2.draw(rect); 143. 144.          // draw the centered label below the bar 145.          Rectangle2D labelBounds 146.             = labelFont.getStringBounds(names[i], context); 147. 148.          double labelWidth = labelBounds.getWidth(); 149.          x = i * barWidth + (barWidth - labelWidth) / 2; 150.          g2.drawString(names[i], (float)x, (float)y); 51.       } 152.    } 153. 154.    private static int DEFAULT_WIDTH = 200; 155.    private static int DEFAULT_HEIGHT = 200; 156. } 

How do I show a large data set one page at a time?

As you saw in Chapter 5, you can add scroll bars to a table. But if the table is truly large, you don't want it sent to the client in its entirety. Downloading the table takes a long time, and chances are that the application user only wants to see the first few rows anyway.

The standard user interface for navigating large table is a "pager," a set of links to each page of the table, to the next and previous page, and if there are a great number of pages, to the next and previous batch of pages. Figure 12-7 shows a pager that scrolls through a large data set the predefined time zones, obtained by a call to java.util.TimeZone.getAvailableIDs().

Figure 12-7. Table with a Pager

graphics/12fig07.jpg


Unfortunately, JSF 1.0 does not include a pager component. However, it is fairly easy to write one, and we give you the code to use or modify in your own applications.

The pager is a companion to a data table. You specify the ID of the data table, the number of pages that the pager displays, and the styles for the selected and unselected links. For example,

 

 <h:dataTable  value="#{bb.data}" var="row" rows="10">    ... </h:dataTable> <corejsf:pager dataTable showpages="20"    selectedStyle/> 

Suppose the user clicks on the ">"link to move to the next page. The pager locates the data table and updates its first property, adding the value of the rows property. You will find that code in the decode method of the PagerRenderer in Listing 12-9.

The encode method is a bit more involved. It generates a set of links. Similarly to a commandLink, clicking the link activates JavaScript code that sets a value in a hidden field and submits the form.

Listing 12-10 shows the index.jsp page that generates the table and the pager. Listing 12-11 shows the trivial backing bean.

Listing 12-9. pager/WEB-INF/classes/com/corejsf/PagerRenderer.java
   1. package com.corejsf;   2.   3. import java.io.IOException;   4. import java.util.Iterator;   5. import java.util.Map;   6. import javax.faces.component.NamingContainer;   7. import javax.faces.component.UIComponent;   8. import javax.faces.component.UIData;   9. import javax.faces.component.UIForm;  10. import javax.faces.context.FacesContext;  11. import javax.faces.context.ResponseWriter;  12. import javax.faces.el.ValueBinding;  13. import javax.faces.render.Renderer;  14.  15. public class PagerRenderer extends Renderer {  16.    public void encodeBegin(FacesContext context, UIComponent component)  17.       throws IOException {  18.       String id = component.getClientId(context);  19.       UIComponent parent = component;  20.       while (!(parent instanceof UIForm)) parent = parent.getParent();  21.       String formId = parent.getClientId(context);  22.  23.       ResponseWriter writer = context.getResponseWriter();  24.  25.       String styleClass = (String) get(context, component, "styleClass");  26.       String selectedStyleClass = (String) get(context, component,  27.             "selectedStyleClass");  28.       String dataTableId = (String) get(context, component, "dataTableId");  29.       Integer a = (Integer) get(context, component, "showpages");  30.       int showpages = a == null ? 0 : a.intValue();  31.  32.       // find the component with the given ID  33.  34.       UIData data = (UIData) findComponent(context.getViewRoot(),  35.          getId(dataTableId, id), context);  36.  37.       int first = data.getFirst();  38.       int itemcount = data.getRowCount();  39.       int pagesize = data.getRows();  40.       if (pagesize <= 0) pagesize = itemcount;  41.  42.       int pages = itemcount / pagesize;  43.       if (itemcount % pagesize != 0) pages++;  44.  45.       int currentPage = first / pagesize;  46.       if (first >= itemcount - pagesize) currentPage = pages - 1;  47.       int startPage = 0;  48.       int endPage = pages;  49.       if (showpages > 0) {  50.          startPage = (currentPage / showpages) * showpages;  51.          endPage = Math.min(startPage + showpages, pages);  52.       }  53.  54.       if (currentPage > 0)  55.          writeLink(writer, component, formId, id, "<", styleClass);  56.  57.       if (startPage > 0)  58.          writeLink(writer, component, formId, id, "<<", styleClass);  59.  60.       for (int i = startPage; i < endPage; i++) {  61.          writeLink(writer, component, formId, id, "" + (i + 1),  62.             i == currentPage ? selectedStyleClass : styleClass);  63.       }  64.  65.       if (endPage < pages)  66.          writeLink(writer, component, formId, id, ">>", styleClass);  67.  68.       if (first < itemcount - pagesize)  69.          writeLink(writer, component, formId, id, ">", styleClass);  70.  71.       // hidden field to hold result  72.       writeHiddenField(writer, component, id);  73.    }  74.  75.    private void writeLink(ResponseWriter writer, UIComponent component,  76.       String formId, String id, String value, String styleClass)  77.       throws IOException {  78.       writer.writeText(" ", null);  79.       writer.startElement("a", component);  80.       writer.writeAttribute("href", "#", null);  81.       writer.writeAttribute("onclick", onclickCode(formId, id, value), null);  82.       if (styleClass != null)  83.          writer.writeAttribute("class", styleClass, "styleClass");  84.       writer.writeText(value, null);  85.       writer.endElement("a");  86.    }  87.  88.    private String onclickCode(String formId, String id, String value) {  89.       StringBuffer buffer = new StringBuffer();  90.       buffer.append("document.forms[");  91.       buffer.append("'");  92.       buffer.append(formId);  93.       buffer.append("'");  94.       buffer.append("]['");  95.       buffer.append(id);  96.       buffer.append("'].value='");  97.       buffer.append(value);  98.       buffer.append("';");  99.       buffer.append(" document.forms["); 100.       buffer.append("'"); 101.       buffer.append(formId); 102.       buffer.append("'"); 103.       buffer.append("].submit()"); 104.       buffer.append("; return false;"); 105.       return buffer.toString(); 106.    } 107. 108.    private void writeHiddenField(ResponseWriter writer, UIComponent component, 109.       String id) throws IOException { 110.       writer.startElement("input", component); 111.       writer.writeAttribute("type", "hidden", null); 112.       writer.writeAttribute("name", id, null); 113.       writer.endElement("input"); 114.    } 115. 116.    public void decode(FacesContext context, UIComponent component) { 117.       String id = component.getClientId(context); 118.       Map parameters = context.getExternalContext() 119.          .getRequestParameterMap(); 120.       String response = (String) parameters.get(id); 121. 122.       String dataTableId = (String) get(context, component, "dataTableId"); 123.       Integer a = (Integer) get(context, component, "showpages"); 124.       int showpages = a == null ? 0 : a.intValue(); 125. 126.       UIData data = (UIData) findComponent(context.getViewRoot(), 127.          getId(dataTableId, id), context); 128. 129.       int first = data.getFirst(); 130.       int itemcount = data.getRowCount(); 131.       int pagesize = data.getRows(); 132.       if (pagesize <= 0) pagesize = itemcount; 133. 134.       if (response.equals("<")) first -= pagesize; 135.       else if (response.equals(">")) first += pagesize; 136.       else if (response.equals("<<")) first -= pagesize * showpages; 137.       else if (response.equals(">>")) first += pagesize * showpages; 138.       else { 139.          int page = Integer.parseInt(response); 140.          first = (page - 1) * pagesize; 141.       } 142.       if (first + pagesize > itemcount) first = itemcount - pagesize; 143.       if (first < 0) first = 0; 144.       data.setFirst(first); 145.    } 146. 147.    private static Object get(FacesContext context, UIComponent component, 148.       String name) { 149.       ValueBinding binding = component.getValueBinding(name); 150.       if (binding != null) return binding.getValue(context); 151.       else return component.getAttributes().get(name); 152.    } 153. 154.    private static UIComponent findComponent(UIComponent component, String id, 155.       FacesContext context) { 156.       String componentId = component.getClientId(context); 157.       if (componentId.equals(id)) return component; 158.       Iterator kids = component.getChildren().iterator(); 159.       while (kids.hasNext()) { 160.          UIComponent kid = (UIComponent) kids.next(); 161.          UIComponent found = findComponent(kid, id, context); 162.          if (found != null) return found; 163.       } 164.       return null; 165.    } 166. 167.    private static String getId(String id, String baseId) { 168.       String separator = "" + NamingContainer.SEPARATOR_CHAR; 169.       String[] idSplit = id.split(separator); 170.       String[] baseIdSplit = baseId.split(separator); 171.       StringBuffer buffer = new StringBuffer(); 172.       for (int i = 0; i < baseIdSplit.length - idSplit.length; i++) { 173.          buffer.append(baseIdSplit[i]); 174.          buffer.append(separator); 75.       } 176.       buffer.append(id); 177.       return buffer.toString(); 178.    } 179. } 

Listing 12-10. pager/index.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.    <%@ taglib uri="http://corejsf.com/pager" prefix="corejsf" %>  5.  6.    <f:view>  7.       <head>  8.          <link href="styles.css" rel="stylesheet" type="text/css"/>  9.          <title>Pager Test</title> 10.       </head> 11.       <body> 12.          <h:form> 13.             <h:dataTable  value="#{bb.data}" var="row" rows="10"> 14.                <h:column> 15.                   <h:outputText value="#{row}" /> 16.                </h:column> 17.             </h:dataTable> 18.             <corejsf:pager dataTable 19.                showpages="20" selectedStyle/> 20.          </h:form> 21.       </body> 22.    </f:view> 23. </html> 

Listing 12-11. pager/WEB-INF/classes/com/corejsf/BackingBean.java
 1. package com.corejsf; 2. 3. public class BackingBean { 4.    private String[] data = java.util.TimeZone.getAvailableIDs(); 5.    public String[] getData() { return data; } 6. } 

How do I generate a popup window?

The basic method for a popup window is simple. Use the JavaScript calls

 

 popup = window.open(url, name, features); popup.focus(); 

The features parameter is a string such as "height=300,width=200,toolbar=no,menubar=no".

The popup window should be displayed when the user clicks a button or link. Attach a function to the onclick handler of the button or link, and have the function return false so that the browser doesn't submit the form or follow the link. For example,

 

 <h:commandButton value="..." onclick="doPopup(this); return false;"/> 

The doPopup function contains the JavaScript instructions for popping up the window. It is contained in a script tag inside the page header.

However, challenges arise when you need to transfer data between the main window and the popup.

Let us look at a specific example. Figure 12-8 shows a page with a popup window that lists the states of the USA or the provinces of Canada, depending on the setting of the radio buttons. The list is generated by a backing bean on the server.

Figure 12-8. Popping Up a Window to Select a State or Province

graphics/12fig08.jpg


How does the backing bean know which state was selected? After all, the form has not yet been posted back to the server when the user requests the popup. We show you two solutions each of them is interesting in its own right and may give you ideas for solving similar problems.

In the first solution, we pass the selection parameter to the popup URL, like this:

 

 window.open("popup.faces?country=" + country[i].value, "popup", features); 

The popup.faces page retrieves the value of the country request parameter as param.country:

 

 <h:dataTable value="#{bb.states[param.country]}" var="state"> 

Here, the states property of the backing bean bb yields a map whose index is the country name.

The second solution (suggested by Marion Bass and Sergey Smirnov) is more involved but also more powerful. In this technique, the popup window is first created as a blank window and then filled with the response to a JSF command.

The JSF command is issued by a form that contains a hidden field and an invisible link, like this:

 

 <h:form  target="popup">    <h:inputHidden  value="#{bb.country}"/>    <h:commandLink  action="showStates" value="">       <f:verbatim></f:verbatim>    </h:commandLink> </h:form> 

Note the following details:

  • The target of the form has the same name as the popup window. Therefore, the browser will show the result of the action inside the popup.

  • The hidden country field will be populated before the form is submitted. It sets the bb.country value binding. This enables the backing bean to return the appropriate set of states or provinces.

  • The action attribute of the command link is used by the navigation handler to select the JSF page that generates the popup contents.

The doPopup function initializes the hidden field and fires the link action:

 

 var hidden = document.forms.hidden; hidden["hidden:go"].value = "x"; // any value will do hidden["hidden:country"].value = country[selected].value; hidden.submit(); 

The value of the selected radio button is transferred into the hidden field. When the hidden form is submitted, that value will be stored in the backing bean.

The value of the link element can be any nonblank entry. It is used by the decode method of the UICommandLink renderer to check that this link was activated.

In this solution, the JSF page for the popup is more straightforward. The table of states or provinces is simply populated by the bean property call

 

 <h:dataTable value="#{bb.statesForCountry}" var="state"> 

The statesForCountry property takes the country property into account it was set when the hidden form was decoded. This approach is more flexible than the first approach because it allows arbitrary bean properties to be set before the popup contents are computed.

With both approaches, it is necessary to send the popup data back to the original page. However, this can be achieved with straightforward JavaScript. The popup's opener property is the window that opened the popup. When the user clicks a link in the popup, we simply set the value of the corresponding text field in the original page:

 

 opener.document.forms[formId][formId + ":state"].value = value; 

How does the popup know the form ID of the original form? Here we take advantage of the flexibility of JavaScript. You can add instance fields to any object on-the-fly. We set an openerFormId field in the popup window when it is constructed:

 

 popup = window.open(...); popup.openerFormId = source.form.id; 

When we are ready to modify the form variables, we retrieve it from the popup window like this:

 

 var formId = window.openerFormId; 

These are the tricks that you need to know in order to deal with popup windows. The following example shows the two approaches that we discussed. The index.jsp and popup.jsp files in Listings 12-12 and 12-13 show the first approach, using a request parameter to configure the popup page. The index2.jsp and popup2.jsp files in Listings 12-14 and 12-15 show the second approach, filling the popup page with the result of a JSF action. Listing 12-16 shows the backing bean, and Listing 12-17 shows the configuration file. Note how the showStates action leads to the popup2.jsp page.

Listing 12-12. popup/index.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.  5.    <f:view>  6.       <head>  7.          <script language="JavaScript1.1">  8.             function doPopup(source) {  9.                country = source.form[source.form.id + ":country"]; 10.                for (var i = 0; i < country.length; i++) { 11.                   if (country[i].checked) { 12.                      popup = window.open("popup.faces?country=" 13.                         + country[i].value, "popup", 14.                         "height=300,width=200,toolbar=no,menubar=no," 15.                         + "scrollbars=yes"); 16.                      popup.openerFormId = source.form.id; 17.                      popup.focus(); 18.                   } 19.                } 20.             } 21.        </script> 22.          <title>A Simple Java Server Faces Application</title> 23.       </head> 24.       <body> 25.          <h:form> 26.             <table> 27.                <tr> 28.                   <td>Country:</td> 29.                   <td> 30.                      <h:selectOneRadio  value="#{bb.country}"> 31.                         <f:selectItem itemLabel="USA"  itemValue="USA"/> 32.                         <f:selectItem itemLabel="Canada"  itemValue="Canada"/> 33.                      </h:selectOneRadio> 34.                   </td> 35.                </tr> 36.                <tr> 37.                   <td>State/Province:</td> 38.                   <td> 39.                      <h:inputText  value="#{bb.state}"/> 40.                   </td> 41.                   <td> 42.                      <h:commandButton value="..." 43.                         onclick="doPopup(this); return false;"/> 44.                   </td> 45.                </tr> 46.             </table> 47.             <p> 48.                <h:commandButton value="Next" action="next"/> 49.             </p> 50.          </h:form> 51.       </body> 52.    </f:view> 53. </html> 

Listing 12-13. popup/popup.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.  5.    <f:view>  6.       <head>  7.          <script type="text/javascript" language="JavaScript1.2">  8.             function doSave(value) {  9.                var formId = window.openerFormId; 10.                opener.document.forms[formId][formId + ":state"].value = value; 11.                window.close(); 12.             } 13.          </script> 14.          <title>Select a state/province</title> 15.       </head> 16.       <body> 17.          <h:form> 18.             <h:dataTable value="#{bb.states[param.country]}" var="state"> 19.                <h:column> 20.                   <h:outputLink value="#" 21.                      onclick="doSave('#{state}');"> 22.                      <h:outputText value="#{state}" /> 23.                   </h:outputLink> 24.                </h:column> 25.             </h:dataTable> 26.          </h:form> 27.       </body> 28.    </f:view> 29. </html> 

Listing 12-14. popup/index2.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.  5.    <f:view>  6.       <head>  7.          <script language="JavaScript1.1">  8.             function doPopup(source) {  9.                country = source.form[source.form.id + ":country"]; 10.                for (var i = 0; i < country.length; i++) { 11.                   if (country[i].checked) { 12.                      popup = window.open("", 13.                         "popup", 14.                         "height=300,width=200,toolbar=no,menubar=no," 15.                         + "scrollbars=yes"); 16.                      popup.openerFormId = source.form.id; 17.                      popup.focus(); 18.                      var hidden = document.forms.hidden; 19.                      hidden["hidden:go"].value = "x"; // any value will do 20.                      hidden["hidden:country"].value = country[i].value; 21.                      hidden.submit(); 22.                   } 23.                } 24.             } 25.          </script> 26.          <title>A Simple Java Server Faces Application</title> 27.       </head> 28.       <body> 29.          <h:form> 30.             <table> 31.                <tr> 32.                   <td>Country:</td> 33.                   <td> 34.                      <h:selectOneRadio  value="#{bb.country}"> 35.                         <f:selectItem itemLabel="USA"  itemValue="USA"/> 36.                         <f:selectItem itemLabel="Canada"  itemValue="Canada"/> 37.                      </h:selectOneRadio> 38.                   </td> 39.                </tr> 40.                <tr> 41.                   <td>State/Province:</td> 42.                   <td> 43.                      <h:inputText  value="#{bb.state}"/> 44.                   </td> 45.                   <td> 46.                      <h:commandButton value="..." 47.                         onclick="doPopup(this); return false;"/> 48.                   </td> 49.                </tr> 50.             </table> 51.             <p> 52.                <h:commandButton value="Next" action="next"/> 53.             </p> 54.          </h:form> 55. 56.          <%-- This hidden form sends a request to a popup window. --%> 57.          <h:form  target="popup"> 58.             <h:inputHidden  value="#{bb.country}"/> 59.             <h:commandLink  action="showStates" value=""> 60.                <f:verbatim/> 61.             </h:commandLink> 62.          </h:form> 63.       </body> 64.    </f:view> 65. </html> 

Listing 12-15. popup/popup2.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.  5.    <f:view>  6.       <head>  7.          <script language="JavaScript1.1">  8.             function doSave(value) {  9.                var formId = window.openerFormId; 10.                opener.document.forms[formId][formId + ":state"].value = value; 11.                window.close(); 12.             } 13.          </script> 14.          <title>Select a state/province</title> 15.       </head> 16.       <body> 17.          <h:form> 18.             <h:dataTable value="#{bb.statesForCountry}" var="state"> 19.                <h:column> 20.                   <h:outputLink value="#" 21.                      onclick="doSave('#{state}');"> 22.                      <h:outputText value="#{state}" /> 23.                   </h:outputLink> 24.                </h:column> 25.             </h:dataTable> 26.          </h:form> 27.       </body> 28.    </f:view> 29. </html> 

Listing 12-16. popup/WEB-INF/classes/com/corejsf/BackingBean.java
  1. package com.corejsf;  2.  3. import java.util.HashMap;  4. import java.util.Map;  5.  6. public class BackingBean {  7.    private String country = "USA";  8.    private String state = "";  9.    private static Map states; 10. 11.    // PROPERTY: country 12.    public String getCountry() { return country; } 13.    public void setCountry(String newValue) { country = newValue; } 14. 15.    // PROPERTY: state 16.    public String getState() { return state; } 17.    public void setState(String newValue) { state = newValue; } 18. 19.    public Map getStates() { return states; } 20. 21.    public String[] getStatesForCountry() { return (String[]) states.get(country); } 22. 23.    static { 24.       states = new HashMap(); 25.       states.put("USA", 26.          new String[] { 27.             "Alabama", "Alaska", "Arizona", "Arkansas", "California", 28.             "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", 29.             "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", 30.             "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", 31.             "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", 32.             "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", 33.             "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", 34.             "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", 35.             "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", 36.             "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming" 37.          }); 38. 39.       states.put("Canada", 40.          new String[] { 41.             "Alberta", "British Columbia", "Manitoba", "New Brunswick", 42.             "Newfoundland and Labrador", "Northwest Territories", 43.             "Nova Scotia", "Nunavut", "Ontario", "Prince Edward Island", 44.             "Quebec", "Saskatchewan", "Yukon" 45.          }); 46.    } 47. } 

Listing 12-17. popup/WEB-INF/faces-config.xml
  1. <?xml version="1.0"?>  2.  3. <!DOCTYPE faces-config PUBLIC  4.   "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"  5.   "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">  6.  7. <faces-config>  8.    <navigation-rule>  9.       <navigation-case> 10.          <from-outcome>next</from-outcome> 11.          <to-view-id>/welcome.jsp</to-view-id> 12.       </navigation-case> 13.       <navigation-case> 14.          <from-outcome>showStates</from-outcome> 15.          <to-view-id>/popup2.jsp</to-view-id> 16.       </navigation-case> 17.       <navigation-case> 18.          <from-outcome>technique1</from-outcome> 19.          <to-view-id>/index.jsp</to-view-id> 20.       </navigation-case> 21.       <navigation-case> 22.          <from-outcome>technique2</from-outcome> 23.          <to-view-id>/index2.jsp</to-view-id> 24.       </navigation-case> 25.    </navigation-rule> 26. 27.    <managed-bean> 28.       <managed-bean-name>bb</managed-bean-name> 29.       <managed-bean-class>com.corejsf.BackingBean</managed-bean-class> 30.       <managed-bean-scope>session</managed-bean-scope> 31.    </managed-bean> 32. </faces-config> 

How do I customize error pages?

You probably don't want your users to see scary stack traces when they run into an error in your web application. There are two mechanisms for customizing the display of errors.

You can specify an error page for a specific JSF page with the following JSP directive:

 

 <%@ page errorPage="error.jsp" %> 

When an error occurs during execution of the page, the error.jsp page is displayed. However, if the error occurs before the page has been compiled, then this mechanism doesn't work.

You can specify generic error pages with the error-page tag in the web.xml file. Specify either a Java exception class or an HTTP error code. For example,

 

 <error-page>    <exception-type>java.lang.Exception</exception-type>    <location>/exception.jsp</location> </error-page> <error-page>    <error-code>500</error-code>    <location>/error.jsp</location> </error-page> <error-page>    <error-code>404</error-code>    <location>/notfound.jsp</location> </error-page> 

If an exception occurs and an error page matches its type, then the matching error page is displayed. Otherwise, an HTTP error 500 is generated.

If an HTTP error occurs and there is a matching error page, it is displayed. Otherwise, the default error page is displayed.

CAUTION

graphics/caution_icon.gif

If an error occurs while trying to display a custom error page, the default error page is displayed instead. If your custom error page stubbornly refuses to appear, check the log files for messages relating to your error page.


If you use the JSP errorPage directive, the exception object is available in the request map with the key "javax.servlet.jsp.jspException". If you use the servlet error-page mechanism, several objects related to the error are placed in the request map see Table 12-1. You can use these values to display information that describes the error.

Table 12-1. Servlet Exception Attributes

Key

Value

Type

javax.servlet.error.status_code

The HTTP error code

Integer

javax.servlet.error.message

A description of the error

String

javax.servlet.error.exception_type

The class of the exception

Class

javax.servlet.error.exception

The exception object

Throwable

javax.servlet.error.request_uri

The path to the application resource that encountered the error

String

javax.servlet.error.servlet_name

The name of the servlet that encountered the error

String


The following sample application uses this technique. We purposely produce a null pointer exception in the password property of the UserBean. Listing 12-18 shows the web.xml file that sets the error page to errorDisplay.jsp (Listing 12-19).

Listing 12-20 shows the ErrorBean class. Its getStackTrace method assembles a complete stack trace that contains all nested exceptions.

NOTE

graphics/note_icon.gif

The errorDisplay.jsp page uses a f:subview tag. This is a workaround for an anomaly in the JSF reference implementation using f:view in an error page causes an assertion error in the framework code.


Listing 12-18. error/WEB-INF/web.xml
  1. <?xml version="1.0"?>  2.  3. <!DOCTYPE web-app PUBLIC  4.    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"  5.    "http://java.sun.com/dtd/web-app_2_3.dtd">  6.  7. <web-app>  8.    <servlet>  9.       <servlet-name>Faces Servlet</servlet-name> 10.       <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> 11.       <load-on-startup>1</load-on-startup> 12.    </servlet> 13. 14.    <servlet-mapping> 15.       <servlet-name>Faces Servlet</servlet-name> 16.       <url-pattern>*.faces</url-pattern> 17.    </servlet-mapping> 18. 19.    <welcome-file-list> 20.       <welcome-file>/index.html</welcome-file> 21.    </welcome-file-list> 22. 23.    <error-page> 24.       <error-code>500</error-code> 25.       <location>/errorDisplay.faces</location> 26.    </error-page> 27. </web-app> 

Listing 12-19. error/errorDisplay.jsp
  1. <html>  2.    <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>  3.    <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>  4.  5.    <f:subview >  6.       <head>  7.          <f:loadBundle basename="com.corejsf.messages" var="msgs"/>  8.          <title><h:outputText value="#{msgs.title}"/></title>  9.       </head> 10.       <body> 11.          <h:form> 12.             <p><h:outputText value="#{msgs.errorOccurred}"/></p> 13.             <p><h:outputText value="#{msgs.copyReport}"/></p> 14.             <h:inputTextarea value="#{error.stackTrace}" 15.                rows="40" cols="80" readonly="true"/> 16.          </h:form> 17.       </body> 18.    </f:subview> 19. </html> 

Listing 12-20. error/WEB-INF/classes/com/corejsf/ErrorBean.java
  1. package com.corejsf;  2.  3. import java.io.PrintWriter;  4. import java.io.StringWriter;  5. import java.sql.SQLException;  6. import java.util.Map;  7. import javax.faces.context.FacesContext;  8. import javax.servlet.ServletException;  9. 10. public class ErrorBean { 11.    public String getStackTrace() { 12.       FacesContext context = FacesContext.getCurrentInstance(); 13.       Map request = context.getExternalContext().getRequestMap(); 14.       Throwable ex = (Throwable) request.get("javax.servlet.error.exception"); 15.       StringWriter sw = new StringWriter(); 16.       PrintWriter pw = new PrintWriter(sw); 17.       fillStackTrace(ex, pw); 18.       return sw.toString(); 19.    } 20. 21.    private static void fillStackTrace(Throwable t, PrintWriter w) { 22.       if (t == null) return; 23.       t.printStackTrace(w); 24.       if (t instanceof ServletException) { 25.          Throwable cause = ((ServletException) t).getRootCause(); 26.          if (cause != null) { 27.             w.println("Root cause:"); 28.             fillStackTrace(cause, w); 29.          } 30.       } else if (t instanceof SQLException) { 31.          Throwable cause = ((SQLException) t).getNextException(); 32.          if (cause != null) { 33.             w.println("Next exception:"); 34.             fillStackTrace(cause, w); 35.          } 36.       } else { 37.          Throwable cause = t.getCause(); 38.          if (cause != null) { 39.             w.println("Cause:"); 40.             fillStackTrace(cause, w); 41.          } 42.       } 43.    } 44. } 

Figure 12-9. A Customized Error Display

graphics/12fig09.jpg




core JavaServer Faces
Core JavaServer Faces
ISBN: 0131463055
EAN: 2147483647
Year: 2003
Pages: 121

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net