blob: d310a6db9352357459305680ab78c688204485c2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.views.xslt;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.ValueStack;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.StrutsException;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
/**
* XSLTResult uses XSLT to transform an action object to XML.
*/
public class XSLTResult implements Result {
private static final long serialVersionUID = 6424691441777176763L;
/** Log instance for this result. */
private static final Logger LOG = LogManager.getLogger(XSLTResult.class);
/** 'stylesheetLocation' parameter. Points to the xsl. */
public static final String DEFAULT_PARAM = "stylesheetLocation";
/**
* Cache of all templates.
*/
private static final Map<String, Templates> templatesCache;
static {
templatesCache = new HashMap<>();
}
// Configurable Parameters
/** Determines whether or not the result should allow caching. */
protected boolean noCache;
/** Indicates the location of the xsl template. */
private String stylesheetLocation;
/** Indicates the property name patterns which should be exposed to the xml. */
private String matchingPattern;
/** Indicates the property name patterns which should be excluded from the xml. */
private String excludingPattern;
/** Indicates the ognl expression representing the bean which is to be exposed as xml. */
private String exposedValue;
/** Indicates the status to return in the response */
private int status = 200;
private String encoding = "UTF-8";
private boolean parse;
private AdapterFactory adapterFactory;
public XSLTResult() {
}
public XSLTResult(String stylesheetLocation) {
this();
setStylesheetLocation(stylesheetLocation);
}
@Inject(StrutsConstants.STRUTS_XSLT_NOCACHE)
public void setNoCache(String xsltNoCache) {
this.noCache = BooleanUtils.toBoolean(xsltNoCache);
}
public void setStylesheetLocation(String location) {
this.stylesheetLocation = location;
}
public String getStylesheetLocation() {
return stylesheetLocation;
}
public String getExposedValue() {
return exposedValue;
}
public void setExposedValue(String exposedValue) {
this.exposedValue = exposedValue;
}
public String getStatus() {
return String.valueOf(status);
}
public void setStatus(String status) {
try {
this.status = Integer.valueOf(status);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Status value not number " + e.getMessage(), e);
}
}
public String getEncoding() {
return encoding;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
/**
* @param parse if true, parse the stylesheet location for OGNL expressions.
*/
public void setParse(boolean parse) {
this.parse = parse;
}
public void execute(ActionInvocation invocation) throws Exception {
if (invocation == null) {
throw new IllegalArgumentException("Invocation cannot be null!");
}
long startTime = System.currentTimeMillis();
String location = getStylesheetLocation();
if (location == null) {
throw new IllegalArgumentException("Parameter 'stylesheetLocation' cannot be null!");
}
if (parse) {
ValueStack stack = invocation.getStack();
location = TextParseUtil.translateVariables(location, stack);
}
try {
HttpServletResponse response = invocation.getInvocationContext().getServletResponse();
response.setStatus(status);
response.setCharacterEncoding(encoding);
PrintWriter 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());
transformer.setErrorListener(buildErrorListener());
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.
LOG.debug("xmlSource = {}", xmlSource);
transformer.transform(xmlSource, new StreamResult(writer));
writer.flush(); // ...and flush...
LOG.debug("Time: {}ms", (System.currentTimeMillis() - startTime));
} catch (Exception e) {
LOG.error("Unable to render XSLT Template, '{}'", location, e);
throw e;
}
}
protected ErrorListener buildErrorListener() {
return new ErrorListener() {
public void error(TransformerException exception) throws TransformerException {
throw new StrutsException("Error transforming result", exception);
}
public void fatalError(TransformerException exception) throws TransformerException {
throw new StrutsException("Fatal error transforming result", exception);
}
public void warning(TransformerException exception) throws TransformerException {
LOG.warn(exception.getMessage(), exception);
}
};
}
protected AdapterFactory getAdapterFactory() {
if (adapterFactory == null) {
adapterFactory = new AdapterFactory();
}
return adapterFactory;
}
protected void setAdapterFactory(AdapterFactory adapterFactory) {
this.adapterFactory = adapterFactory;
}
/**
* @return 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.
*/
protected URIResolver getURIResolver() {
return new ServletURIResolver(ServletActionContext.getServletContext());
}
protected Templates getTemplates(final String path) throws TransformerException, IOException {
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();
factory.setURIResolver(getURIResolver());
factory.setErrorListener(buildErrorListener());
templates = factory.newTemplates(new StreamSource(resource.openStream()));
templatesCache.put(path, templates);
}
}
return templates;
}
protected Source getDOMSourceForStack(Object value) throws IllegalAccessException, InstantiationException {
return new DOMSource(getAdapterFactory().adaptDocument("result", value) );
}
}