XsltRenderer.java

/* 
 * Licensed 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.esigate.xml;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.Writer;

import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.XMLConstants;

import nu.validator.htmlparser.common.DoctypeExpectation;
import nu.validator.htmlparser.dom.Dom2Sax;
import nu.validator.htmlparser.dom.HtmlDocumentBuilder;

import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.esigate.Driver;
import org.esigate.HttpErrorPage;
import org.esigate.Renderer;
import org.esigate.http.HttpResponseUtils;
import org.esigate.impl.DriverRequest;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Applies an XSLT transformation to the retrieved data.
 * 
 * If no namespace is specified in the document to transform, it is asumed as:<br>
 * xmlns="http://www.w3.org/1999/xhtml"
 * 
 * @author Stanislav Bernatskyi
 * @author Francois-Xavier Bonnet
 */
public class XsltRenderer implements Renderer {
    private static final TransformerFactory TRANSFORMER_FACTORY = TransformerFactory.newInstance();
    private Transformer transformer;

    /**
     * @param template
     *            The path to the xsl template, relative to the context root
     * @param driver
     *            driver
     * @param originalRequest
     * @throws IOException
     *             If an error occurs while writing to the output
     * @throws HttpErrorPage
     */
    public XsltRenderer(String template, Driver driver, DriverRequest originalRequest) throws IOException,
            HttpErrorPage {
        StringBuilder templateStringBuilder = new StringBuilder();
        CloseableHttpResponse response = driver.render(template, originalRequest.getOriginalRequest());
        templateStringBuilder.append(HttpResponseUtils.toString(response));
        transformer = createTransformer(IOUtils.toInputStream(templateStringBuilder));
    }

    /**
     * @param xsl
     *            The xsl template to apply as a String
     * @throws IOException
     *             If an error occurs while writing to the output
     */
    public XsltRenderer(String xsl) throws IOException {
        InputStream templateStream = IOUtils.toInputStream(xsl);
        transformer = createTransformer(templateStream);
    }

    private static Transformer createTransformer(InputStream templateStream) throws IOException {
        try {
            // Ensure XSLT cannot use advanced extensions during processing.
            TRANSFORMER_FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
            return TRANSFORMER_FACTORY.newTransformer(new StreamSource(templateStream));
        } catch (TransformerConfigurationException e) {
            throw new ProcessingFailedException("Failed to create XSLT template", e);
        } finally {
            templateStream.close();
        }
    }

    /** {@inheritDoc} */
    @Override
    public void render(DriverRequest httpRequest, String src, Writer out) throws IOException {
        try {
            HtmlDocumentBuilder htmlDocumentBuilder = new HtmlDocumentBuilder();
            htmlDocumentBuilder.setDoctypeExpectation(DoctypeExpectation.NO_DOCTYPE_ERRORS);
            Document document = htmlDocumentBuilder.parse(new InputSource(new StringReader(src)));
            Source source = new DOMSource(document);
            DOMResult result = new DOMResult();
            transformer.transform(source, result);
            XhtmlSerializer serializer = new XhtmlSerializer(out);
            Dom2Sax dom2Sax = new Dom2Sax(serializer, serializer);
            dom2Sax.parse(result.getNode());
        } catch (TransformerException e) {
            throw new ProcessingFailedException("Failed to transform source", e);
        } catch (SAXException e) {
            throw new ProcessingFailedException("Failed serialize transformation result", e);
        }
    }
}