XSLT

Name

Type

Required

Default

Purpose

templateName

String

Yes

n/a

The name of the Velocity template to use. The meaning of this property will depend on how Velocity is configured to load templates. The template will normally be located within the WAR.

exposeDataFormatter

boolean

No

false

Should we add a SimpleDateFormat object, initialized for the request locale, to the Velocity context?

exposeCurrencyFormatter

boolean

No

false

Should we add a NumberFormat object, initialized for the request locale, to the Velocity context?

poolSize

int

No

40

The number of Velocity writers to use to handle this page. Refer to Velocity documentation for further information on this optimization.

This section discusses how the MVC web framework supports XSLT views.

Installing Domify

As JAXP is part of J2EE 1.3, the supporting libraries are guaranteed to be in place. However, the framework's built-in XSLT support depends on the open source Domify library (discussed in Chapter 6 and Chapter 13) to expose JavaBeans as XML nodes. The /lib/runtime/common package in the sample application download contains domify.jar, the small JAR file required. This must be copied to the /WEB-INF/lib directory of WARs using XML domification. Again, the Ant build script for the sample application handles this.

Implementing the View Interface for XSLT

As with JSP and Velocity views, we'll need one view instance for each XSLT stylesheet. However, this implementation can be provided by a framework; there is no need for application code to use XSLT directly.

The com.interface21.web.servlet.view.xslt.XsltView standard implementation, like the view implementations we've already seen, extends the com.interface21.web.servlet.view.AbstractView convenience superclass. It uses Domify to convert JavaBean model data to XML form (if necessary) before performing XSLT transforms using the JAXP standard API.

The underlying XML parser and XSLT transformation engine may vary between servers, as JAXP insulates code using it from the actual implementation. With JBoss/Jetty, the products used are Apache Xerces and Apache Xalan respectively - the commonest choices.

Performing XSLT transforms

The XsltView class exposes a bean property enabling the stylesheet location to be set to a URL within the WAR:

    public void setStylesheet (String url) {      this.url = url;    } 

On initialization, each instance of XsltView will obtain a new instance of the
javax.xml.transform.TransformerFactory interface:

     this. transformerFactory = TransformerFactory.newInstance() ; 

