Package org.exist.http.servlets

Source Code of org.exist.http.servlets.XSLTServlet

/*
*  eXist Open Source Native XML Database
*  Copyright (C) 2010 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.AuthenticationException;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.security.Subject;
import org.exist.security.internal.AccountImpl;
import org.exist.security.internal.web.HttpAccount;
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.Receiver;
import org.exist.util.serializer.ReceiverToSAX;
import org.exist.util.serializer.SAXSerializer;
import org.exist.util.serializer.SAXToReceiver;
import org.exist.util.serializer.SerializerPool;
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.xquery.value.ValueSequence;
import org.exist.xslt.TransformerFactoryAllocator;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
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.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.URIResolver;
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.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.net.*;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.zip.GZIPInputStream;

/**
* eXist-db servlet for XSLT transformations.
*
* @author Wolfgang
*/
public class XSLTServlet extends HttpServlet {

  private static final long serialVersionUID = -7258405385386062151L;

  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_OUTPUT = "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<String, CachedStylesheet> cache = new HashMap<String, CachedStylesheet>();
    private Boolean caching = null;

    /**
     * @return Value of TransformerFactoryAllocator.PROPERTY_CACHING_ATTRIBUTE or TRUE if not present.
     */
    private boolean isCaching() {
      if (caching == null) {
        final Object property = pool.getConfiguration().getProperty(TransformerFactoryAllocator.PROPERTY_CACHING_ATTRIBUTE);
      if (property != null){
        caching = (Boolean) property;
            } else {
        caching = true;
            }
      }
      return caching;
    }


    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        final String stylesheet = (String) request.getAttribute(REQ_ATTRIBUTE_STYLESHEET);
        if (stylesheet == null){
            throw new ServletException("No stylesheet source specified!");
        }

        Item inputNode = null;
       
