http://www.developer.com/

Back to article

Spinning Your Code with XSLT and JSF in Cocoon


May 3, 2004

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:

First Rendering Method: Stylesheet Only

The simplest approach is to take the XML representation of our JSF UI tree and apply to it an XSL template (via a standard Cocoon "transformer" component designed for the purpose). We can use one stylesheet for each "page" in our finished application, selecting exactly the components we require from the XML representation and calling the XSL template we want to handle that component. By using the "extension" part of XSL, we can write a single stylesheet to handle every type of component and rendering what we wish—for example, we might want to be able to display a UISelectOne as a drop-down list box. We would write our stylesheet to format a UISelectOne's XML representation into an HTML "select" element once, and put it in our "master" stylesheet. This master stylesheet then is imported in each of our page-specific stylesheets, so nothing needs to be repeated.

The master stylesheet would contain a definition like this:

<xsl:template name="UISelectOne">
  <xsl:param name="bgcolor" select="white"/>
  <xsl:param name="fontcolor" select="black"/>
  <td>
    <xsl:attribute name="bgcolor">
      <xsl:value-of select="$bgcolor"/>
    </xsl:attribute>
    <font>
      <xsl:attribute name="color">
        <xsl:value-of select="$fontcolor"/>
      </xsl:attribute>

      <select>
        <xsl:attribute name="name">
          <xsl:value-of select="local-name()"/>
        </xsl:attribute>
        <xsl:for-each select="valid-values/valid-value">
          <xsl:variable name="defval" select="../../default-value"/>
          <xsl:variable name="curval" select="@value"/>
          <option>
            <xsl:attribute name="value">
              <xsl:value-of select="@value"/>
            </xsl:attribute>
            <xsl:if test="$defval=$curval">
              <xsl:attribute name="selected"/>
            </xsl:if>
            <xsl:value-of select="@label"/>
          </option>
        </xsl:for-each>
      </select>
    </font>
  </td>
</xsl:template>

A portion of our page-specific stylesheet might look something like the following:

<xsl:template match="/response">
  <html>
    <body>
      <xsl:apply-templates select="*[@name='name']"/>
      <xsl:apply-templates select="*[@name='occupation']"/>
    <body>
  </html>
</xsl:template>

<xsl:template match="UISelectOne">
  <xsl:call-template name="UISelectOne">
    <xsl:with-param name="bgcolor" select="white"/>
    <xsl:with-param name="fontcolor" select="black"/>
  </xsl:call-template>
</xsl:template>

In the example above, we assume that the XML of the response from JSF begins with a "response" element, and contains nested UIInput, UISelect, and other elements named for their respective components. In the body of the "/response" template, we are effectively selecting the specific rendering of each element, first the element called "name", then the one called "occupation". If "occupation" were actually a UISelectOne element, the "UISelectOne" template would then match, and call the named template from the master stylesheet. (In practice, we might use namespaces, e.g. the "namespace:element" notation, to keep names from potentially conflicting.)

Second Rendering Method: Template and Stylesheet

For page developers who find XSL daunting, however, this can be a problematic approach. Cocoon provides an easy way to combine multiple XML sources into a single XML stream within a pipeline (called "aggregating"). If we write a "template" file for each page, instead of an XSL stylesheet, we can make life easier for our page designer without sacrificing any of the power of XSL. The "template" can consist mostly of well-formed HTML (or XHTML, specifically), which is an XML dialect in any case. Where the dynamic components are to appear, we can use specific XML elements to denote the element we want—ideally, using a special "namespace" to ensure it is clear we're talking about a Faces component.

Our template might look something like this:

<html>
<body>
  <form action="someAction">
    <table border="1" align="center">
      <tr>
        <td>Name:</td>
        <td><UIInput width="60" bgcolor="white"
                     fontcolor="green"/></td>
      </tr>
      <tr>
        <td>Occupation:</td>
        <td><UISelectOne bgcolor="white" fontcolor="green"/></td>
      </tr>
    </table>
  </form>
</body>
</html>

In this example, the bulk of our template is just regular (but well-formed) HTML, with special elements indicating where the substitution of the Faces components should go.

Here's how it might work:

  1. The user makes a request for a page—say, for the sake of an example, a registration form.
  2. Cocoon matches the request to our pipeline, and executes the Action. The action interacts with JSF as we've described above, and produces an XML stream containing the response.
  3. The pipeline now merges the XML response with our template file, which indicates the order and layout of our registration form.
  4. Now, we pass this merged XML through a final "master" XSL stylesheet again, which combines the instructions in the template with the response data from the JSF response XML. This stylesheet can remain the same for every request, meaning the XSL only needs to be dealt with once.

Benefits

Either of the two approaches above has significant potential benefit, but perhaps even more so for the second method. They include:

  1. Use of the significant expressive power of XSL for the component designer (or, more accurately, the designer of the Renderer for the components).
  2. Easy page design, with the page design isolated from the complexity of XSL.
  3. The page designer can, however, select the display for each element, and specify options and formatting specifications through the use of simple attributes, just like HTML (with which they are no doubt already familiar).
  4. Capabilities (validation, state-saving, isolation from application logic) of JSF.
  5. The ability to change a component's appearance in all pages by altering one stylesheet.
  6. The ability to customize a component's appearance in a single page by calling an overriding stylesheet for that page.
  7. The ability to switch output formats (to WML, PDF, XSL, SVG, or JPG) with an alternate rendering stylesheet, no application changes required.
  8. All of Cocoon's other functionality (such as easy internationalization) is available to the developer.

Hopefully, you've seen in this article that it is not only possible, but indeed quite practical, to put the new JSF standard to use with a display technology other than JSF, and have gained some idea how that might be done. A number of open-source projects are already underway to provide implementations of the JSF standard, including MyFaces, Smile, and the Keel meta-framework, from which the above examples were drawn.

About the Author

Michael Nash is the president of JGlobal Limited, a software development, consulting, training and support company specializing in open source Java technologies. He is also a core developer of the Keel meta-framework, the author of two books and a number of articles and papers about next-generation web-application development with Java, and a member of the JSR-127 (JavaServer Faces) Expert Group. He can be reached at mnash@jglobal.com.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date