It can use this object to create a javax.xml.transform.Templates object: a compiled, threadsafe representation of the stylesheet that can cheaply return javax.xml.transform.Transformer objects for use with the stylesheet:

    private void cacheTemplates() {      if (url != null && !" .equals (url)) {        Source s = getStylesheetSource (url) ;        try {          this . templates = transformerFactory. newTemplates (s) ;                                                                }        catch (TransformerConfigurationException ex) {         throw new ApplicationContextException (           "Can't load stylesheet at "' + url + "' in XsltView with name "' +           getName() + " ' ", ex) ;       }     }   } 

The XsltView class's getStylesheetSource() method returns a javax.xml.transform.Source object given a URL. The default implementation uses the ServletContext.getRealPath() method to find the file system location of the URL within the WAR. This works fine in all containers I've tried, but isn't guaranteed to be portable (imagine, for example, a container that extracted WAR contents to a database, not a file system), so this method must be overridden by a custom subclass for use in containers that don't allow such file access:

    protected Source getStylesheetSource(String url)        throws ApplicationContextException {      String realpath = getWebApplicationContext().getServletContext().                          getRealPath(url);      if (realpath == null)        throw new ApplicationContextException(          "Can't resolve real path for XSLT stylesheet at "'+ url +          "; probably results from container restriction: " +          "override XsltView.getStylesheetSource() to use an " +          "alternative approach to getRealPath()");        Source s = new StreamSource(new File(realpath));                                                                             return s;    } 

Note 

Warning: The ServletContext.getRealPath() method is potentially non-portable. However, it's so useful in practice that many web application frameworks and packages for use in web applications use it.

The actual transformation is accomplished in the doTransform() method, which writes the result to the response. Note that we create a javax.xml.transform.Transformer object from the cached Templates object to perform the transform. We can't cache a Transformer object, instead of a Templates object, as Transformers are not threadsafe. Thus use of a cached Templates object is an essential performance optimization; we don't want to have to create a new Transformer from scratch, requiring the XSLT stylesheet to be parsed and its structure analyzed, for each transform:

    protected void doTransform(HttpServletResponse response, Node dom)        throws IOException, ServletException {      try {        Transformer trans = this.templates.newTransformer() :                                                                          trans.setOutputProperty(OutputKeys.INDENT, "yes");        trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount",          " 2 ") ;        trans.transform(new DOMSource(dom), new StreamResult(new          BufferedOutputStream(response.getOutputStream())));                                                                    }    catch (TransformerConfigurationException ex) {      throw new ServletException(            "Couldn't create XSLT transformer for stylesheet ' " + url +            " ' in XSLT view with name=' " + getName() + " ' ", ex);    }    catch (TransformerException ex) {    throw new ServletException(          "Couldn't perform transform with stylesheet ' " + url +          " ' in XSLT view with name= ' " + getName() + " ' ", ex);      }    } 

Although I've set two Xalan-specific properties here, this won't prevent the code working with another XSLT engine; they'll simply be ignored.

The implementation of the renderMergedOutputModel() method will expose the contents of our model map as a single XML document, "domifying" JavaBean objects if data isn't already XML:

    protected void renderMergedOutputModel (Map model,        HttpServletRequest request, HttpServletResponse response)       throws ServletException, IOException {      if (model == null)        throw new ServletException ("Cannot do XSLT transform on null model");      Node dom = null;      String docRoot = null;      Object singleModel = null; 

If we have a single model object in our model map, we must check whether it already contains XML data. If it does, we simply expose it to the XML view:

    if (model .size() == 1) {      docRoot = (String) model .keySet(). iterator() .next();      singleModel = model .get (docRoot) ;    }    if (singleModel != null && (singleModel instanceof Node)) {      dom = (Node) singleModel;    } else { 

If we have multiple model objects or if a single model object isn't in XML format, we'll need to "domify" the entire model map. We use the docRoot bean property value to provide a name for the model:

    try {     addRequestInfoToModel (model, request) ;     dom = this .domAdapter. adapt (model,       (docRoot == null) ? this. root : docRoot);                                                                               }   catch (RuntimeException rex) {   throw new ServletException ("Error domifying model in XSLT view " +                               "with name= ' " + getName() + " ' ", rex);   } } 

Now we have XML data in the dom object, we can invoke the XML transform:

     doTransform(response, dom) ;   } 

This takes care of interaction with the JAXP API. Java application code won't need to perform any XSLT handling or even XML handling unless it's relevant to the application's business requirements.

Date and Currency Formatting Support

The framework's XsltView also solves another common problem. XSLT doesn't provide support for date formatting, so, as with Velocity, we need to help XSLT out in this regard (XSLT 2.0 is likely to add a date formatting capability modeled on Java's java.text.SimpleDateFormat). We also should be able to provide the XLST stylesheet with information about the user's locale, to allow it to initiate date formatting in the correct style. Although the user's locale is available from the HttpServletRequest, it's not part of our data model (and nor should it be).

We add additional information to the model before domification to expose this information as follows:

   model. put (REQUEST_INFO_KEY, new RequestInfo (request) ); 

The com.interface21.web.servlet.view.RequestInfo class, which can also be used for other view technologies, exposes the following properties, each returning a standard two-letter code such as GB or en:

    public String getCountry();     public String getLanguage() ; 

These codes can drive lookups in XSLT or be used as arguments to Java XSLT extension functions that provide date formatting functionality to XSLT. XSLT provides a simple Java language binding that allows XSLT stylesheets to invoke Java methods using reflection. While Java extension functions can be abused (they offer a small back door towards abuse of XSLT: imagine, for example, what extension functions that performed JDBC operations might do to maintainability), they are occasionally very useful, and are easy to use. The present use - to enhance the capabilities of XSLT view formatting in an area in which it is weak - is a good example of appropriate use.

Note 

Note that I've borrowed the ideas behind this approach from an excellent article in Java World by Taylor Cowan on XSLT extension functions: see http://www.javaworld.com/javaworld/jw-12-2001/jw-1221-xslt-p2.html.

However, the use of extension functions reduces the portability of stylesheets, which otherwise don't depend on Java, so their use should be kept to a minimum.

The com.interface21.web.servlet.view.xslt.FormatHelper class exposes two static methods that enable us to format dates and currency amounts:

    public static Node dateTimeElement (long date, String language,      String country);    public static String currency (double amount, String language,      String country) ; 

The dateTimeElement() method returns an XML Node, rather than literal content, built using a SimpleDateFormat object for the specified locale. (We can use the JAXP API to create nodes in an extension function.) This new node is not included in the XML input document, but will look like this:

    <formatted-date>            <month>July</month>            <day-of-week>Tuesday</day-of-week>            <year>2002</year>            <day-of-month>23</day-of-month>            <hours>11</hours>            <minutes>53</minutes>            <am-pm>AM</am-pm>    </formatted-date> 

Please see the source for the FormatHelper class for the implementation of the dateTimeElement() method. If we create a stylesheet rule for the <formatted-date> element we can now format the information as we desire as follows:

    <xsl:apply-templates select="format:dateTimeElement(when/time, 'en', 'GB') "/> 

The currency() method of the FormatHelper class simply returns a string containing a formatted currency amount, such as £3.50 or $3.50. We can use it like this:

    <xsl:value-of select="format:currency(totalPrice, 'en', 'GB')" />. 

To use these extension methods in stylesheets, all we need to do is declare a namespace for them in the root element, as follows:

 <xsl:stylesheet version="1.0"      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"      xmlns:format="com.interface21.web.servlet.view.xslt.FormatHelper"                                                       > 

Finally we need to be able to customize domification. Domify can't handle cyclic references, such as that between Performance and Show in our object model: every Performance has a parent Show that contains a list of Performance objects including the original Performance. Hence we also need to be able to exclude properties from domification to ensure that infinite loops due to cyclic references are avoided. The excludedProperties property of the XsltView class is a CSV list of entries of the form <concrete class>.<property name> that tells Domify which properties to suppress. We can also use this to simplify the DOM document exposed by Domify. We'll see an example of this below.

Defining XSLT Views for Use in an Application

All we need to do in application code is to declare new view beans of type XsltView in /WEB-INF/classes/views.properties, specifying the name of the document root, template URL (within the WAR), and any properties we wish to exclude. The definition for our sample view is:

    showReservation.class=com.interface21.web.servlet.view.xslt.XsltView    showReservation.root=reservationInfo    showReservation.stylesheet=/xsl/showReservation.xsl    showReservation.excludedProperties=      com.wrox.expertj2ee.ticket.referencedata.support.ShowImpl.performances 

Note that if we don't specify a stylesheet URL, the output will display the input XML document. This is very useful functionality during development, which I omitted from the code listings above for simplicity:

Name

Type

Required

Default

Purpose

root

String

Varies

null

Root of the generated XML document. Required unless the model contains a single element (in which case the attribute name is used). If the single element is an XML Node, it is used unchanged as the basis for the model, and any value of the root property is ignored.

stylesheet

String

No

Default behavior is to show input XML unchanged

Specifies the location of the XSLT stylesheet within WAR. If no value is specified for this property, the XML document will be written to the response.

cache

boolean

No

true

Whether the XSLT stylesheet should be cached in compiled form when the view is initialized, or whether the stylesheet should be reread with each response generated. A value of true is useful during debugging, as it enables the stylesheet to be changed without restarting the application, but should not be used in production.

Excluded Properties

CSV-format String

No

n/a

Set of properties (on fully-qualified classes) that should be excluded from "domification". See discussion and example above.

Important 

Although the framework's default XsltView implementation uses the Domify library to convert JavaBean model data to XML form, and TrAX to perform XSL transforms, an alternative implementation could use alternative libraries to achieve this. For example, model-specific implementations (as opposed to the generic implementation described here) could use XML data binding to achieve higher performance.



Expert One-on-One J2EE Design and Development
Microsoft Office PowerPoint 2007 On Demand
ISBN: B0085SG5O4
EAN: 2147483647
Year: 2005
Pages: 183

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