        final String sourceAttrib = (String) request.getAttribute(REQ_ATTRIBUTE_INPUT);
        if (sourceAttrib != null) {

            Object sourceObj = request.getAttribute(sourceAttrib);
            if (sourceObj != null) {
              if (sourceObj instanceof ValueSequence) {
          final ValueSequence seq = (ValueSequence) sourceObj;
         
          if (seq.size() == 1) {
            sourceObj = seq.itemAt(0);
                    }
        }
             
                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);}
            }
        }

    try {
      pool = BrokerPool.getInstance();
    } catch (final EXistException e) {
      throw new ServletException(e.getMessage(), e);
    }

    Subject user = pool.getSecurityManager().getGuestSubject();

    Subject requestUser = HttpAccount.getUserFromServletRequest(request);
        if (requestUser != null)
          {user = requestUser;}

        // Retrieve username / password from HTTP request attributes
        final String userParam = (String) request.getAttribute("xslt.user");
        final String passwd = (String) request.getAttribute("xslt.password");

    if (userParam != null) {
        try {
          user = pool.getSecurityManager().authenticate(userParam, passwd);
        } catch (final AuthenticationException e1) {
          response.sendError(HttpServletResponse.SC_FORBIDDEN, "Wrong password or user");
          return;
        }
      }

        final SAXTransformerFactory factory = TransformerFactoryAllocator.getTransformerFactory(pool);
        final Templates templates = getSource(user, request, response, factory, stylesheet);
        if (templates == null){
            return;
        }
       
        //do the transformation
        DBBroker broker = null;
        try {
            broker = pool.get(user);

            final TransformerHandler handler = factory.newTransformerHandler(templates);
            setTransformerParameters(request, handler.getTransformer());
           
            final Properties properties = handler.getTransformer().getOutputProperties();
            setOutputProperties(request, properties);

            String encoding = properties.getProperty("encoding");
            if (encoding == null){
                encoding = "UTF-8";
            }
            response.setCharacterEncoding(encoding);

            final String mediaType = properties.getProperty("media-type");
            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);}
            }

            final SAXSerializer sax = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
            final Writer writer = new BufferedWriter(response.getWriter());
            sax.setOutput(writer, properties);

            final SAXResult result = new SAXResult(sax);
            handler.setResult(result);
           
            final Serializer serializer = broker.getSerializer();
            serializer.reset();
           
            Receiver receiver = new ReceiverToSAX(handler);
            try {
                XIncludeFilter xinclude = new XIncludeFilter(serializer, receiver);
                receiver = xinclude;
                String moduleLoadPath;

                final String base = (String) request.getAttribute(REQ_ATTRIBUTE_BASE);
                if (base != null) {
                    moduleLoadPath = getServletContext().getRealPath(base);
                } else if (stylesheet.startsWith("xmldb:exist://")) {
                  moduleLoadPath = XmldbURI.xmldbUriFor(stylesheet).getCollectionPath();
                } else {
                    moduleLoadPath = getCurrentDir(request).getAbsolutePath();
                }

                xinclude.setModuleLoadPath(moduleLoadPath);

                serializer.setReceiver(receiver);
                if (inputNode != null){
                    serializer.toSAX((NodeValue)inputNode);

                } else {
                    final SAXToReceiver saxreceiver = new SAXToReceiver(receiver);
                    final XMLReader reader = pool.getParserPool().borrowXMLReader();
                    reader.setContentHandler(saxreceiver);

                  //Handle gziped input stream
                  InputStream stream;
                   
                  InputStream inStream = new BufferedInputStream(request.getInputStream());
                  inStream.mark(10);
                    try {
                      stream = new GZIPInputStream(inStream);
                    } catch (final IOException e) {
                      inStream.reset();
                      stream = inStream;
          }

                    reader.parse(new InputSource(stream));
                }

            } catch (final SAXParseException e) {
                LOG.error(e.getMessage());
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());

            } catch (final SAXException e) {
                throw new ServletException("SAX exception while transforming node: " + e.getMessage(), e);

            } finally {
                SerializerPool.getInstance().returnObject(sax);
            }
           
            writer.flush();
            response.flushBuffer();

        } catch (final IOException e) {
            throw new ServletException("IO exception while transforming node: " + e.getMessage(), e);

        } catch (final TransformerException e) {
            throw new ServletException("Exception while transforming node: " + e.getMessage(), e);
           
        } catch (final Throwable e){
            LOG.error(e);
            throw new ServletException("An error occurred: " + e.getMessage(), e);

        } finally {
            pool.release(broker);
        }
    }

    /*
     * Please add comments to this method. make assumption clear. These might not be valid.
     */
    private Templates getSource(Subject user, HttpServletRequest request, HttpServletResponse response,
                                SAXTransformerFactory factory, String stylesheet)
        throws ServletException, IOException {

        // Check if stylesheet contains an URI. If not, try to resolve from file system
        if(stylesheet.indexOf(':') == Constants.STRING_NOT_FOUND) {
            // replace double slash
            stylesheet = stylesheet.replaceAll("//", "/");
            File f = new File(stylesheet);
            if (f.canRead()) {
                // Found file, get URI
                stylesheet = f.toURI().toASCIIString();

            } else {
                // if the stylesheet path is absolute, it must be resolved relative to the webapp root
                // f.isAbsolute is problematic on windows.
                if (stylesheet.startsWith("/")) {

                  final String url = getServletContext().getRealPath(stylesheet);
                  if (url == null) {
                        response.sendError(HttpServletResponse.SC_NOT_FOUND,
                                "Stylesheet not found (URL: "+stylesheet+")");
                        return null;
                  }
                   
                    f = new File(url);
                    stylesheet = f.toURI().toASCIIString();
                   
                } else {
                    // relative path is relative to the current working directory
                    f = new File(getCurrentDir(request), stylesheet);
                    stylesheet = f.toURI().toASCIIString();
                }
               
                if (!f.canRead()) {
                    response.sendError(HttpServletResponse.SC_NOT_FOUND,
                            "Stylesheet not found (URL: "+stylesheet+")");
                    return null;
                }
            }
        }

        // Try to figure out the base directory of the stylesheet file.
        // This is required to locate resources imported within the stylesheet.
        String base;
        final 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 = cache.get(stylesheet);
        if(cached == null) {
            cached = new CachedStylesheet(factory, user, stylesheet, base);
            cache.put(stylesheet, cached);
        }
        return cached.getTemplates(user);
    }

    /*
     * Please explain what this method is about. Write about assumptions / input.
     */
    private File getCurrentDir(HttpServletRequest request) {
        String path = request.getPathTranslated();
        if (path == null) {
            path = request.getRequestURI().substring(request.getContextPath().length());
            final int p = path.lastIndexOf('/');
            if(p != Constants.STRING_NOT_FOUND) {
                path = path.substring(0, p);
            }
            path = getServletContext().getRealPath(path);
        }
       
        final File file = new File(path);
        if (file.isDirectory()){
            return file;
        } else {
            return file.getParentFile();
        }
    }

    /**
     *  Copy "xslt." attributes from HTTP request to transformer. Does not copy 'input', 'output'
     * and 'styleheet' attributes.
     */
    private void setTransformerParameters(HttpServletRequest request, Transformer transformer) throws XPathException {
       
        for (final Enumeration<String> e = request.getAttributeNames(); e.hasMoreElements(); ) {

            final String name = e.nextElement();
            if (name.startsWith(REQ_ATTRIBUTE_PREFIX) &&
                !(name.startsWith(REQ_ATTRIBUTE_OUTPUT) || REQ_ATTRIBUTE_INPUT.equals(name)
                                                            || REQ_ATTRIBUTE_STYLESHEET.equals(name))) {
                Object value = request.getAttribute(name);
                if (value instanceof NodeValue) {
                    final 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);
            }
        }
    }

    /**
     * Copies 'output' attributes to properties object.
     */
    private void setOutputProperties(HttpServletRequest request, Properties properties) {
        for (final Enumeration<String> e = request.getAttributeNames(); e.hasMoreElements(); ) {
            final String name = e.nextElement();
            if (name.startsWith(REQ_ATTRIBUTE_OUTPUT)) {
                final Object value = request.getAttribute(name);
                if (value != null){
                    properties.setProperty(name.substring(REQ_ATTRIBUTE_OUTPUT.length()), value.toString());
                }
            }
        }
    }

    private class CachedStylesheet {

        SAXTransformerFactory factory;
        long lastModified = -1;
        Templates templates = null;
        String uri;

        public CachedStylesheet(SAXTransformerFactory factory, Subject 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(Subject user) throws ServletException {
            if (uri.startsWith("xmldb:exist://")) {
                final 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 (doc == null) {
                        throw new ServletException("Stylesheet not found: " + docPath);
                    }
                   
                    if (!isCaching() || (doc != null && (templates == null
                                     || doc.getMetadata().getLastModified() > lastModified))) {
                        templates = getSource(broker, doc);
                    }
                   
                    lastModified = doc.getMetadata().getLastModified();

                } catch (final PermissionDeniedException e) {
                    throw new ServletException("Permission denied to read stylesheet: " + uri, e);

                } catch (final EXistException e) {
                    throw new ServletException("Error while reading stylesheet source from db: " + e.getMessage(), e);

                } finally {
                    pool.release(broker);
                    if (doc != null) {
                        doc.getUpdateLock().release(Lock.READ_LOCK);
                    }
                }
               
            } else {
                try {
                    final URL url = new URL(uri);
                    final 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 (final IOException e) {
                    throw new ServletException("Error while reading stylesheet source from uri: " + uri +
                            ": " + e.getMessage(), e);

                } catch (final 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 {
                final TemplatesHandler handler = factory.newTemplatesHandler();
                handler.startDocument();

                final Serializer serializer = broker.getSerializer();
                serializer.reset();
                serializer.setSAXHandlers(handler, null);
                serializer.toSAX(stylesheet);

                handler.endDocument();      
                return handler.getTemplates();

            } catch (final SAXException e) {
                throw new ServletException("A SAX exception occurred while compiling the stylesheet: "
                        + e.getMessage(), e);

            } catch (final TransformerConfigurationException e) {
                throw new ServletException("A configuration exception occurred while " +
                        "compiling the stylesheet: " + e.getMessage(), e);
            }
        }
    }

    /*
     * TODO: create generic resolver for whole database
     */
    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)
         */
        @Override
        public Source resolve(String href, String base)
                throws TransformerException {
            URL url;
            try {
                //TODO : use dedicated function in XmldbURI
                url = new URL(baseURI + "/"  + href);
                final URLConnection connection = url.openConnection();
                return new StreamSource(connection.getInputStream());
               
            } catch (final MalformedURLException e) {
                return null;

            } catch (final IOException e) {
                return null;
            }
        }
    }

    /*
     * TODO: create generic resolver for whole database
     */
    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)
         */
        @Override
        public Source resolve(String href, String base)
                throws TransformerException {
            final 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 (final 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.getSubject(), Permission.READ)){
                throw new TransformerException("Insufficient privileges to read resource " + path);
            }
           
            final DOMSource source = new DOMSource(xslDoc);
            return source;
        }
    }
}
TOP

Related Classes of org.exist.http.servlets.XSLTServlet

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.