Creating an Ajax-Enabled Application, a Phase Listener Approach

   
By Rick Palkovic and Mark Basler, November 2006  

Articles Index

This is the fourth in a short series of articles that add Ajax functionality to a Java EE web application developed in the NetBeans IDE.

As discussed in the previous article in this series, the JavaServer Faces technology included in the Java EE platform enables you to create your own custom components, which you can then reuse in different applications. The approach in the previous article required the custom component and its resources to be bundled and distributed with the web application.

This article describes a phase listener approach to building the custom component. In this approach, the component's resources are packaged with the component in a .jar file that is bundled with the web application. The resources are served through a phase listener on the server. To distinguish this approach from the previous one in the example project, it is called CompB.

Learn About Ajax

For background information about Ajax and strategies for implementation, see Ajax Design Strategies by Ed Ort and Mark Basler.
 

As in the previous custom component approach, the CompB custom component generates the JavaScript code needed to handle Ajax interactions with the server. Again, you use the same Java Servlet that was used in the do-it-yourself method.

The client component's resources, such as the JavaScript file and CSS, are accessed through the phase listener. The phase listener approach takes advantage of more of the power of JavaServer Faces technology than did the component approach described in the previous article in this series.

The phase listener can delegate responsibility to a managed bean's method or use a legacy servlet. In this example, you use the legacy servlet. Using the legacy servlet has two major advantages:

  • It avoids the need to rewrite server-side functionality.
  • It limits the risk of adopting new technology by making it easy to return to the original architecture.

The disadvantage of using a legacy servlet is that it does not take advantage of the inherently more capable and robust managed bean approach. For information on how to use a managed bean for server-side processing, see the discussion in Using PhaseListener Approach for Java Server Faces Technology with AJAX and Accessing Resources From JavaServer Faces Custom Components

The CompB (phase listener) approach can be appropriate in the following cases:

  • When your legacy application is not based on JavaServer Faces technology, as is the case with the example application discussed here. In these cases, the data that may be affected by the Ajax call is managed by a legacy mechanism. Adding Ajax functionality tied to a custom component can reduce the risk to an existing production application by leaving critical data-handling server software in place.
  • When the Ajax call doesn't affect the JavaServer Faces view state that is associated with the web page.
  • When you're not willing to pay the performance price of saving, transferring and restoring component view state. For example, when an Ajax call is triggered often to perform polling action.
  • When the Ajax call retrieves read-only data.

The CompB approach also has its shortcomings, namely:

  • It can't handle Ajax calls that modify the component view state. Problems arise if the Ajax call modifies state that is also maintained in the serialized view state that is reconstituted when a JavaServer Faces form or action is submitted.
  • If a library's components are created by different developers, a different phase listener might be required for each component's specific needs. Multiple phase listeners could cause requests to be fulfilled multiple times, corrupting the data being served.
  • Phase listeners in the .jar files that are registered in all the faces-config.xml files bundled with your application are fired sequentially, with no guarantee to the order.
  • The very existence of multiple phase listeners creates a performance burden because all that are registered execute on every request.
 
Contents
 
Ajax Lifecycle
Getting Started
Implementing a Component Approach
Tag Library
The JavaServer Faces Config File
The CompBPhaseListener.java File
The CompBTag Tag Class
The CompBRenderer Class
The compB.css Style Sheet
The compB.js File
The Dispatcher Class
Building and Deploying the Application
Summary
Next Steps
References
 
Ajax Lifecycle

Figure 1 shows how resources for the component are accessed from the web container. The cycle begins when the user navigates to the book catalog page.

 
Figure 1: CompB Resource Information Flow
 

