Debugging and Logging

Programming

In the following sections, we discuss issues that are of interest to JSF programmers. We show you how to use Eclipse with JSF, how to reduce the drudgery of common implementation tasks, how to initialize your application, and how to package components into a reusable JAR file.

How Do I Use JSF with Eclipse?

You can use Eclipse to edit JSF pages and to compile the code for beans, converters, validators, and components. We assume that you are familiar with the basic operation of Eclipse, and so we cover only the special configuration details for JSF programming.

The principal issue is the installation of JSF and Ant libraries. It would be tedious to install the libraries separately for each project.

First, we consider the JSF libraries. The trick is to make a project that contains the libraries, and to use it as a base for all JSF projects. Here is how you set up the base project (which we will call jsflibs).

1.

Select File -> New -> Project from the menu and supply the project name, jsflibs.

2.

In the "Java Settings" screen of the project wizard, click the "Libraries" tab, then click the "Add External JARs..." button. If you use GlassFish, simply add the javaee.jar file from the glassfish/lib directory. If you use another JSF implementation, add all required JAR files.

Figure 13-12. Adding libraries to the jsflibs project


3.

Click the "Order and Export" button and check the libraries that you just added (see Figure 13-13).

Figure 13-13. Exporting libraries in the jsflibs project


4.

Click the "Finish" button.

Whenever you set up your JSF projects, start with the "New Java Project" wizard in the usual way. However, when you get to the "Java Settings" screen, click the "Projects" tab and check the jsflibs project (see Figure 13-14). Now all the required libraries are automatically included in your project.

Figure 13-14. Including the jsflibs project


For easy invocation of Ant, the build.xml file must be included in the project directory. This differs from the setup of our sample applications, for which we use a single build.xml file for all applications. If you like our setup, add the following build.xml file into each Eclipse project directory:

  <project default="build.war">      <basename property="app" file="${basedir}"/>      <property file="../build.properties"/>      <property name="appdir" value="${basedir}"/>      <import file="../build.xml"/>   </project>

Or, if you prefer, copy the build.xml and build.properties files into each project.

To run Ant, right-click the build.xml file and select "Run Ant...". The Ant messages show up in the console.

Tip

We recommend that you install the XMLBuddy plugin and make it the default editor for JSF pages. You can download it from http://xmlbuddy.com. Of course, since XML Buddy is a real XML editor, it will frown upon <%...%> delimiters. We suggest that you use proper XML syntax (see the note on page 15 of Chapter 1).


With this configuration, Eclipse becomes a bare bones development environment for JSF. Of course, you would like more, such as autocompletion in JSF pages, wiggly underlines when you make errors in JSF or XML files, visual editing of page navigation, debugging, and so on. All this will be supplied by the JSF extensions to the Web Tools Platform (WTP), which itself is based on several other Eclipse extensions. At the time of this writing, the installation procedure and feature set are still quite preliminary. In particular, the JSF Tools project is only approaching version 0.5.

If you want to experiment with these upcoming features, we suggest that you make a separate installation of Eclipse and use the Software Update feature to download the extensions. Select the JSF Tools and have the installer find all required extensions.

How Do I Locate a Configuration File?

Some applications prefer to process their own configuration files rather than using faces-config.xml or web.xml. The challenge is to locate the file because you do not know where the web container stores the files of your web application. In fact, the web container need not physically store your files at all it can choose to read them out of the WAR file.

Instead, use the getResourceAsStream method of the ExternalContext class. For example, suppose you want to read app.properties in the WEB-INF directory of your application. Here is the required code:

  FacesContext context = FacesContext.getCurrentInstance();   ExternalContext external = context.getExternalContext();   InputStream in = external.getResourceAsStream("/WEB-INF/app.properties");

How Can a JSF Component Access Resources From a JAR File?

Suppose you provide a component library and deliver it to your customers in a file mycomponent.jar. Some of the renderers need to generate links to images or JavaScript code that are stored inside the JAR file. You could ask your customers to extract a file with resources into a specific location, but that is error-prone and less attractive than just telling them to drop a JAR file into WEB-INF/lib.

As an example, consider a spinner with graphical increment and decrement buttons (see Figure 13-15). The renderer will want to generate HTML of the form

  <input type="image" url="image URL" .../>

Figure 13-15. The spinner component with image buttons


