FileDocCategorySizeDatePackage
XSLTResult.javaAPI DocExample14397Mon Jul 23 13:26:44 BST 2007org.apache.struts2.views.xslt

XSLTResult

public class XSLTResult extends Object implements com.opensymphony.xwork2.Result
XSLTResult uses XSLT to transform action object to XML. Recent version has been specifically modified to deal with Xalan flaws. When using Xalan you may notice that even though you have very minimal stylesheet like this one
<xsl:template match="/result">
<result />
</xsl:template>

then Xalan would still iterate through every property of your action and it's all descendants.

If you had double-linked objects then Xalan would work forever analysing infinite object tree. Even if your stylesheet was not constructed to process them all. It's becouse current Xalan eagerly and extensively converts everything to it's internal DTM model before further processing.

Thet's why there's a loop eliminator added that works by indexing every object-property combination during processing. If it notices that some object's property were already walked through, it doesn't get any deeper. Say, you have two objects x and y with the following properties set (pseudocode):

x.y = y;
and
y.x = x;
action.x=x;

Due to that modification the resulting XML document based on x would be:

<result>
<x>
<y/>
</x>
</result>

Without it there would be endless x/y/x/y/x/y/... elements.

The XSLTResult code tries also to deal with the fact that DTM model is built in a manner that childs are processed before siblings. The result is that if there is object x that is both set in action's x property, and very deeply under action's a property then it would only appear under a, not under x. That's not what we expect, and that's why XSLTResult allows objects to repeat in various places to some extent.

Sometimes the object mesh is still very dense and you may notice that even though you have relatively simple stylesheet execution takes a tremendous amount of time. To help you to deal with that obstacle of Xalan you may attach regexp filters to elements paths (xpath).

Note: In your .xsl file the root match must be named result.
This example will output the username by using getUsername on your action class:

<xsl:template match="result">
<html>
<body>
Hello <xsl:value-of select="username"/> how are you?
</body>
<html>
<xsl:template/>

In the following example the XSLT result would only walk through action's properties without their childs. It would also skip every property that has "hugeCollection" in their name. Element's path is first compared to excludingPattern - if it matches it's no longer processed. Then it is compared to matchingPattern and processed only if there's a match.


<result name="success" type="xslt">
<param name="location">foo.xslt</param>
<param name="matchingPattern">^/result/[^/*]$</param>
<param name="excludingPattern">.*(hugeCollection).*</param>
</result>

In the following example the XSLT result would use the action's user property instead of the action as it's base document and walk through it's properties. The exposedValue uses an ognl expression to derive it's value.

<result name="success" type="xslt">
<param name="location">foo.xslt</param>
<param name="exposedValue">user$</param>
</result>
* This result type takes the following parameters:
  • location (default) - the location to go to after execution.
  • parse - true by default. If set to false, the location param will not be parsed for Ognl expressions.
  • matchingPattern - Pattern that matches only desired elements, by default it matches everything.
  • excludingPattern - Pattern that eliminates unwanted elements, by default it matches none.

struts.properties related configuration:

  • struts.xslt.nocache - Defaults to false. If set to true, disables stylesheet caching. Good for development, bad for production.
Example:

<result name="success" type="xslt">foo.xslt</result>

Fields Summary
private static final long
serialVersionUID
private static final Log
LOG
Log instance for this result.
public static final String
DEFAULT_PARAM
'stylesheetLocation' parameter. Points to the xsl.
private static final Map
templatesCache
Cache of all tempaltes.
protected boolean
noCache
Determines whether or not the result should allow caching.
private String
stylesheetLocation
Indicates the location of the xsl template.
private String
matchingPattern
Indicates the property name patterns which should be exposed to the xml.
private String
excludingPattern
Indicates the property name patterns which should be excluded from the xml.
private String
exposedValue
Indicates the ognl expression respresenting the bean which is to be exposed as xml.
private boolean
parse
private AdapterFactory
adapterFactory
Constructors Summary
public XSLTResult()

    
public XSLTResult(String stylesheetLocation)

        this();
        setStylesheetLocation(stylesheetLocation);
    
Methods Summary
public voidexecute(com.opensymphony.xwork2.ActionInvocation invocation)

        long startTime = System.currentTimeMillis();
        String location = getStylesheetLocation();

        if (parse) {
            ValueStack stack = ActionContext.getContext().getValueStack();
            location = TextParseUtil.translateVariables(location, stack);
        }


        try {
            HttpServletResponse response = ServletActionContext.getResponse();

            Writer writer = response.getWriter();

            // Create a transformer for the stylesheet.
            Templates templates = null;
            Transformer transformer;
            if (location != null) {
                templates = getTemplates(location);
                transformer = templates.newTransformer();
            } else
                transformer = TransformerFactory.newInstance().newTransformer();

            transformer.setURIResolver(getURIResolver());

            String mimeType;
            if (templates == null)
                mimeType = "text/xml"; // no stylesheet, raw xml
            else
                mimeType = templates.getOutputProperties().getProperty(OutputKeys.MEDIA_TYPE);
            if (mimeType == null) {
                // guess (this is a servlet, so text/html might be the best guess)
                mimeType = "text/html";
            }

            response.setContentType(mimeType);

            Object result = invocation.getAction();
            if (exposedValue != null) {
                ValueStack stack = invocation.getStack();
                result = stack.findValue(exposedValue);
            }

            Source xmlSource = getDOMSourceForStack(result);

            // Transform the source XML to System.out.
            PrintWriter out = response.getWriter();

            LOG.debug("xmlSource = " + xmlSource);
            transformer.transform(xmlSource, new StreamResult(out));

            out.close(); // ...and flush...

            if (LOG.isDebugEnabled()) {
                LOG.debug("Time:" + (System.currentTimeMillis() - startTime) + "ms");
            }

            writer.flush();
        } catch (Exception e) {
            LOG.error("Unable to render XSLT Template, '" + location + "'", e);
            throw e;
        }
    
protected AdapterFactorygetAdapterFactory()

        if (adapterFactory == null)
            adapterFactory = new AdapterFactory();
        return adapterFactory;
    
protected javax.xml.transform.SourcegetDOMSourceForStack(java.lang.Object value)

        return new DOMSource(getAdapterFactory().adaptDocument("result", value) );
    
public java.lang.StringgetExcludingPattern()

        return excludingPattern;
    
public java.lang.StringgetExposedValue()

        return exposedValue;
    
public java.lang.StringgetMatchingPattern()

        return matchingPattern;
    
public java.lang.StringgetStylesheetLocation()

        return stylesheetLocation;
    
protected javax.xml.transform.TemplatesgetTemplates(java.lang.String path)

        String pathFromRequest = ServletActionContext.getRequest().getParameter("xslt.location");

        if (pathFromRequest != null)
            path = pathFromRequest;

        if (path == null)
            throw new TransformerException("Stylesheet path is null");

        Templates templates = templatesCache.get(path);

        if (noCache || (templates == null)) {
            synchronized (templatesCache) {
                URL resource = ServletActionContext.getServletContext().getResource(path);

                if (resource == null) {
                    throw new TransformerException("Stylesheet " + path + " not found in resources.");
                }

                LOG.debug("Preparing XSLT stylesheet templates: " + path);

                TransformerFactory factory = TransformerFactory.newInstance();
                templates = factory.newTemplates(new StreamSource(resource.openStream()));
                templatesCache.put(path, templates);
            }
        }

        return templates;
    
protected javax.xml.transform.URIResolvergetURIResolver()
Get the URI Resolver to be called by the processor when it encounters an xsl:include, xsl:import, or document() function. The default is an instance of ServletURIResolver, which operates relative to the servlet context.

        return new ServletURIResolver(
                ServletActionContext.getServletContext());
    
protected voidsetAdapterFactory(AdapterFactory adapterFactory)

        this.adapterFactory = adapterFactory;
    
public voidsetExcludingPattern(java.lang.String excludingPattern)

        this.excludingPattern = excludingPattern;
    
public voidsetExposedValue(java.lang.String exposedValue)

        this.exposedValue = exposedValue;
    
public voidsetLocation(java.lang.String location)

deprecated
Use #setStylesheetLocation(String)

        setStylesheetLocation(location);
    
public voidsetMatchingPattern(java.lang.String matchingPattern)

        this.matchingPattern = matchingPattern;
    
public voidsetNoCache(java.lang.String val)

        noCache = "true".equals(val);
    
public voidsetParse(boolean parse)
If true, parse the stylesheet location for OGNL expressions.

param
parse

        this.parse = parse;
    
public voidsetStylesheetLocation(java.lang.String location)

        if (location == null)
            throw new IllegalArgumentException("Null location");
        this.stylesheetLocation = location;