The steps in Figure 1 are as follows:

  1. The user navigates to the book catalog page, either by clicking from the bookstore home page or entering the catalog page's URL directly.
  2. The web server serves the page. When the page is rendered, the HTML markup provides URLs to access the resources required by the page, including the JavaScript and CSS files required by the component.
  3. As the page is loading, the browser accesses the resources using the URLs that were rendered with the page. For example, the component's JavaScript file and CSS file are referenced by the following lines in the HTML markup:
  4. <script type="text/javascript" 
                 src="/bookstore2/faces/jsf-example/compB/compB.js"></script>
    <link type="text/css" rel="stylesheet" 
                 href="/bookstore2/faces/jsf-example/compB/compB.css" />
    

  5. The resource URLs are received by the FacesServlet. The FacesServlet forwards the links to the phase listener, which is packaged with the component in the CompB.jar archive.
  6. The phase listener identifies the location of the requested resources, fetches them from the CompB.jar archive, and returns them to the phase listener.
  7. The phase listener returns the resource information through the FacesServlet to the client browser.

Figure 2 shows how data for a specific pop-up balloon is obtained through the Ajax request.

 
Figure 2: Ajax Call Lifecycle
 
  1. The user mouses over a link on the book catalog page, executing the onmouseover event handler.
  2. The onmouseover event handler calls the bpui.compB.showPopup() function in the compB.js file. This function sends a request to the PopupServlet through the XMLHttpRequest object.
  3. The PopupServlet receives the request and, using the existing BookDBA object, obtains the book title detail data and formats a response to the request.
  4. PopupServlet then returns an XML response that holds the book detail.
  5. The component-specific ajaxReturnFunction() is called when the response is returned from the PopupServlet. The ajaxReturnFunction() then extracts book detail data from the XML message, populates the pop-up balloon's table, and makes the balloon visible to the user.
 
Getting Started

This article assumes that you have downloaded and installed the latest NetBeans IDE and the example application that forms the basis of discussion. If you have not done so, refer to the first article in the series, Creating an Ajax-Enabled Application, a Do-It-Yourself Approach, and download the necessary tools now.

Note that the example project already contains files for all four implementation approaches in addition to the original application:

  • The base application, with no Ajax implementation
  • The do-it-yourself approach to Ajax
  • A toolkit approach to Ajax, using the Dojo toolkit
  • The CompA approach, which bundles component resources with the web application
  • The CompB phase listener approach that is the topic of this article
 
Implementing a Component Approach

In this, your second implementation of a JavaServer Faces component, you create a component with resources accessed by a servlet. When creating your custom component, you edit the tag library definition file ui.tld, the JavaServer Faces configuration file faces-config.xml, the tag handler CompBTag.java, and the renderer CompBRenderer.java. You also create a phase listener, CompBPhaseListener.java. The phase listener is the major difference between the CompA and CompB approaches.

 
About CompB
 

The component CompB has been precompiled into a .jar file in the project. In the Project window, you can find the file located in the project's bookstore2 > Web Pages > WEB-INF > lib folder. As you examine the source files for CompB, you will open the read-only files in this .jar file.

The source files used to build CompB.jar can be seen in the Files window, under the bookstore2 > compB node. The readme.html file in the compB folder describes how to compile the source files. In the procedures in this article, you do not edit the source files. Use the files to experiment and as the basis of your own JavaServer Faces components.

 
Opening the Bookstore Project in the NetBeans IDE
 

To see how the component is implemented, begin by opening the bookcatalog.jsp file in the NetBeans IDE . If the bookstore2 project is already open in the NetBeans IDE from your exercise in the last article, Creating an Ajax-Enabled Application, a Component Approach, skip these steps and go directly to the next section.

To open the project:

  1. Start the NetBeans IDE.
  2. From the NetBeans toolbar, choose File > Open. The Open Project window opens.
  3. Navigate to project /examples/web/bookstore2, where project is the path to your project directory.
  4. Select the bookstore2 project folder and click Open Project Folder. The IDE opens the project folder and selects bookstore2 as the main project. The bookstore project is also opened because bookstore2 depends on many of the files in bookstore.
  5. If the IDE flags the bookstore project and alerts you that references need to be resolved:

    1. Right-click the project and choose Resolve Reference Problems from the contextual menu.
    2. In the resulting window, click Resolve and navigate to the server /glassfish/lib directory, where server is the root of your GlassFish server installation.
    3. Select the javaee.jar file and open it. Click Close to resolve the reference and close the window.
  6. If the bookstore2 project also produces an alert:

    1. Right-click the project name and choose Resolve Missing Server Problem from the contextual menu.
    2. In the resulting window, select the GlassFish server that you registered in Creating an Ajax-Enabled Application, a Do-It-Yourself Approach. Click OK to resolve the reference.
 