The challenge is to specify an image URL that activates some mechanism for fetching the image from the component JAR file.

There is no standard solution to this problem, perhaps because the basic HTML render kit does not require images or lengthy JavaScript. However, four approaches have been proposed:

  1. The simplest approach is to use a servlet to locate resources:

    public class ResourceServlet extends HttpServlet {    public void doGet(HttpServletRequest req, HttpServletResponse resp)          throws ServletException, IOException {       String resourcePath = req.getPathInfo();       if (resourcePath == null) return;       InputStream in = getClass().getResourceAsStream(resourcePath);       if (in == null) return;       OutputStream out = resp.getOutputStream();       int ch;       while ((ch = in.read()) != -1) out.write(ch);    } }

    Place the servlet code in the same JAR file as the component renderer and the image resources. Render URLs of the form resource/resourcePath.

    Unfortunately, this approach requires the user of your component to edit the web.xml of their application. They need to add these elements:

    <servlet>    <servlet-name>Resource Servlet</servlet-name>    <servlet-class>com.corejsf.ResourceServlet</servlet-class> </servlet> <servlet-mapping>    <servlet-name>Resource Servlet</servlet-name>    <url-pattern>/resource/*</url-pattern> </servlet-mapping>
  2. The MyFaces components use a servlet filter (see http://myfaces.apache.org/tomahawk/extensionsFilter.html). This also requires editing of web.xml.

  3. The Weblets project (https://weblets.dev.java.net/doc/introduction.html) augments the JSF view handler. The approach is a bit more complex and requires a weblet-config.xml file for resource versioning and controlling the URL pattern for retrieving resources. Check out this project for a generic and robust solution.

  4. The JSF Extensions project (https://jsf-extensions.dev.java.net/) contains a phase listener for fetching resources. This approach is attractive because the listener can be specified in the META-INF/faces-config.xml file of the component JAR file. We implemented a slight variation of this approach.

Listing 13-27 shows the phase listener implementation. The spinnerLib project in the companion code builds a component file spinnerLib.jar that contains a spinner with graphical buttons. (We leave it as an exercise to the reader to arrange the buttons more nicely.) The resourceLocatorTest application in the companion code contains an application that uses the spinner. Note that the application needs to do nothing about resource loading; it simply contains spinnerLib.jar in the WEB-INF/lib directory.

Listing 13-27. spinnerLib/src/java/com/corejsf/ResourcePhaseListener.java

  1. package com.corejsf;   2.   3. import java.io.IOException;   4. import java.io.InputStream;   5. import java.io.OutputStream;   6. import java.util.HashMap;   7. import java.util.Map;   8. import javax.faces.FacesException;   9. import javax.faces.application.ViewHandler;  10. import javax.faces.context.ExternalContext;  11. import javax.faces.context.FacesContext;  12. import javax.faces.event.PhaseEvent;  13. import javax.faces.event.PhaseId;  14. import javax.faces.event.PhaseListener;  15. import javax.servlet.http.HttpServletResponse;  16.  17. public class ResourcePhaseListener implements PhaseListener {  18.  19.    public static final String RESOURCE_PREFIX = "/resource";  20.  21.    public static final String RESOURCE_LOCATION_PARAM = "r";  22.  23.    public static final String CONTENT_TYPE_PARAM = "ct";  24.  25.    public static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";  26.  27.    private Map<String, String> extensionToContentType = null;  28.  29.    public ResourcePhaseListener() {  30.       extensionToContentType = new HashMap<String, String>();  31.       extensionToContentType.put(".js", "text/javascript");  32.       extensionToContentType.put(".gif", "image/gif");  33.       extensionToContentType.put(".jpg", "image/jpeg");  34.       extensionToContentType.put(".jpeg", "image/jpeg");  35.       extensionToContentType.put(".png", "image/png");  36.    }  37.  38.    public PhaseId getPhaseId() {  39.       return PhaseId.RESTORE_VIEW;  40.    }  41.  42.    public void beforePhase(PhaseEvent phaseEvent) {  43.    }  44.  45.    public void afterPhase(PhaseEvent event) {  46.       if (event.getFacesContext().getViewRoot().getViewId().startsWith(  47.             RESOURCE_PREFIX)) {  48.          FacesContext context = event.getFacesContext();  49.          ExternalContext external = context.getExternalContext();  50.  51.          String resourcePath =  52.                (String) external.getRequestParameterMap().get(  53.                      RESOURCE_LOCATION_PARAM);  54.          if (resourcePath == null)  55.             return;  56.  57.          String contentType =  58.                (String) external.getRequestParameterMap().get(  59.                      CONTENT_TYPE_PARAM);  60.          if (contentType == null) {  61.             int extensionIndex = resourcePath.lastIndexOf(".");  62.             if (extensionIndex != -1)  63.                contentType =  64.                      extensionToContentType.get(resourcePath  65.                            .substring(extensionIndex));  66.             if (contentType == null)  67.                contentType = DEFAULT_CONTENT_TYPE;  68.          }  69.  70.          InputStream in = getClass().getResourceAsStream(resourcePath);  71.          HttpServletResponse servletResponse =  72.                (HttpServletResponse) external.getResponse();  73.          try {  74.             OutputStream out = servletResponse.getOutputStream();  75.             servletResponse.setContentType(contentType);  76.             int ch;  77.             while ((ch = in.read()) != -1)  78.                out.write(ch);  79.          } catch (IOException ex) {  80.             throw new FacesException(ex);  81.          }  82.          context.responseComplete();  83.       }  84.    }  85.  86.    /**  87.     * Returns a URL for fetching a resource through this listener  88.     *  89.     * @param context the faces context  90.     * @param String resourcePath the path to the resource  91.     * @param String contentType the content type to include in the URL, or null  92.     *           if no content type should be included  93.     * @return the URL of the form  94.     *         /appname/resource.faces?r=resourcePath,ct=contentType or  95.     *         /appname/faces/resource?r=resourcePath,ct=contentType  96.     */  97.     public static String getURL(FacesContext context, String resourcePath,  98.           String contentType) {  99.        ViewHandler handler = context.getApplication().getViewHandler(); 100. 101.        String url = handler.getActionURL(context, RESOURCE_PREFIX); 102.        StringBuilder r = new StringBuilder(url); 103.        r.append("?" + RESOURCE_LOCATION_PARAM + "=").append(resourcePath); 104.        if (contentType != null) 105.           r.append("," + CONTENT_TYPE_PARAM + "=").append(contentType); 106.        return r.toString(); 107.    } 108. }     

