/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-07 The eXist Project
* http://exist-db.org
*
* 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id$
*/
package org.exist.http.servlets;
import org.apache.log4j.Logger;
import org.exist.EXistException;
import org.exist.collections.Collection;
import org.exist.dom.DocumentImpl;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.security.User;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.lock.Lock;
import org.exist.storage.serializers.Serializer;
import org.exist.storage.serializers.XIncludeFilter;
import org.exist.util.serializer.*;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.Constants;
import org.exist.xquery.XPathException;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.NodeValue;
import org.exist.xquery.value.Type;
import org.exist.xslt.TransformerFactoryAllocator;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.*;
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.StreamSource;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.xml.sax.SAXParseException;
public class XSLTServlet extends HttpServlet {
private final static String REQ_ATTRIBUTE_PREFIX = "xslt.";
private final static String REQ_ATTRIBUTE_STYLESHEET = "xslt.stylesheet";
private final static String REQ_ATTRIBUTE_INPUT = "xslt.input";
private final static String REQ_ATTRIBUTE_PROPERTIES = "xslt.output.";
private final static String REQ_ATTRIBUTE_BASE = "xslt.base";
private final static Logger LOG = Logger.getLogger(XSLTServlet.class);
private BrokerPool pool;
private final Map cache = new HashMap();
private Boolean caching = null;
private boolean isCaching() {
if (caching == null) {
Object property = pool.getConfiguration().getProperty(TransformerFactoryAllocator.PROPERTY_CACHING_ATTRIBUTE);
if (property != null)
caching = (Boolean) property;
else
caching = true;
}
return caching;
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String stylesheet = (String) request.getAttribute(REQ_ATTRIBUTE_STYLESHEET);
if (stylesheet == null)
throw new ServletException("No stylesheet source specified!");
Item inputNode = null;
String sourceAttrib = (String) request.getAttribute(REQ_ATTRIBUTE_INPUT);
if (sourceAttrib != null) {
Object sourceObj = request.getAttribute(sourceAttrib);
if (sourceObj != null) {
if (sourceObj instanceof Item) {
inputNode = (Item) sourceObj;
if (!Type.subTypeOf(inputNode.getType(), Type.NODE))
throw new ServletException("Input for XSLT servlet is not a node. Read from attribute " +
sourceAttrib);
LOG.debug("Taking XSLT input from request attribute " + sourceAttrib);
} else
throw new ServletException("Input for XSLT servlet is not a node. Read from attribute " +
sourceAttrib);
}
}
String userParam = (String) request.getAttribute("xslt.user");
String passwd = (String) request.getAttribute("xslt.password");
if (userParam == null) {
userParam = org.exist.security.SecurityManager.GUEST_USER;
passwd = userParam;
}
try {
pool = BrokerPool.getInstance();
User user = pool.getSecurityManager().getUser(userParam);
if (user != null) {
if (!user.validate(passwd)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Wrong password or user");
}
}
SAXTransformerFactory factory = TransformerFactoryAllocator.getTransformerFactory(pool);
Templates templates = getSource(user, request, response, factory, stylesheet);
if (templates == null)
return;
//do the transformation
DBBroker broker = null;
try {
broker = pool.get(user);
TransformerHandler handler = factory.newTransformerHandler(templates);
setParameters(request, handler.getTransformer());
Properties properties = handler.getTransformer().getOutputProperties();
setOutputProperties(request, properties);
String mediaType = properties.getProperty("media-type");
String encoding = properties.getProperty("encoding");
if (encoding == null)
encoding = "UTF-8";
response.setCharacterEncoding(encoding);
if (mediaType != null) {
if (encoding == null)
response.setContentType(mediaType);
//check, do mediaType have "charset"
else if (mediaType.indexOf("charset") == -1)
response.setContentType(mediaType + "; charset=" + encoding);
else
response.setContentType(mediaType);
}
SAXSerializer sax = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
Writer writer = new BufferedWriter(response.getWriter());
sax.setOutput(writer, properties);
SAXResult result = new SAXResult(sax);
handler.setResult(result);
Serializer serializer = broker.getSerializer();
serializer.reset();
Receiver receiver = new ReceiverToSAX(handler);
try {
XIncludeFilter xinclude = new XIncludeFilter(serializer, receiver);
receiver = xinclude;
String moduleLoadPath;
String base = (String) request.getAttribute(REQ_ATTRIBUTE_BASE);
if (base != null)
moduleLoadPath = getServletContext().getRealPath(base);
else
moduleLoadPath = getCurrentDir(request).getAbsolutePath();
xinclude.setModuleLoadPath(moduleLoadPath);
serializer.setReceiver(receiver);
if (inputNode != null)
serializer.toSAX((NodeValue)inputNode);
else {
SAXToReceiver saxreceiver = new SAXToReceiver(receiver);
XMLReader reader = pool.getParserPool().borrowXMLReader();
reader.setContentHandler(saxreceiver);
reader.parse(new InputSource(request.getInputStream()));
}
} catch (SAXParseException e) {
LOG.error(e.getMessage());
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
} catch (SAXException e) {
throw new ServletException("SAX exception while transforming node: " + e.getMessage(), e);
} finally {
SerializerPool.getInstance().returnObject(sax);
}
writer.flush();
response.flushBuffer();
} catch (IOException e) {
throw new ServletException("IO exception while transforming node: " + e.getMessage(), e);
} catch (TransformerException e) {
throw new ServletException("Exception while transforming node: " + e.getMessage(), e);
} catch (Throwable e){
LOG.error(e);
throw new ServletException("An error occurred: " + e.getMessage(), e);
} finally {
pool.release(broker);
}
} catch (EXistException e) {
throw new ServletException(e.getMessage(), e);
}
}
private Templates getSource(User user, HttpServletRequest request, HttpServletResponse response,
SAXTransformerFactory factory, String stylesheet)
throws ServletException, IOException {
String base;
if(stylesheet.indexOf(':') == Constants.STRING_NOT_FOUND) {
File f = new File(stylesheet);
if (f.canRead())
stylesheet = f.toURI().toASCIIString();
else {
if (f.isAbsolute()) {
f = new File(getServletContext().getRealPath(stylesheet));
stylesheet = f.toURI().toASCIIString();
} else {
f = new File(getCurrentDir(request), stylesheet);
stylesheet = f.toURI().toASCIIString();
}
if (!f.canRead()) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Stylesheet not found");
return null;
}
}
}
int p = stylesheet.lastIndexOf("/");
if(p != Constants.STRING_NOT_FOUND)
base = stylesheet.substring(0, p);
else
base = stylesheet;
if (LOG.isDebugEnabled())
LOG.debug("Loading stylesheet from " + stylesheet);
CachedStylesheet cached = (CachedStylesheet)cache.get(stylesheet);
if(cached == null) {
cached = new CachedStylesheet(factory, user, stylesheet, base);
cache.put(stylesheet, cached);
}
return cached.getTemplates(user);
}
private File getCurrentDir(HttpServletRequest request) {
String path = request.getPathTranslated();
if (path == null) {
path = request.getRequestURI().substring(request.getContextPath().length());
int p = path.lastIndexOf(';');
if(p != Constants.STRING_NOT_FOUND)
path = path.substring(0, p);
path = getServletContext().getRealPath(path);
}
File file = new File(path);
if (file.isDirectory())
return file;
else
return file.getParentFile();
}
private void setParameters(HttpServletRequest request, Transformer transformer) throws XPathException {
for (Enumeration e = request.getAttributeNames(); e.hasMoreElements(); ) {
String name = (String) e.nextElement();
if (name.startsWith(REQ_ATTRIBUTE_PREFIX) &&
!(name.startsWith(REQ_ATTRIBUTE_PROPERTIES) || REQ_ATTRIBUTE_INPUT.equals(name) || REQ_ATTRIBUTE_STYLESHEET.equals(name))) {
Object value = request.getAttribute(name);
if (value instanceof NodeValue) {
NodeValue nv = (NodeValue) value;
if (nv.getImplementationType() == NodeValue.IN_MEMORY_NODE) {
value = nv.toMemNodeSet();
}
}
transformer.setParameter(name, value);
transformer.setParameter(name.substring(REQ_ATTRIBUTE_PREFIX.length()), value);
}
}
}
private void setOutputProperties(HttpServletRequest request, Properties properties) {
for (Enumeration e = request.getAttributeNames(); e.hasMoreElements(); ) {
String name = (String) e.nextElement();
if (name.startsWith(REQ_ATTRIBUTE_PROPERTIES)) {
Object value = request.getAttribute(name);
if (value != null)
properties.setProperty(name.substring(REQ_ATTRIBUTE_PROPERTIES.length()), value.toString());
}
}
}
private class CachedStylesheet {
SAXTransformerFactory factory;
long lastModified = -1;
Templates templates = null;
String uri;
public CachedStylesheet(SAXTransformerFactory factory, User user, String uri, String baseURI) throws ServletException {
this.factory = factory;
this.uri = uri;
if (!baseURI.startsWith("xmldb:exist://"))
factory.setURIResolver(new ExternalResolver(baseURI));
getTemplates(user);
}
public Templates getTemplates(User user) throws ServletException {
if (uri.startsWith("xmldb:exist://")) {
String docPath = uri.substring("xmldb:exist://".length());
DocumentImpl doc = null;
DBBroker broker = null;
try {
broker = pool.get(user);
doc = broker.getXMLResource(XmldbURI.create(docPath), Lock.READ_LOCK);
if (!isCaching() || (doc != null && (templates == null || doc.getMetadata().getLastModified() > lastModified)))
templates = getSource(broker, doc);
lastModified = doc.getMetadata().getLastModified();
} catch (PermissionDeniedException e) {
throw new ServletException("Permission denied to read stylesheet: " + uri, e);
} catch (EXistException e) {
throw new ServletException("Error while reading stylesheet source from db: " + e.getMessage(), e);
} finally {
pool.release(broker);
doc.getUpdateLock().release(Lock.READ_LOCK);
}
} else {
try {
URL url = new URL(uri);
URLConnection connection = url.openConnection();
long modified = connection.getLastModified();
if(!isCaching() || (templates == null || modified > lastModified || modified == 0)) {
LOG.debug("compiling stylesheet " + url.toString());
templates = factory.newTemplates(new StreamSource(connection.getInputStream()));
}
lastModified = modified;
} catch (IOException e) {
throw new ServletException("Error while reading stylesheet source from uri: " + uri +
": " + e.getMessage(), e);
} catch (TransformerConfigurationException e) {
throw new ServletException("Error while reading stylesheet source from uri: " + uri +
": " + e.getMessage(), e);
}
}
return templates;
}
private Templates getSource(DBBroker broker, DocumentImpl stylesheet)
throws ServletException {
factory.setURIResolver(new DatabaseResolver(broker, stylesheet));
try {
TemplatesHandler handler = factory.newTemplatesHandler();
handler.startDocument();
Serializer serializer = broker.getSerializer();
serializer.reset();
serializer.setSAXHandlers(handler, null);
serializer.toSAX(stylesheet);
handler.endDocument();
return handler.getTemplates();
} catch (SAXException e) {
throw new ServletException("A SAX exception occurred while compiling the stylesheet: " + e.getMessage(), e);
} catch (TransformerConfigurationException e) {
throw new ServletException("A configuration exception occurred while compiling the stylesheet: " + 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;
DBBroker broker;
public DatabaseResolver(DBBroker broker, DocumentImpl myDoc) {
this.broker = broker;
this.doc = myDoc;
}
/* (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;
//TODO : use dedicated function in XmldbURI
if(href.startsWith("/"))
path = href;
else
path = collection.getURI() + "/" + href;
DocumentImpl xslDoc;
try {
xslDoc = (DocumentImpl) broker.getXMLResource(XmldbURI.create(path));
} 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(broker.getUser(), Permission.READ))
throw new TransformerException("Insufficient privileges to read resource " + path);
DOMSource source = new DOMSource(xslDoc);
return source;
}
}
}