Replacing the bookcatalog.jsp File
 

Instead of editing the existing bookcatalog.jsp file, replace it with the bookcatalog_compB.jsp file already present in the project.

  1. In the NetBeans IDE, click the Projects tab to open the Projects view of your project.
  2. Expand the bookstore2 > Web Pages > books node and select the bookcatalog.jsp file.
  3. Right-click and choose Delete from the contextual menu. In the confirmation pop-up window, click Yes.
  4. Select the bookcatalog_compB.jsp file, right-click, and choose Copy from the contextual menu.
  5. Select the books node, right-click, and choose Paste from the contextual menu. A copy of the bookcatalog_compB.jsp file appears in the list.
  6. Select the copy of the bookcatalog_compB.jsp file, right-click, and choose Rename from the contextual menu. Rename the file to bookcatalog.jsp to make it part of the project build
  7. Double-click the bookcatalog.jsp file to open it in the NetBeans Editor.
 
Examining the bookcatalog.jsp File
 

To follow the discussion of the bookcatalog.jsp file, make sure line numbers are displayed in the NetBeans Editor. To display line numbers, right-click in the left margin of the Editor window and choose Show Line Numbers from the contextual menu.

On your first view of the file, note that it is very similar to the version used for Creating an Ajax-Enabled Application, a Component Approach.

The changes in the file appear on lines 33–37, where the bpui:compB component is identified, along with the servlet that responds to the component's Ajax call:

<%@taglib prefix="bpui" uri="#" %>

<f:view>
   <bpui:compB id="pop0" url="./PopupServlet?bookId="/>
</f:view>

The onmouseover and onmouseout event handlers (lines 78–79) also have changed to reference the CompB versions of the showPopup() and hidePopup() functions.

In other ways, the bookcatalog.jsp page is the same as the CompA version.

 
Tag Library

Now, examine your project's tag library descriptor ( .tld) file. In the CompB approach, the ui.tld file is contained in the CompB.jar file. Open the file in the NetBeans Editor:

  1. In the Projects window of the NetBeans IDE, expand the bookstore2 > Libraries > CompB.jar > META-INF node.
  2. Double click the ui.tld file to open it as a read-only file in the NetBeans Editor.

In the ui.tld file, you see that tags for CompB lie between lines 13 and 67. The id, url, style, and styleClass attributes declared in the file are used the same way as the same attributes declared in the ui.tld file for CompA. One obvious difference is the definition of the <name> tag (line 15):

<name>compB</name>
 

The name is used with the taglib namespace prefix bpui in the bookcatalog.jsp file.

 
The JavaServer Faces Configuration File

In the CompB approach, you must put the component's resources in a location that the phase listener can find. When you place the CompB.jar file in the project's Libraries folder, it is deployed to the .war file's WEB-INF/lib directory. This directory is automatically searched by the phase listener when it looks for package resources. This feature enables others to easily use the components you create.

The phase listener itself is registered by means of the faces-config.xml file.

To view the faces-config.xml file:

  1. View the project hierarchy in the Projects window.
  2. Expand the bookstore2 > Libraries > compB.jar > META-INF node.
  3. Double-click faces-config.xml to open the file in the NetBeans Editor.

In the faces-config.xml file, consider lines 12–30, which configure the CompB component.

Lines 15–22 register the name and location of the renderer for the component.

Lines 26–28 register the name and location of the phase listener for the component. The phase listener handles all of the component's requests for resources and methods.