How Do I Package a Set of Tags into a JAR File?

If you designed components, validators, or converters that are reusable across multiple projects, you will want to package them into JAR files so that they can be added to the WEB-INF/lib directory of any web application.

You will want to make the JAR file self-contained so that users do not have to worry about editing tag library descriptor or configuration files. Follow these steps:

1.

Place a TLD file into the META-INF directory. The TLD file should contain a uri element that users of your library can reference in their JSF pages.

2.

Place a file named faces-config.xml into the META-INF directory that contains the required component, validator, and converter elements.

3.

Place any resource bundles and configuration files together with your classes. Load them with ResourceBundle.getBundle or Class.getResourceAsStream.

4.

Avoid name clashes by using an appropriate prefix for the global names, such as component names, message keys, or loggers, used by your implementation.

For example, Figure 13-16 shows the directory structure of the spinner component library that was discussed on page 673.

Figure 13-16. The directory structure of a spinner component library


How do I Get the Form ID for Generating document.forms[id] in JavaScript?

Some components will need to generate JavaScript code to access the current form, either to submit it or to access fields contained in the form.

The JSF API has no convenience method for finding the form ID. Use the following code:

  UIComponent parent = component;   while (!(parent instanceof UIForm)) parent = parent.getParent();   String formId = parent.getClientId(context);

(We supply this code in the getFormId method of the com.corejsf.util.Renderers class.)

You can now render JavaScript commands, like this:

  String command = "document.forms['" + formId + "'].submit()";

How Do I Make a JavaScript Function Appear Only Once Per Page?

Some components require substantial amounts of client-side JavaScript. If you have multiple instances of such a component in your page, you do not want to render multiple copies of the function.

To suppress multiple copies of the same code, your renderer can get the request map (facesContext.getExternalContext().getRequestMap()) and put a value of Boolean.TRUE with a key that indicates the component type. Next time the renderer is called, it can retrieve the key to find out if it has run previously in the same request.

How Do I Carry Out Initialization or Cleanup Work?

