Spinning Your Code with XSLT and JSF in Cocoon
In this article, we will explore a sophisticated and flexible approach to user-interface building for Web applications. We will explore the combination of the XML processing language XSL with the newly released JavaServer Faces (JSF) standard API, using the Apache Cocoon framework as an enabler for both.
Overview of JSF
Much has been written about the new JSF standard for UI construction, and it is easy to assume that JSF is mostly an adjunct to Java Server Pages (JSP). Most of the examples shown, after all, show JSF in conjunction with JSP and a JSF-oriented tag library to render the user interface. JSF is in fact independent of JSP, though, and easily can be used with other UI technologies. An example is included with the reference implementation of JSF, showing how it can be rendered via XUL.
In a nutshell, JSF provides a way for developers to use and create non-visual user interface components, and to associate those components with "renderers" (via a render "kit"). The components themselves are associated with application logic (e.g. JavaBeans and/or events), and the rendering is separate from the component itself. In this way, the components can be laid out into a design by a Web page designer who is not himself a programmer, for example, leaving the programmer free to concentrate on actual application logic development. JSF handles the conversion of data to and from the types used to display and input data (essentially various strings) into appropriate data types for the non-visual components. The developer simply provides the "hooks" for the UI components to tie into. If necessary, the developer also can create (or purchase, or obtain from some third party) custom JSF components, for specialized needs not met by the standard component library included with his or her JSF implementation of choice.
In addition to handling type conversion, JSF also handles preservation of UI state between requests, as well as basic (and extensible) validation. This means that the application user, presented with a form, can fill in the form, submit it, and have the same form with his data intact presented back to him if a field is not valid (perhaps he keyed in a word where only a number is allowed). For a validation this basic, the application logic is never even executed. For more sophisticated validation (such as against a database to check for a valid value), the application can simply return control to the same UI page, and the data is again preserved. All of this is done without any extra effort on the part of the application developer.
JSF prepares a "tree" of components for rendering (starting with a UIRoot element). This allows components to be nested within each other (e.g. a table containing a series of input fields, as a simple example). It is this "tree" that is handed to the renderer for processing when it is time to display the result. JSF takes components through a complete lifecycle, as we will discuss below.
Overview of Cocoon
Cocoon is a large and sophisticated project, and has many more features than we have space to talk about here—its Web site at http://xml.apache.org/cocoon gives a much more detailed look at its facets. For our purposes, though, the essential capability of Cocoon is to act as a Web interface for XML "pipelines." These pipelines can originate from many sources, and can have their contents manipulated and transformed before they are sent to the destination—in our case, a user's browser.
A Cocoon pipeline is defined in an XML configuration file (called the sitemap), which contains a sequence of elements. Usually, these include a specification for a "generator" (the source of the XML for the pipeline), one or more "transformers," (which modify the XML stream, often via XSL), and a "serializer," which writes the modified stream to the destination. Again, this is a simplification, and Cocoon pipelines are capable of much more—but we will see, even this much can make a powerful combination. One more Cocoon element we will need is an "action"—this is a component that can execute arbitrary application logic during the initialization of a pipeline. This is where we will "plug in" our application logic.
Putting JSF and Cocoon Together
Putting JSF and Cocoon together involves defining the flow of control from a URL request, to our application logic, and back to the user.
When a user requests a URL from Cocoon (as a POST or GET request), a component called a "matcher" determines which pipeline will handle the request. Let's assume that we've received a URL that matches our application pipeline: The first thing that happens is that any required pipeline initialization takes place. In our case, this includes our application logic. The request is available to the action, meaning our application has access to any field values or parameters that accompanied the request. At this point, we change hats and transfer control of the request to the JSF lifecycle.
First, a request "tree" is either created or re-created, depending on whether this is a new or repeat request. Request values then are applied to the components in the tree—passing all form fields and parameter values that were part of the original request. Now, the tree is complete, and event handling can begin, if any events are requested—we'll assume for our initial example that there are none. Next, validations are applied—this may cause the lifecycle to jump directly to the response phase if there are validation errors. If there are no errors, the model values are updated—this is where Faces passes control to our custom code that implements application logic. JavaBeans (usually) are specified to receive the model request values, and the application is then invoked.
Once application logic is complete, the appropriate model values returned (if any) from the execution of the application logic are applied to the component tree. This may result in new values being applied to existing components, or entirely new components being added. Now we get to the interesting phase, from the point of view of our exploration: the rendering response phase. This final phase of the request lifecycle is where Faces applies a RenderKit to a JSF "tree," resulting in output that can be sent to the user's browser (or other device).
Converting the JSF "tree" to a series of SAX events (or an XML document) is very straightforward because the tree already has a hierarchical structure, and appropriate methods are supplied to allow us to "walk" this structure, emitting SAX events or building an XML DOM document as we go. Each component in the tree becomes an XML element, complete with (potentially) nested elements and attributes. This is the point where we begin the pipeline itself, effectively handing control back to Cocoon.
At this point, we have XML, which is the fuel for our Cocoon pipeline, and we have a couple of choices as to how to handle it: