Monday, September 10, 2007

Part I: Struts 2 XML experiments using XSLTResult

I started teaching myself Struts 2 recently and it includes a new way to work with XML. Essentially, Struts 2 Actions are all POJOs. The usual default ResultType for a Struts action is a DispatcherResult (which renders JSPs and such).

Alternatively you can use the XSLTResult to work with XML/XSLT as your view technology. When I first looked at XSLTResult I posted a couple of queries on the Struts mailing list. At that time I didn't understand XSLTResult and am now pleasantly surprised. In this and my next blog entries I will be answering my own queries.

XSLTResult attempts to convert your Struts Action POJO into XML using various adapter classes. The default behaviour of the XSLTResult is convert the entire Action POJO into some sort of XML, this is normally *not* what you want to happen. Via the Struts config result parameters you can choose to include or exclude the POJO attributes you are interested in (using matchingPattern/excludingPattern) or you can explicitly target an attribute to access (using exposedValue).

Via the Struts mailing list, I also found that the best way to control exactly what XML gets created by the Struts Action is to have a method that creates a Document and target that specific method.

Here are some examples of how XSLTResult works. There are no XSL stylesheets used in this blog entry! XSLResult can be used to output XML from a Struts Action POJO and this is what the following examples will be doing. By additionally specifying a stylesheet you can further process the resultant XML.

Example.java: Struts Action POJO

The following Struts Action POJO class establishes various attributes in different formats (string, array and Document). This will be used in the following experiments.


package example;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public class Example extends ExampleSupport {

public String execute() throws Exception {
return SUCCESS;
}

public String getHello(){
return "Hello";
}

public String[] getMonths(){
String months[] =
{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"July", "Aug", "Sep", "Oct", "Nov", "Dec"};
return months;
}

public Document getBookmarks() throws ParserConfigurationException, SAXException, IOException{
String xml ="<xbel>" +
"<bookmarks href="\"http://www.google.com\">"" +
"<title>Google</title>" +
"</bookmarks>" +
"</xbel>";
return xmlString2Document(xml);
}

private Document xmlString2Document(String xml) throws UnsupportedEncodingException, ParserConfigurationException, SAXException, IOException{
byte[] byteArray = xml.getBytes("UTF-8");
ByteArrayInputStream baos = new ByteArrayInputStream(byteArray);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document doc = factory.newDocumentBuilder().parse(baos);
return doc;
}
}

Example 1: Default XSLTResult behaviour

The default behaviour of XSLTResult is to show the XML output conversion. Without specifying any further parameters this will attempt to serialize the entire bean (which depending on the bean could be a very costly action computationally). This shows what the above example bean produces on my system.

Struts Config Snippet: Default XSLTResult (no location XSL)


<action name="XML1" class="example.Example">
<result type="xslt"/>
</action>

<?xml version="1.0" encoding="UTF-8"?>
<result>
<actionErrors />
<actionMessages />
<xbel>
<bookmarks href="http://www.google.com">
<title>Google</title>
</bookmarks>
</xbel>
<errorMessages />
<errors />
<fieldErrors />
<hello>Hello</hello>
<locale>
<ISO3Country>GBR</ISO3Country>
<ISO3Language>eng</ISO3Language>
<country>GB</country>
<displayCountry>United Kingdom</displayCountry>
<displayLanguage>English</displayLanguage>
<displayName>English (United Kingdom)</displayName>
<displayVariant>
</displayVariant>
<language>en</language>
<variant>
</variant>
</locale>
<months>
<item>Jan</item>
<item>Feb</item>
<item>Mar</item>
<item>Apr</item>
<item>May</item>
<item>Jun</item>
<item>July</item>
<item>Aug</item>
<item>Sep</item>
<item>Oct</item>
<item>Nov</item>
<item>Dec</item>
</months>
<texts>null</texts>
</result>
Example 2: Accessing String attributes

Struts Config Snippet: XSLTResult (string, no location XSL)

This time I have specified the value of the bean attribute that I am interested in, the string called hello (returned by getHello()).


<action name="XML2" class="example.Example">
<result type="xslt">
<param name="exposedValue">hello</param>
</result>
</action>

<result>Hello</result>
Example 3: Accessing Array attributes

Struts Config Snippet: XSLTResult (array, no location XSL)

This time I specify that I want an array displayed as XML (returned by getMonths()).


<action name="XML2" class="example.Example">
<result type="xslt">
<param name="exposedValue">months</param>
</result>
</action>

<result>
<item>Jan</item>
<item>Feb</item>
<item>Mar</item>
<item>Apr</item>
<item>May</item>
<item>Jun</item>
<item>July</item>
<item>Aug</item>
<item>Sep</item>
<item>Oct</item>
<item>Nov</item>
<item>Dec</item>
</result>
Example 4: Accessing Document attributes

Struts Config Snippet: XSLTResult (Document, no location XSL)

On this occasion the method returns a Document type which is displayed as a faithful serialisation of the Document tree. I think this method provides the most control, although we should be aware that there can be limitations when working with large DOM trees.


<action name="XML2" class="example.Example">
<result type="xslt">
<param name="exposedValue">bookmarks</param>
</result>
</action>

<xbel>
<bookmarks href="http://www.google.com">
<title>Google</title>
</bookmarks>
</xbel>
Example 5: Accessing Multiple attributes

Struts Config Snippet: XSLTResult (Combining parameters with OGNL, no location XSL)

Using OGNL notation you can even specify a list of attributes in which you are interested. Where there appears to be no way to pass parameters directly into the XSLT processing using this technique it is easy enough to add parameter like data into a transformation.


<action name="XML2" class="example.Example">
<result type="xslt">
<param name="exposedValue">{months,bookmarks}</param>
</result>
</action>

<result>
<xbel>
<bookmarks href="http://www.google.com">
<title>Google</title>
</bookmarks>
</xbel>
<item>
<item>Jan</item>
<item>Feb</item>
<item>Mar</item>
<item>Apr</item>
<item>May</item>
<item>Jun</item>
<item>July</item>
<item>Aug</item>
<item>Sep</item>
<item>Oct</item>
<item>Nov</item>
<item>Dec</item>
</item>
</result>

To answer the first part of my query: "How to pass parameters to XSL in XSLTResult", you don't. If you want to pass parameters data then this should be included as part of the XML source.

In my next blog entry I will answer the second part of my query and introduce a new XSLTResult, one which handles XSLT 2.0.