JSF 1.2 takes advantage of the @PostConstruct and @PreDestroy annotations that are part of Java SE 6/EE 5 Common Annotations, as defined in JSR 250. If a managed bean is loaded in a Java EE 5-compliant container, methods that are annotated with @PostConstruct will be automatically called.

When a request, session, or application scope ends, the methods of all managed beans in the scope that are annotated with @PostConstruct will be invoked. Put your initialization and cleanup work into methods of your managed beans and provide the @PostConstruct and @PreDestroy annotations.

In the future, standalone web containers such as Tomcat may also choose to support these annotations.

If you use JSF 1.1 or you do not want to rely on the annotation support, you must attach listeners that carry out your initialization and cleanup work.

  • To manage application scope objects, attach a ServletContextListener. Implement the contextInitialized and contextDestroyed methods. Add the listener class to the web.xml file like this:

    <listener>    <listener-class>mypackage.MyListener</listener-class> </listener>
  • To manage session scope objects, attach an HttpSessionListener. Implement the sessionCreated and sessionDestroyed methods. Add the listener class to the web.xml file as in the preceding case.

  • To manage request scope objects, attach a PhaseListener. (Phase listeners are discussed in Chapter 7.) You can initialize objects in the beforePhase method when the phase ID is APPLY_REQUEST_VALUES. You can clean up in the afterPhase method when the phase ID is RENDER_RESPONSE.

How Do I Store a Managed Bean Longer than Request Scope But Shorter Than Session Scope?

Obviously, most web applications need to store data beyond a single page request, but the session scope is too long. Disadvantages of session scope include:

  • Excessive memory consumption

  • Risk of stale data

  • Problems with multiple windows that refer to different states

The last issue is particularly vexing. A user might browse for information, then open another window and continue along another path in the other window to compare the results.

Ideally, an application would like to keep state for a conversation that involves a sequence of related screens.