In summary, the file registers the renderer CompBRenderer and the lifecycle component CompBPhaseListener. You now examine the major difference between the CompA and CompB approaches: the phase listener.

 
The CompBPhaseListener.java File

The JavaServer Faces framework invokes the phase listener every time a request passes through the FacesServlet. The sole responsibility of the phase listener in your project is to serve the resources needed by the component.

The .java source files have been included in the compB.jar file. Such inclusion is not typical, but in discussing this example it is convenient to have the source files close to their corresponding classes.

To view CompBPhaseListener.java:

  1. View your project's Projects window.
  2. Expand the bookstore2 > Web Pages > WEB-INF > lib > compB.jar > com sun > javaee > blueprints > components > ui > example node.
  3. Double-click the CompBPhaseListener.java file to open it in the NetBeans Editor.
 
Figure 3: Opening CompBPhaseListener.java
 

Note the afterPhase() method, beginning on line 55. This method is called by the JavaServer Faces framework at each phase of the lifecycle that is returned by the getPhaseId() method. In this example, the afterPhase() method is called during the PhaseId.RESTORE_VIEW lifecycle phase. In the event that the phase listener is called for on all phases, then the getPhaseId() method returns PhaseId.ANY_PHASE, and afterPhase() is called after every request through the FacesServlet.

In line 60, the afterPhase() method obtains the application key:

int iPos=rootId.indexOf(APP_KEY);
 

The value of APP_KEY is set to /jsf-example/ in line 45. The characters jsf-example are part of the URL resource path set in CompBRenderer. This value sets the path to the resources for the component and limits the requests for which the phase listener operates, thus reducing possible side effects to other requests being made through the FacesServlet.

For example, when the afterPhase() method is passed an event, it notes that the value of APP_KEY for the event is /jsf-example/. The method then extracts the root ID ( rootId) of the event. The root ID is the actual URL of the resource that is located in the compB.jar, as constructed by the renderer CompBRenderer.

In CompBPhaseListener.java, the URL for the JavaScript resource that is constructed in the afterPhase() method in line 65 is:

PATH_PREFIX + rootId
 

In the project build, the URL resolves to:

http://localhost:8080/faces/jsf-example/compB/compB.js
 

The if-else statements in lines 64–73 of the afterPhase() method guarantee that JavaScript, CSS, and image content types are handled properly and can be found by resource consumers. These lines test to see that the application key exists and that the resource URL is one of the types declared in lines 40–44. If the suffix is recognized as a resource, afterPhase() constructs a relative URL for the resource and passes it to the handleResourceRequest() method.

The handleResourceRequest() method (lines 100–127) is straightforward. Line 103 finds the fully qualified URL given the relative URL for the resource:

URL sxURL = CompBPhaseListener.class.getResource(resource);
 

The getResource() method allows the servlet container to make a resource available to servlets from any source. Resources can be located on a local or remote file system, in a database, or in a .war file. For example, the relative URL of the CompB component's JavaScript file is /jsf-example/compB/compB.js. The getResource() method finds the resource in a local class path in the project's .war file, in the path web/WEB-INF/lib/compB.jar/META-INF/jsf-example/compB/compB.js.

If the resource cannot be found, the getResource() method returns null, and the try-catch block in lines 110–119 prints an error message when the readWriteBinaryUtil() method throws an exception. That method simply reads in the resources.

Note that you, as the component developer, are responsible for creating resources and placing them in the proper locations. After the component has been successfully packaged, it can be used without regard to such details.

So, the handleResoureRequest() method looks up the resource, composes its full URL, and returns the URL along with the resource content type. Line 112 sets the response status to 200, which indicates that the resource was found successfully. It then opens a stream and puts back a binary stream.

The method that reads and writes the binary stream is readWriteBinaryUtil(), shown in lines 142–169. The reason to use a stream is because not all resources are character data. The binary stream allows image resource data to be transferred.

