/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-09 Wolfgang M. Meier
* wolfgang@exist-db.org
* http://exist.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package org.exist.xquery.functions.transform;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Iterator;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.URIResolver;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TemplatesHandler;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.log4j.Logger;
import org.exist.collections.Collection;
import org.exist.dom.DocumentImpl;
import org.exist.dom.NodeProxy;
import org.exist.dom.QName;
import org.exist.http.servlets.ResponseWrapper;
import org.exist.memtree.DocumentBuilderReceiver;
import org.exist.memtree.MemTreeBuilder;
import org.exist.numbering.NodeId;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.storage.lock.Lock;
import org.exist.storage.serializers.Serializer;
import org.exist.storage.serializers.XIncludeFilter;
import org.exist.storage.serializers.EXistOutputKeys;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.BasicFunction;
import org.exist.xquery.Cardinality;
import org.exist.xquery.Constants;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.Option;
import org.exist.xquery.Variable;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.functions.response.ResponseModule;
import org.exist.xquery.value.FunctionParameterSequenceType;
import org.exist.xquery.value.FunctionReturnSequenceType;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.JavaObjectValue;
import org.exist.xquery.value.NodeValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.Type;
import org.exist.xquery.value.ValueSequence;
import org.exist.xslt.TransformerFactoryAllocator;
import org.exist.util.serializer.ReceiverToSAX;
import org.exist.util.serializer.Receiver;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
/**
* @author Wolfgang Meier (wolfgang@exist-db.org)
*/
public class Transform extends BasicFunction {
private static final Logger logger = Logger.getLogger(Transform.class);
public final static FunctionSignature signatures[] = {
new FunctionSignature(
new QName("transform", TransformModule.NAMESPACE_URI, TransformModule.PREFIX),
"Applies an XSL stylesheet to the node tree passed as first argument. The stylesheet " +
"is specified in the second argument. This should either be an URI or a node. If it is an " +
"URI, it can either point to an external location or to an XSL stored in the db by using the " +
"'xmldb:' scheme. Stylesheets are cached unless they were just created from an XML " +
"fragment and not from a complete document. " +
"Stylesheet parameters " +
"may be passed in the third argument using an XML fragment with the following structure: " +
"<parameters><param name=\"param-name1\" value=\"param-value1\"/>" +
"</parameters>. There are two special parameters named \"exist:stop-on-warn\" and " +
"\"exist:stop-on-error\". If set to value \"yes\", eXist will generate an XQuery error " +
"if the XSL processor reports a warning or error.",
new SequenceType[] {
new FunctionParameterSequenceType("node-tree", Type.NODE, Cardinality.ZERO_OR_ONE, "The source-document (node tree)"),
new FunctionParameterSequenceType("stylesheet", Type.ITEM, Cardinality.EXACTLY_ONE, "The XSL stylesheet"),
new FunctionParameterSequenceType("parameters", Type.NODE, Cardinality.ZERO_OR_ONE, "The transformer parameters")
},
new FunctionReturnSequenceType(Type.NODE, Cardinality.ZERO_OR_ONE, "the transformed result (node tree)")),
new FunctionSignature(
new QName("transform", TransformModule.NAMESPACE_URI, TransformModule.PREFIX),
"Applies an XSL stylesheet to the node tree passed as first argument. The stylesheet " +
"is specified in the second argument. This should either be an URI or a node. If it is an " +
"URI, it can either point to an external location or to an XSL stored in the db by using the " +
"'xmldb:' scheme. Stylesheets are cached unless they were just created from an XML " +
"fragment and not from a complete document. " +
"Stylesheet parameters " +
"may be passed in the third argument using an XML fragment with the following structure: " +
"<parameters><param name=\"param-name1\" value=\"param-value1\"/>" +
"</parameters>. There are two special parameters named \"exist:stop-on-warn\" and " +
"\"exist:stop-on-error\". If set to value \"yes\", eXist will generate an XQuery error " +
"if the XSL processor reports a warning or error. The fourth argument specifies serialization " +
"options in the same way as if they " +
"were passed to \"declare option exist:serialize\" expression. An additional serialization option, " +
"xinclude-path, is supported, which specifies a base path against which xincludes will be expanded " +
"(if there are xincludes in the document). A relative path will be relative to the current " +
"module load path.",
new SequenceType[] {
new FunctionParameterSequenceType("node-tree", Type.NODE, Cardinality.ZERO_OR_ONE, "The source-document (node tree)"),
new FunctionParameterSequenceType("stylesheet", Type.ITEM, Cardinality.EXACTLY_ONE, "The XSL stylesheet"),
new FunctionParameterSequenceType("parameters", Type.NODE, Cardinality.ZERO_OR_ONE, "The transformer parameters"),
new FunctionParameterSequenceType("serialization-options", Type.STRING, Cardinality.EXACTLY_ONE, "The serialization options")},
new FunctionReturnSequenceType(Type.NODE, Cardinality.ZERO_OR_ONE, "the transformed result (node tree)")),
new FunctionSignature(
new QName("stream-transform", TransformModule.NAMESPACE_URI, TransformModule.PREFIX),
"Applies an XSL stylesheet to the node tree passed as first argument. The parameters are the same " +
"as for the transform function. stream-transform can only be used within a servlet context. Instead " +
"of returning the transformed document fragment, it directly streams its output to the servlet's output stream. " +
"It should thus be the last statement in the XQuery.",
new SequenceType[] {
new FunctionParameterSequenceType("node-tree", Type.NODE, Cardinality.ZERO_OR_ONE, "The source-document (node tree)"),
new FunctionParameterSequenceType("stylesheet", Type.ITEM, Cardinality.EXACTLY_ONE, "The XSL stylesheet"),
new FunctionParameterSequenceType("parameters", Type.NODE, Cardinality.ZERO_OR_ONE, "The transformer parameters")
},
new SequenceType(Type.ITEM, Cardinality.EMPTY)),
new FunctionSignature(
new QName("stream-transform", TransformModule.NAMESPACE_URI, TransformModule.PREFIX),
"Applies an XSL stylesheet to the node tree passed as first argument. The parameters are the same " +
"as for the transform function. stream-transform can only be used within a servlet context. Instead " +
"of returning the transformed document fragment, it directly streams its output to the servlet's output stream. " +
"It should thus be the last statement in the XQuery.",
new SequenceType[] {
new FunctionParameterSequenceType("node-tree", Type.NODE, Cardinality.ZERO_OR_ONE, "The source-document (node tree)"),
new FunctionParameterSequenceType("stylesheet", Type.ITEM, Cardinality.EXACTLY_ONE, "The XSL stylesheet"),
new FunctionParameterSequenceType("parameters", Type.NODE, Cardinality.ZERO_OR_ONE, "The transformer parameters"),
new FunctionParameterSequenceType("serialization-options", Type.STRING, Cardinality.EXACTLY_ONE, "The serialization options")},
new SequenceType(Type.ITEM, Cardinality.EMPTY))
};
private final Map cache = new HashMap();
private boolean caching = true;
private boolean stopOnError = true;
private boolean stopOnWarn = false;
/**
* @param context
* @param signature
*/
public Transform(XQueryContext context, FunctionSignature signature) {
super(context, signature);
Object property = context.getBroker().getConfiguration().getProperty(TransformerFactoryAllocator.PROPERTY_CACHING_ATTRIBUTE);
if (property != null)
caching = (Boolean) property;
}
/* (non-Javadoc)
* @see org.exist.xquery.BasicFunction#eval(org.exist.xquery.value.Sequence[], org.exist.xquery.value.Sequence)
*/
public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {
if(args[0].isEmpty()) {
return Sequence.EMPTY_SEQUENCE;
}
Item inputNode = args[0].itemAt(0);
Item stylesheetItem = args[1].itemAt(0);
Node options = null;
if(!args[2].isEmpty())
options = ((NodeValue)args[2].itemAt(0)).getNode();
// apply serialization options set on the XQuery context
Properties serializeOptions = new Properties();
if (getArgumentCount() == 4) {
String serOpts = args[3].getStringValue();
String[] contents = Option.tokenize(serOpts);
for (int i = 0; i < contents.length; i++) {
String[] pair = Option.parseKeyValuePair(contents[i]);
if (pair == null)
throw new XPathException(this, "Found invalid serialization option: " + pair);
logger.info("Setting serialization property: " + pair[0] + " = " + pair[1]);
serializeOptions.setProperty(pair[0], pair[1]);
}
} else
context.checkOptions(serializeOptions);
boolean expandXIncludes =
serializeOptions.getProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes").equals("yes");
Properties stylesheetParams = new Properties();
if (options != null)
parseParameters(stylesheetParams, options);
TransformerHandler handler = createHandler(stylesheetItem, stylesheetParams);
TransformErrorListener errorListener = new TransformErrorListener();
handler.getTransformer().setErrorListener(errorListener);
if (isCalledAs("transform"))
{
//transform:transform()
ValueSequence seq = new ValueSequence();
context.pushDocumentContext();
MemTreeBuilder builder = context.getDocumentBuilder();
DocumentBuilderReceiver builderReceiver = new DocumentBuilderReceiver(builder, true);
SAXResult result = new SAXResult(builderReceiver);
result.setLexicalHandler(builderReceiver); //preserve comments etc... from xslt output
handler.setResult(result);
Receiver receiver = new ReceiverToSAX(handler);
Serializer serializer = context.getBroker().getSerializer();
serializer.reset();
try {
serializer.setProperties(serializeOptions);
if (expandXIncludes) {
XIncludeFilter xinclude = new XIncludeFilter(serializer, receiver);
String xipath = serializeOptions.getProperty(EXistOutputKeys.XINCLUDE_PATH);
if (xipath != null) {
File f = new File(xipath);
if (!f.isAbsolute())
xipath = new File(context.getModuleLoadPath(), xipath).getAbsolutePath();
} else
xipath = context.getModuleLoadPath();
xinclude.setModuleLoadPath(xipath);
receiver = xinclude;
}
serializer.setReceiver(receiver);
serializer.toSAX((NodeValue)inputNode);
} catch (Exception e) {
throw new XPathException(this, "Exception while transforming node: " + e.getMessage(), e);
}
errorListener.checkForErrors();
Node next = builder.getDocument().getFirstChild();
while (next != null) {
seq.add((NodeValue) next);
next = next.getNextSibling();
}
context.popDocumentContext();
return seq;
}
else
{
//transform:stream-transform()
ResponseModule myModule = (ResponseModule)context.getModule(ResponseModule.NAMESPACE_URI);
// response object is read from global variable $response
Variable respVar = myModule.resolveVariable(ResponseModule.RESPONSE_VAR);
if(respVar == null)
throw new XPathException(this, "No response object found in the current XQuery context.");
if(respVar.getValue().getItemType() != Type.JAVA_OBJECT)
throw new XPathException(this, "Variable $response is not bound to an Java object.");
JavaObjectValue respValue = (JavaObjectValue)
respVar.getValue().itemAt(0);
if (!"org.exist.http.servlets.HttpResponseWrapper".equals(respValue.getObject().getClass().getName()))
throw new XPathException(this, signatures[1].toString() +
" can only be used within the EXistServlet or XQueryServlet");
ResponseWrapper response = (ResponseWrapper) respValue.getObject();
//setup the response correctly
String mediaType = handler.getTransformer().getOutputProperty("media-type");
String encoding = handler.getTransformer().getOutputProperty("encoding");
if(mediaType != null)
{
if(encoding == null)
{
response.setContentType(mediaType);
}
else
{
response.setContentType(mediaType + "; charset=" + encoding);
}
}
//do the transformation
try {
OutputStream os = new BufferedOutputStream(response.getOutputStream());
StreamResult result = new StreamResult(os);
handler.setResult(result);
Serializer serializer = context.getBroker().getSerializer();
serializer.reset();
Receiver receiver = new ReceiverToSAX(handler);
try {
serializer.setProperties(serializeOptions);
if (expandXIncludes) {
XIncludeFilter xinclude = new XIncludeFilter(serializer, receiver);
String xipath = serializeOptions.getProperty(EXistOutputKeys.XINCLUDE_PATH);
if (xipath != null) {
File f = new File(xipath);
if (!f.isAbsolute())
xipath = new File(context.getModuleLoadPath(), xipath).getAbsolutePath();
} else
xipath = context.getModuleLoadPath();
xinclude.setModuleLoadPath(xipath);
receiver = xinclude;
}
serializer.setReceiver(receiver);
serializer.toSAX((NodeValue)inputNode);
} catch (Exception e) {
throw new XPathException(this, "Exception while transforming node: " + e.getMessage(), e);
}
errorListener.checkForErrors();
os.close();
//commit the response
response.flushBuffer();
} catch (IOException e) {
throw new XPathException(this, "IO exception while transforming node: " + e.getMessage(), e);
}
return Sequence.EMPTY_SEQUENCE;
}
}
/**
* @param stylesheetItem
* @param options
* @return
* @throws TransformerFactoryConfigurationError
* @throws XPathException
*/
private TransformerHandler createHandler(Item stylesheetItem, Properties options) throws TransformerFactoryConfigurationError, XPathException
{
SAXTransformerFactory factory = TransformerFactoryAllocator.getTransformerFactory(context.getBroker().getBrokerPool());
TransformerHandler handler;
try
{
Templates templates = null;
if(Type.subTypeOf(stylesheetItem.getType(), Type.NODE))
{
NodeValue stylesheetNode = (NodeValue)stylesheetItem;
// if the passed node is a document node or document root element,
// we construct an XMLDB URI and use the caching implementation.
if(stylesheetNode.getImplementationType() == NodeValue.PERSISTENT_NODE)
{
NodeProxy root = (NodeProxy) stylesheetNode;
if (root.getNodeId() == NodeId.DOCUMENT_NODE || root.getNodeId().getTreeLevel() == 1)
{
//as this is a persistent node (e.g. a stylesheet stored in the db)
//set the URI Resolver as a DatabaseResolver
factory.setURIResolver(new DatabaseResolver(root.getDocument()));
String uri = XmldbURI.XMLDB_URI_PREFIX + context.getBroker().getBrokerPool().getId() + "://" + root.getDocument().getURI();
templates = getSource(factory, uri);
}
}
if(templates == null)
{
templates = getSource(factory, stylesheetNode);
}
}
else
{
String stylesheet = stylesheetItem.getStringValue();
templates = getSource(factory, stylesheet);
}
handler = factory.newTransformerHandler(templates);
if(options != null)
{
setParameters(options, handler.getTransformer());
}
}
catch (TransformerConfigurationException e)
{
throw new XPathException(this, "Unable to set up transformer: " + e.getMessage(), e);
}
return handler;
}
private void parseParameters(Properties properties, Node options) throws XPathException {
if(options.getNodeType() == Node.ELEMENT_NODE && options.getLocalName().equals("parameters")) {
Node child = options.getFirstChild();
while(child != null) {
if(child.getNodeType() == Node.ELEMENT_NODE && child.getLocalName().equals("param")) {
Element elem = (Element)child;
String name = elem.getAttribute("name");
String value = elem.getAttribute("value");
if(name == null || value == null)
throw new XPathException(this, "Name or value attribute missing for stylesheet parameter");
if (name.equals("exist:stop-on-warn"))
stopOnWarn = value.equals("yes");
else if (name.equals("exist:stop-on-error"))
stopOnError = value.equals("yes");
else
properties.setProperty(name, value);
}
child = child.getNextSibling();
}
}
}
private void setParameters(Properties parameters, Transformer handler) {
for (Iterator i = parameters.keySet().iterator(); i.hasNext();) {
String key = (String) i.next();
handler.setParameter(key, parameters.getProperty(key));
}
}
private Templates getSource(SAXTransformerFactory factory, String stylesheet)
throws XPathException, TransformerConfigurationException {
String base;
if(stylesheet.indexOf(':') == Constants.STRING_NOT_FOUND) {
File f = new File(stylesheet);
if(f.canRead())
stylesheet = f.toURI().toASCIIString();
else {
stylesheet = context.getModuleLoadPath() + File.separatorChar + stylesheet;
f = new File(stylesheet);
if(f.canRead()) stylesheet = f.toURI().toASCIIString();
}
}
//TODO : use dedicated function in XmldbURI
int p = stylesheet.lastIndexOf("/");
if(p != Constants.STRING_NOT_FOUND)
base = stylesheet.substring(0, p);
else
base = stylesheet;
CachedStylesheet cached = (CachedStylesheet)cache.get(stylesheet);
try {
if(cached == null) {
cached = new CachedStylesheet(factory, stylesheet, base);
cache.put(stylesheet, cached);
}
return cached.getTemplates();
} catch (MalformedURLException e) {
LOG.debug(e.getMessage(), e);
throw new XPathException(this, "Malformed URL for stylesheet: " + stylesheet, e);
} catch (IOException e) {
throw new XPathException(this, "IO error while loading stylesheet: " + stylesheet, e);
}
}
private Templates getSource(SAXTransformerFactory factory, NodeValue stylesheetRoot) throws XPathException, TransformerConfigurationException
{
TemplatesHandler handler = factory.newTemplatesHandler();
try {
handler.startDocument();
stylesheetRoot.toSAX(context.getBroker(), handler, null);
handler.endDocument();
return handler.getTemplates();
} catch (SAXException e) {
throw new XPathException(this,
"A SAX exception occurred while compiling the stylesheet: " + e.getMessage(), e);
}
}
//TODO: revisit this class with XmldbURI in mind
private class CachedStylesheet {
SAXTransformerFactory factory;
long lastModified = -1;
Templates templates = null;
String uri;
public CachedStylesheet(SAXTransformerFactory factory, String uri, String baseURI)
throws TransformerConfigurationException, IOException, XPathException {
this.factory = factory;
this.uri = uri;
if (!baseURI.startsWith(XmldbURI.EMBEDDED_SERVER_URI_PREFIX))
factory.setURIResolver(new ExternalResolver(baseURI));
getTemplates();
}
public Templates getTemplates() throws TransformerConfigurationException, IOException, XPathException {
if (uri.startsWith(XmldbURI.EMBEDDED_SERVER_URI_PREFIX)) {
String docPath = uri.substring(XmldbURI.EMBEDDED_SERVER_URI_PREFIX.length());
DocumentImpl doc = null;
try {
doc = context.getBroker().getXMLResource(XmldbURI.create(docPath), Lock.READ_LOCK);
if (!caching || (doc != null && (templates == null || doc.getMetadata().getLastModified() > lastModified)))
templates = getSource(doc);
lastModified = doc.getMetadata().getLastModified();
} catch (PermissionDeniedException e) {
throw new XPathException(Transform.this, "Permission denied to read stylesheet: " + uri);
} finally {
if (doc != null) doc.getUpdateLock().release(Lock.READ_LOCK);
}
} else {
URL url = new URL(uri);
URLConnection connection = url.openConnection();
long modified = connection.getLastModified();
if(!caching || (templates == null || modified > lastModified || modified == 0)) {
LOG.debug("compiling stylesheet " + url.toString());
InputStream is = connection.getInputStream();
try {
templates = factory.newTemplates(new StreamSource(is));
} finally {
is.close();
}
}
lastModified = modified;
}
return templates;
}
private Templates getSource(DocumentImpl stylesheet)
throws XPathException, TransformerConfigurationException {
factory.setURIResolver(new DatabaseResolver(stylesheet));
TransformErrorListener errorListener = new TransformErrorListener();
factory.setErrorListener(errorListener);
TemplatesHandler handler = factory.newTemplatesHandler();
try {
handler.startDocument();
Serializer serializer = context.getBroker().getSerializer();
serializer.reset();
serializer.setSAXHandlers(handler, null);
serializer.toSAX(stylesheet);
handler.endDocument();
Templates t = handler.getTemplates();
errorListener.checkForErrors();
return t;
} catch (Exception e) {
if (e instanceof XPathException)
throw (XPathException) e;
throw new XPathException(Transform.this,
"An exception occurred while compiling the stylesheet: " + stylesheet.getURI() +
": " + e.getMessage(), e);
}
}
}
private class ExternalResolver implements URIResolver {
private String baseURI;
public ExternalResolver(String base) {
this.baseURI = base;
}
/* (non-Javadoc)
* @see javax.xml.transform.URIResolver#resolve(java.lang.String, java.lang.String)
*/
public Source resolve(String href, String base)
throws TransformerException {
URL url;
try {
//TODO : use dedicated function in XmldbURI
url = new URL(baseURI + "/" + href);
URLConnection connection = url.openConnection();
return new StreamSource(connection.getInputStream());
} catch (MalformedURLException e) {
return null;
} catch (IOException e) {
return null;
}
}
}
private class DatabaseResolver implements URIResolver {
DocumentImpl doc;
// Base path
String basePath;
public DatabaseResolver(DocumentImpl myDoc) {
this.doc = myDoc;
basePath = myDoc.getCollection().getURI().toString();
LOG.debug("Database Resolver base path set to " + basePath);
}
/** Simplify a path removing any "." and ".." path elements.
* Assumes an absolute path is given.
*/
public String normalizePath(String path) {
if (!path.startsWith("/"))
throw new IllegalArgumentException("normalizePath may only be applied to an absolute path; " +
"argument was: " + path + "; base: " + basePath);
String[] pathComponents = path.substring(1).split("/");
int numPathComponents = Array.getLength(pathComponents);
String[] simplifiedComponents = new String[numPathComponents];
int numSimplifiedComponents = 0;
for(String s : pathComponents) {
if (s.length() == 0) continue; // Remove empty elements ("//")
if (s.equals(".")) continue; // Remove identity elements ("/./")
if (s.equals("..")) { // Remove parent elements ("/../") unless at the root
if (numSimplifiedComponents > 0) numSimplifiedComponents--;
continue;
}
simplifiedComponents[numSimplifiedComponents++] = s;
}
if (numSimplifiedComponents == 0) return "/";
StringBuffer b = new StringBuffer(path.length());
for(int x = 0; x < numSimplifiedComponents; x++) {
b.append("/").append(simplifiedComponents[x]);
}
if (path.endsWith("/")) {
b.append("/");
}
return b.toString();
}
/* (non-Javadoc)
* @see javax.xml.transform.URIResolver#resolve(java.lang.String, java.lang.String)
*/
public Source resolve(String href, String base)
throws TransformerException {
Collection collection = doc.getCollection();
String path;
if (href.length()==0) {
path = base;
} else
//TODO : use dedicated function in XmldbURI
if (href.startsWith("/"))
path = href;
else if (base == null || base.length() == 0) {
path = basePath + "/" + href;
}
else {
// Maybe base never contains this prefix? Check to be sure.
if (base.startsWith(XmldbURI.EMBEDDED_SERVER_URI_PREFIX)) {
base = base.substring(XmldbURI.EMBEDDED_SERVER_URI_PREFIX.length());
}
path = base.substring(0, base.lastIndexOf("/") + 1) + href;
}
path = normalizePath(path);
XmldbURI uri = XmldbURI.create(path);
LOG.debug("Resolving database path " + href + " with base " + base + " to " + path + " (URI = " + uri.toASCIIString() + ")");
DocumentImpl xslDoc;
try {
xslDoc = (DocumentImpl) context.getBroker().getXMLResource(uri);
} catch (PermissionDeniedException e) {
throw new TransformerException(e.getMessage(), e);
}
if(xslDoc == null) {
LOG.debug("Document " + href + " not found in collection " + collection.getURI());
return null;
}
if(!xslDoc.getPermissions().validate(context.getUser(), Permission.READ))
throw new TransformerException("Insufficient privileges to read resource " + path);
DOMSource source = new DOMSource(xslDoc);
source.setSystemId(uri.toASCIIString());
return source;
}
}
private class TransformErrorListener implements ErrorListener {
private final static int NO_ERROR = 0;
private final static int WARNING = 1;
private final static int ERROR = 2;
private final static int FATAL = 3;
private int errcode = NO_ERROR;
private Exception exception;
protected void checkForErrors() throws XPathException {
switch (errcode) {
case WARNING:
if (stopOnWarn)
throw new XPathException("XSL transform reported warning: " + exception.getMessage(),
exception);
break;
case ERROR:
if (stopOnError)
throw new XPathException("XSL transform reported error: " + exception.getMessage(), exception);
break;
case FATAL:
throw new XPathException("XSL transform reported error: " + exception.getMessage(), exception);
}
}
public void warning(TransformerException except) throws TransformerException {
LOG.warn("XSL transform reports warning: " + except.getMessage(), except);
errcode = WARNING;
exception = except;
if (stopOnWarn)
throw except;
}
public void error(TransformerException except) throws TransformerException {
LOG.warn("XSL transform reports recoverable error: " + except.getMessage(), except);
errcode = ERROR;
exception = except;
if (stopOnError)
throw except;
}
public void fatalError(TransformerException except) throws TransformerException {
LOG.warn("XSL transform reports fatal error: " + except.getMessage(), except);
errcode = FATAL;
exception = except;
throw except;
}
}
}