A number of approaches have been proposed to solve this problem.

  1. The Apache MyFaces components library contains a nonvisual saveState component (http://myfaces.apache.org/tomahawk/uiSaveState.html). You use the component to add beans or bean properties to the view state when it is saved, and to restore them when the view is restored. Combine this with client-side state saving, and you have a simple ad hoc solution. Place components such as the following onto some of your pages:

    <t:saveState  value="#{myBean1}"/> <t:saveState  value="#{myBean2.myproperty}"/>

    The state is carried between pages with matching components, and it is discarded when a page is reached that does not have a matching component.

  2. The JSF Extensions project (https://jsf-extensions.dev.java.net) imitates the "flash" scope of Ruby on Rails. The flash is a map whose entries have a limited lifetime. When you place an entry into the flash, it available in the next page and discarded when the user moves to a subsequent page. The JSF Extensions project defines a top-level variable flash in the expression language for accessing the flash.

  3. With the dialog manager in the Apache Shale project (http://struts.apache.org/struts-shale/features-dialog-manager.html), you define a sequence of pages that make up a "dialog." The dialog manager allows you to consider the page sequence as a reusable unit. One of the features is a dialog scope that holds entries while the dialog is in progress and removes them when the dialog is complete.

  4. The Seam framework (http://www.jboss.com/products/seam) allows you to group related pages into a "conversation." Through the use of annotations, beans can be scoped to live in a conversation. It is possible to have multiple conversations, involving the same pages, in separate browser windows. These concepts are likely to find their way into a future version of JSF via JSR 299 (http://jcp.org/en/jsr/detail?id=299). For more information on the Seam framework, see Chapter 12.

How Do I Extend the JSF Expression Language?

Sometimes it is useful to extend the expression language. For example, the JNDI integration in Shale allows you to use expressions such as #{jndi['jdbc/CustomerDB']} for making JNDI lookups. (See http://struts.apache.org/struts-shale/features-jndi-integration.html for more information.) This was achieved by adding a resolver that processes an expression base.property (or the equivalent base[property]), where base is the string "jndi" and property is the JNDI name.

In JSF 1.2, you extend the ELResolver class to implement a resolver. The key method is

  public Object getValue(ELContext context, Object base, Object property)

If your resolver knows how to resolve the expression base.property, then you call

  context.setPropertyResolved(true);

and return the value of the expression.

There are several other methods for type inquiry and builder tool support; see the API documentation for details.

Next, we view a concrete example: looking up components by ID. Consider, for example, the expression

  view.loginForm.password.value

We want to find the component with the ID loginForm inside the view root, then the component with the ID password inside the form, and then call its getValue method. Our resolver will handle expressions of the form component.name:

  public class ComponentIdResolver extends ELResolver {      public Object getValue(ELContext context, Object base, Object property) {         if (base instanceof UIComponent && property instanceof String) {            UIComponent r = ((UIComponent) base).findComponent((String) property);            if (r != null) {               context.setPropertyResolved(true);               return r;            }         }         return null;      }      ...   }     

Note that our resolver is called to resolve the first two subexpressions (view.log-inForm and view.loginForm.password). The last expression is resolved by the managed bean resolver that is part of the JSF implementation.

The initial expression view is a special case. Resolvers are called with base set to null and property set to the initial expression string. The JSF implicit object resolver resolves that expression, returning the UIViewRoot object of the page.

As another example, we build a resolver for system properties. For example, the expression

  sysprop['java.version']

should return the result of calling

  System.getProperty("java.version");

To make matters more interesting, the expression

  sysprop.java.version

should also work. This custom resolver must handle the special case in which the base is null and the property is "sysprop". It must also deal with partially complete subexpressions such as sysprop.java.

We collect the list of expressions in a nested class SystemPropertyResolver.Partial-Resolution. Our resolver distinguishes two cases:

  1. If base is null and property is "sysprop", return an empty PartialResolution object.

  2. If base is a PartialResolution object and property is a string, add the property to the end of the list. Then try to look up the system property whose key is the dot-separated concatenation of the list entries. If the system property exists, return it. Otherwise, return the augmented list.

The following code excerpt illustrates these cases:

  public class SystemPropertyResolver extends ELResolver {      public Object getValue(ELContext context, Object base, Object property) {         if (base == null && "sysprop".equals(property)) {            context.setPropertyResolved(true);            return new PartialResolution();         }         if (base instanceof PartialResolution && property instanceof String) {            ((PartialResolution) base).add((String) property);            Object r = System.getProperty(base.toString());            context.setPropertyResolved(true);            if (r == null) return base;            else return r;          }         return null;       }      ...      public static class PartialResolution extends ArrayList<String> {         public String toString() {            StringBuilder r = new StringBuilder();            for (String s : this)             {               if (r.length() > 0) r.append('.');               r.append(s);             }            return r.toString();          }       }   }     

To add the custom resolver to your JSF application, add elements such as the following to faces-config.xml (or another application configuration file):

  <application>      <el-resolver>com.corejsf.ComponentIdResolver</el-resolver>      ...   </application>

You will find the complete implementation for the two sample resolvers in the ch12/resolver example of the companion code.

Note

In JSF 1.1, modifying the expression language is a bit more cumbersome. The JSF 1.1 implementation provides concrete subclasses of the abstract classes VariableResolver and PropertyResolver. A VariableResolver resolves the initial subexpression, and the PropertyResolver is in charge of evaluating the dot or bracket operator.

If you want to introduce your own variables, you supply your own variable resolver and specified it in the application configuration file, like this:

<application>    <variable-resolver>       com.corejsf.CustomVariableResolver    </variable-resolver>    ... </application>

In your resolver class, supply a constructor with a single parameter of type VariableResolver. Then the JSF implementation passes you its default variable resolver. This makes it straightforward to use the decorator pattern. Here is an example of a variable resolver that recognizes the variable name sysprop:

public class CustomVariableResolver extends VariableResolver {    private VariableResolver original;    public CustomVariableResolver(VariableResolver original) {       this.original = original;     }    public Object resolveVariable(FacesContext context, String name) {       if (name.equals("sysprop")) return System.getProperties();       return original.resolveVariable(context, name);     } }

The implementation of a PropertyResolver is similar.


The JSF implementation applies resolvers in the following order:

  1. Resolve JSP implict objects.

  2. Resolve JSF implicit objects facesContext and view.

  3. Resolve names of managed beans.

  4. Resolve names of resource bundles.

  5. Process resolvers in application configuration resources (such as faces-config.xml).

  6. Process legacy variable resolvers.

  7. Process legacy property resolvers.

  8. Process resolvers added by calling Application.addELResolver.



Core JavaServerT Faces
Core JavaServer(TM) Faces (2nd Edition)
ISBN: 0131738860
EAN: 2147483647
Year: 2004
Pages: 84

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