As an aside, note that the images folder that holds the pop-up balloon's corner images are located in the same directory in your project as the compB.css style sheet. The style sheet finds the images with a relative path name, not a fully qualified one. Thus you will see references to the images in compB.css like the following (taken from line 3 of the style sheet):

./images/compB_corner_tl.gif
 

You now examine the way actions are mapped in the phase listener.

JavaServer Faces 1.2 introduced a unified expression language. In line 82, the afterPhase() method calls the expression factory:

MethodExpression mex=context.getApplication().getExpressionFactory().
               createMethodExpression(context.getELContext(),
               methodx, null, argTypes);
 

This line creates an expression out of the method for use with a managed bean. In your project you use a legacy servlet rather than a managed bean. For information on how to use a managed bean for server-side processing, see the discussion in Using PhaseListener Approach for Java Server Faces Technology with Ajax.

 
The CompBTag Tag Class

You now examine the CompBTag class referenced by the ui.tld file to see how the pop-up component's tag data is used.

To view CompBTag.java:

  1. View your project's Projects window.
  2. Expand the bookstore2 > Web Pages > WEB-INF > lib > compB.jar > com sun > javaee > blueprints > components > ui > example node.
  3. Double-click the CompBTag.java file to open it in the NetBeans Editor.

The CompBTag class extracts attribute values from the tag, populates the component, and maps to a renderer type that is registered in faces-config.xml. Its function is identical to the CompATag class in the CompA approach.

The setProperties() method, beginning on line 76, extracts the attribute values for style, styleClass, and url. Line 79 of the method extracts the inputs from the tag and puts them into the component itself:

UIOutput outComp = (UIOutput)component;
 

Then, through the getRendererType() method (lines 45–47), the JavaServer Faces framework determines which renderer type to call:

public String getRendererType() {
                  

    return ("CompB");
                  

}
                
 

The getRendererType() method maps the CompB tag to a renderer type. That renderer type is determined by the CompBRenderer class.

 
The CompBRenderer Class

To see how the CompBRenderer class executes the rendering, you now examine the CompBRenderer.java file.

  1. View your project's Projects window.
  2. Expand the bookstore2 > Web Pages > WEB-INF > lib > compB.jar > com sun > javaee > blueprints > components > ui > example node.
  3. Double-click the CompBRenderer.java file to open it in the NetBeans Editor.

In the CompBRenderer.java file, scroll to lines 45–47 of the CompBRenderer class definition:

    private static final String COMPB_SCRIPT_RESOURCE=
                 "/jsf-example/compB/compB.js";
    private static final String COMPB_CSS_RESOURCE=
                 "/jsf-example/compB/compB.css";
    private static final String COMPB_TEMPLATE_RESOURCE=
                 "/META-INF/jsf-example/compB/compB_template.txt";
 

Lines 45–46 show that the class uses the script resources of the compB.js and compB.css files. These resources display the HTML markup for the pop-up balloon and provide the information that the balloon displays.

Line 47 shows that the class uses a resource template. Much of the code that was in the CompARenderer.java file in the previous article is drawn from a template in this example. A resource template is the preferred approach because the component does not need to be recompiled whenever you change the template HTML code. To view the template contents:

  1. From your project's the Project window, expand the bookstore2 > Web Pages > WEB-INF > lib > compB.jar > META-INF > jsf-example > compB node.
  2. Double-click compB_template.txt.

Note that the CompA approach could also have made use of a resource template.

Other than renaming instances of CompA to CompB (for example, changing the namespace from bpui.compA to bpui.compB), not much is different between the CompA and CompB versions of the renderer.

 
The compB.css Style Sheet

The CSS file for the CompB implementation differs only slightly from CompA approach. To view the style sheet contents in the NetBeans Editor:

  1. In the Projects view of your project, expand the bookstore2 > Web Pages > WEB-INF > lib > compB.jar > META-INF > jsf-example > compB node.
  2. Double-click the compB.css file.

In the file, note that the class selector namespace has changed to .bpui_compB. The namespace ensures a unique name for the styles, eliminating the possibility of inadvertent duplication. The name changes are the only difference between the CompB and CompA versions of the style sheet.

