![JAR search and dependency download from the Maven repository](/logo.png)
org.xins.server.XSLTCallingConvention Maven / Gradle / Ivy
/*
* $Id: XSLTCallingConvention.java,v 1.61 2011/03/19 09:11:18 agoubard Exp $
*
* See the COPYRIGHT file for redistribution and use restrictions.
*/
package org.xins.server;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.xins.common.Utils;
import org.xins.common.collections.InvalidPropertyValueException;
import org.xins.common.collections.MissingRequiredPropertyException;
import org.xins.common.manageable.InitializationException;
import org.xins.common.text.TextUtils;
/**
* XSLT calling convention.
* The XSLT calling convention input is the same as for the standard calling
* convention. The XSLT calling convention output is the result of the XML
* normally returned by the standard calling convention and the specified
* XSLT.
* The Mime type of the return data can be specified in the XSLT using the
* media-type or method attribute of the XSL output element.
* More information about the XSLT calling convention can be found in the
* user guide.
*
* @version $Revision: 1.61 $ $Date: 2011/03/19 09:11:18 $
* @author Anthony Goubard
* @author Ernst de Haan
*/
public class XSLTCallingConvention extends StandardCallingConvention {
/**
* The name of the runtime property that defines if the templates should be
* cached. Should be either "true"
or "false"
.
* By default the cache is enabled.
*/
protected static final String TEMPLATES_CACHE_PROPERTY = "templates.cache";
/**
* The name of the input parameter that specifies the location of the XSLT
* template to use.
*/
protected static final String TEMPLATE_PARAMETER = "_template";
/**
* The name of the input parameter used to clear the template cache.
*/
protected static final String CLEAR_TEMPLATE_CACHE_PARAMETER = "_cleartemplatecache";
/**
* The XSLT transformer. Never null
.
*/
private final TransformerFactory _factory;
/**
* Flag that indicates whether the templates should be cached. This field
* is set during initialization.
*/
private boolean _cacheTemplates;
/**
* The prefix to use for the _template parameter.
* This field is set during initialization.
* If the value is null
the _template parameter is not allowed.
*/
private String _templatesPrefix;
/**
* Location of the XSLT templates. This field is initially
* null
and set during initialization.
*/
private String _location;
/**
* Cache for the XSLT templates. Never null
.
*/
private Map _templateCache;
/**
* Constructs a new XSLTCallingConvention
object.
*/
public XSLTCallingConvention() {
// Create the transformer factory
_factory = TransformerFactory.newInstance();
// Initialize the template cache
_templateCache = new HashMap(89);
}
protected void initImpl(Map runtimeProperties)
throws MissingRequiredPropertyException,
InvalidPropertyValueException,
InitializationException {
// Determine if the template cache should be enabled
String cacheEnabled = runtimeProperties.get(TEMPLATES_CACHE_PROPERTY);
initCacheEnabled(cacheEnabled);
// Get the base directory of the style sheets.
_location = getXSLTLocation(runtimeProperties, "source");
// Determine whether template location can be passed as parameter
_templatesPrefix = getXSLTLocation(runtimeProperties, "parameter.prefix");
}
/**
* Determines if the template cache should be enabled. If no value is
* passed, then by default the cache is enabled. An invalid value, however,
* will trigger an {@link InvalidPropertyValueException}.
*
* @param cacheEnabled
* the value of the runtime property that specifies whether the cache
* should be enabled, can be null
.
*
* @throws InvalidPropertyValueException
* if the value is incorrect.
*/
private void initCacheEnabled(String cacheEnabled)
throws InvalidPropertyValueException {
// By default, the template cache is enabled
if (TextUtils.isEmpty(cacheEnabled)) {
_cacheTemplates = true;
// Trim before comparing with 'true' and 'false'
} else {
cacheEnabled = cacheEnabled.trim();
if ("true".equals(cacheEnabled)) {
_cacheTemplates = true;
} else if ("false".equals(cacheEnabled)) {
_cacheTemplates = false;
} else {
throw new InvalidPropertyValueException(TEMPLATES_CACHE_PROPERTY,
cacheEnabled, "Expected either \"true\" or \"false\".");
}
}
// Log whether the cache is enabled or not
if (_cacheTemplates) {
Log.log_3440();
} else {
Log.log_3441();
}
}
/**
* Initializes the location for the XSLT templates.
*
* The name of the runtime property that defines the location of the XSLT
* templates should indicate a directory, either locally or remotely.
* Local locations will be interpreted as relative to the user home
* directory. The value should be a URL or a relative directory.
*
* Examples of valid locations include:
*
*
* projects/dubey/xslt/
* file:///home/john.doe/projects/dubey/xslt/
* http://johndoe.com/projects/dubey/xslt/
* https://xslt.johndoe.com/
* http://xslt.mycompany.com/myapi/
* file:///c:/home/
*
*
* XSLT template files must match the names of the corresponding
* functions.
*
* @param runtimeProperties
* the runtime properties, cannot be null
.
*
* @param propertySuffix
* the suffix of the runtime property we're looking for, cannot be null
.
*
* @return
* the path location where to find the XSLT style sheet files or null
* if no location is specified.
*/
private String getXSLTLocation(Map runtimeProperties, String propertySuffix) {
// Get the value of the property
String templatesProperty = "templates." + getAPI().getName() + ".xins-xslt." + propertySuffix;
String location = runtimeProperties.get(templatesProperty);
if (TextUtils.isEmpty(location)) {
return null;
}
// If the value is not a URL, it's considered as a relative path.
// Relative URLs use the user directory as base dir.
if (location.indexOf("://") == -1) {
// Attempt to convert the home directory to a URL
String home = System.getProperty("user.dir");
String homeURL = "";
try {
homeURL = new File(home).toURL().toString();
// If the conversion to a URL failed, then just use the original
} catch (IOException exception) {
Utils.logIgnoredException(exception);
}
// Prepend the home directory URL
location = homeURL + location;
}
// Log the base directory for XSLT templates
Log.log_3442(getAPI().getName(), propertySuffix, location);
return location;
}
@Override
protected FunctionRequest convertRequestImpl(HttpServletRequest httpRequest)
throws InvalidRequestException,
FunctionNotSpecifiedException {
FunctionRequest request = super.convertRequestImpl(httpRequest);
// We also need to store a few properties in the backpack
Map backpack = request.getBackpack();
backpack.put(CLEAR_TEMPLATE_CACHE_PARAMETER, "true".equals(httpRequest.getParameter(CLEAR_TEMPLATE_CACHE_PARAMETER)));
backpack.put(TEMPLATE_PARAMETER, httpRequest.getParameter(TEMPLATE_PARAMETER));
backpack.put(BackpackConstants.FUNCTION_NAME, httpRequest.getParameter("_function"));
return request;
}
protected void convertResultImpl(FunctionResult xinsResult,
HttpServletResponse httpResponse,
Map backpack)
throws IOException {
// If the request is to clear the cache, just clear the cache.
if ((Boolean) backpack.get(CLEAR_TEMPLATE_CACHE_PARAMETER)) {
_templateCache.clear();
PrintWriter out = httpResponse.getWriter();
out.write("Done.");
out.close();
return;
}
// Get the XML output similar to the standard calling convention.
StringWriter xmlOutput = new StringWriter(1024);
CallResultOutputter.output(xmlOutput, xinsResult);
xmlOutput.close();
// Get the location of the XSLT file.
String xsltLocation = null;
String templatesSuffix = (String) backpack.get(TEMPLATE_PARAMETER);
if (_templatesPrefix != null && templatesSuffix != null) {
if (templatesSuffix.indexOf("..") != -1) {
throw new IOException("Incorrect " + TEMPLATE_PARAMETER + " parameter: " + templatesSuffix);
}
xsltLocation = _templatesPrefix + templatesSuffix;
}
if (_templatesPrefix == null && templatesSuffix != null) {
throw new IOException(TEMPLATE_PARAMETER + " parameter not allowed.");
}
if (xsltLocation == null) {
if (_location == null) {
throw new IOException("No location specified for the XSLT stylesheets.");
}
xsltLocation = _location + backpack.get(BackpackConstants.FUNCTION_NAME) + ".xslt";
}
try {
// Load the template or get it from the cache.
Templates templates;
if (_cacheTemplates && _templateCache.containsKey(xsltLocation)) {
templates = (Templates) _templateCache.get(xsltLocation);
} else {
Log.log_3443(xsltLocation);
templates = _factory.newTemplates(new StreamSource(xsltLocation));
if (_cacheTemplates) {
_templateCache.put(xsltLocation, templates);
}
}
// Proceed to the transformation.
Transformer xformer = templates.newTransformer();
Source source = new StreamSource(new StringReader(xmlOutput.toString()));
Writer buffer = new StringWriter(4096);
Result result = new StreamResult(buffer);
xformer.transform(source, result);
// Determine the MIME type for the output.
String mimeType = getContentType(templates.getOutputProperties());
if (mimeType != null) {
httpResponse.setContentType(mimeType);
}
httpResponse.setStatus(HttpServletResponse.SC_OK);
PrintWriter out = httpResponse.getWriter();
out.print(buffer.toString());
out.close();
} catch (Exception exception) {
if (exception instanceof IOException) {
throw (IOException) exception;
} else {
String message = "Cannot transform the result with the XSLT "
+ "located at \"" + xsltLocation + "\".";
IOException ioe = new IOException(message);
ioe.initCause(exception);
throw ioe;
}
}
}
/**
* Gets the MIME type and the character encoding to return for the HTTP response.
*
* @param outputProperties
* the output properties defined in the XSLT, never null
.
*
* @return
* the content type, never null
.
*/
private String getContentType(Properties outputProperties) {
String mimeType = outputProperties.getProperty("media-type");
if (mimeType == null) {
String method = outputProperties.getProperty("method");
if ("xml".equals(method)) {
mimeType = "text/xml";
} else if ("html".equals(method)) {
mimeType = "text/html";
} else if ("text".equals(method)) {
mimeType = "text/plain";
}
}
String encoding = outputProperties.getProperty("encoding");
if (mimeType != null && encoding != null) {
mimeType += "; charset=" + encoding;
}
return mimeType;
}
}