Note also that the image file names under the bookstore2 > Web Pages > WEB-INF > lib > compB.jar > META-INF > jsf-example > compB > images node have been changed to begin with compB_ in order to make them unique.

 
The compB.js File

The compB.js file is almost the same as the compA.js file. Again, the namespace has changed from bpui.compA to bpui.compB. Functionally, the script does exactly the same thing as its CompA counterpart.

Note that, because of the separate namespaces, it would be possible to use both components (CompA and CompB) in the same page.

To view the compB.js file in the NetBeans Editor:

  1. In the Project view of your project, expand the bookstore2 > Web Pages > WEB-INF > lib > compB.jar > META-INF > jsf-example > compB node.
  2. Double-click the compB.js file.
 
The Dispatcher Class

The Dispatcher class finds known URL patterns, alters them if necessary, and forwards them for further processing. The dispatchers for the CompA and CompB approaches are identical. See the discussion in the previous article for details.

 
Building and Deploying the Application

Now, examine the generated HTML markup for the project by building and deploying it.

  1. Build and deploy the application by choosing Run > Run Main Project from the NetBeans menu bar, or clicking the Run button from the toolbar.
  2. The application opens in your client browser. In the deployed application, click the Start Shopping link to navigate to the book catalog page.
  3. In your browser, view the source HTML for the page (typically, choose View > Page Source or similar menu item).

The HTML code for the deployed page is very similar to that produced in the CompA approach. The only significant differences are the CompB naming conventions and the new URLs of the component resources. For example, in CompB, the JavaScript resource file is:

<script type="text/javascript" 
             src="/bookstore2/faces/jsf-example/compB/compB.js"></script>
 

Whereas, in the CompA version it is:

<script type="text/javascript" 
            src="/bookstore2/compA.js"></script>
 
 
Summary

This series of four articles shows a progression for implementing Ajax functionality in a legacy application:

  • Directly coding the web application for Ajax capability.
  • Taking advantage of the Dojo toolkit libraries.
  • Using a JavaServer Faces component whose resources are bundled with the web application (CompA).
  • Using a JavaServer Faces component with a phase listener on the server side to provide static resources for the component (CompB).

You can build and deploy the project in the NetBeans IDE using any of these approaches by using the appropriate bookcatalog.jsp file during the build.

 
The Phase Listener Approach
 

The CompB approach, using a JavaServer Faces phase listener, takes greater advantage of the JavaServer Faces framework than did the CompA approach discussed in the previous article. In the CompB approach, the JavaServer Faces component is compiled into a .jar file that is placed in the application's Web Pages/WEB-INF/lib project directory. The component sends Ajax requests to the server and handles the replies. The component can be reused by placing it in the WEB-INF/lib directory of other projects.

On the server side, Ajax calls are handled by the phase listener, which serves resources for the component. To fulfill the Ajax request on the server side, the phase listener requests data from the legacy servlet that was used with the original application. When building an application from scratch, you would more likely use a managed bean instead of a servlet.

In some circumstances, the use of a JavaServer Faces component with a phase listener degrades performance because the phase listener is called on every request through the FacesServlet. In the example that has been the subject of these articles, the performance penalty is minimal because Ajax requests occur infrequently.

Other advantages of the CompB approach are the same as those of CompA, namely:

  • Encapsulation of functionality in the component, which aids in troubleshooting and limits the risk of adopting new technology. In the bookstore application, you can remove one line from the bookcatalog.jsp file and return to your original application, affecting only one page.
  • Ability to use multiple components on a page.
  • Reusability of the component in other pages and applications.
 
Next Steps

Check the Hands-On Java EE page for a follow-on article that describes state management with JavaServer Faces technology. Managing application state exploits the full capabilities of JavaServer Faces technology.

 
References
 
Developer Services and Training
 
Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
If you would like a reply to your comment, please submit your email address:
Note: We may not respond to all submitted comments.