org.alfresco.repo.template.XSLTProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.template.XSLTProcessor.java

Source

/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco 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 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.template;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.xml.transform.ErrorListener;
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.URIResolver;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.alfresco.repo.processor.BaseProcessor;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.TemplateException;
import org.alfresco.service.cmr.repository.TemplateProcessor;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.XMLUtil;
import org.apache.bsf.BSFManager;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xml.utils.Constants;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import freemarker.cache.TemplateLoader;

public class XSLTProcessor extends BaseProcessor implements TemplateProcessor {
    private static final Log log = LogFactory.getLog(XSLTProcessor.class);

    private static final String LOCALE_SEPARATOR = "_";

    private final static String MSG_ERROR_NO_TEMPLATE = "error_no_template";
    private static final String MSG_UNABLE_TO_READ_TEMPLATE = "template.xslt.read_error";
    private static final String MSG_UNABLE_TO_PARSE_TEMPLATE = "template.xslt.parse_error";

    public final static QName ROOT_NAMESPACE = QName.createQName(null, "root_namespace");

    private String defaultEncoding = "UTF-8";
    private TemplateLoader templateLoader;

    public void register() {
        super.register();
        templateLoader = new ClassPathRepoTemplateLoader(this.services.getNodeService(),
                this.services.getContentService(), defaultEncoding);
    }

    public void process(String template, Object model, Writer out) {
        TemplateSource templateSource;
        try {
            templateSource = (TemplateSource) templateLoader.findTemplateSource(template);
        } catch (IOException ex) {
            throw new TemplateException(MSG_UNABLE_TO_READ_TEMPLATE, new Object[] { ex.getMessage() }, ex);
        }
        process(templateSource, model, out);
    }

    public void processString(final String template, Object model, Writer out) {
        TemplateSource stringTemplateSource = new TemplateSource() {
            public long lastModified() {
                return System.currentTimeMillis();
            }

            public InputStream getResource(String name) {
                return null;
            }

            public Reader getReader(String encoding) throws IOException {
                return new StringReader(template);
            }

            public void close() throws IOException {
            }
        };
        process(stringTemplateSource, model, out);
    }

    /**
     * @param templateSource
     * @param model
     * @param out
     */
    private void process(TemplateSource templateSource, Object model, Writer out) {
        if ((model == null) || !XSLTemplateModel.class.isAssignableFrom(model.getClass())) {
            throw new IllegalArgumentException("\"model\" must be an XSLTemplateModel object: " + model);
        }

        XSLTemplateModel xsltModel = (XSLTemplateModel) model;
        System.setProperty("org.apache.xalan.extensions.bsf.BSFManager", BSFManager.class.getName());

        Document xslTemplate;
        try {
            xslTemplate = XMLUtil.parse(templateSource.getReader(defaultEncoding));
        } catch (IOException ex) {
            throw new TemplateException(MSG_UNABLE_TO_READ_TEMPLATE, new Object[] { ex.getMessage() }, ex);
        } catch (SAXException sax) {
            throw new TemplateException(MSG_UNABLE_TO_PARSE_TEMPLATE, new Object[] { sax.getMessage() }, sax);
        } finally {
            try {
                templateSource.close();
            } catch (IOException ex) {
                // There's little to be done here. Log it and carry on
                log.warn("Error while trying to close template stream", ex);
            }
        }

        List<String> scriptIds = addScripts(xsltModel, xslTemplate);
        addParameters(xsltModel, xslTemplate);

        final LinkedList<TransformerException> errors = new LinkedList<TransformerException>();
        final ErrorListener errorListener = new ErrorListener() {
            public void error(final TransformerException te) throws TransformerException {
                log.debug("error " + te.getMessageAndLocation());
                errors.add(te);
            }

            public void fatalError(final TransformerException te) throws TransformerException {
                log.debug("fatalError " + te.getMessageAndLocation());
                throw te;
            }

            public void warning(final TransformerException te) throws TransformerException {
                log.debug("warning " + te.getMessageAndLocation());
                errors.add(te);
            }
        };

        final TemplateSource resourceSource = templateSource;
        final URIResolver uriResolver = new URIResolver() {
            public Source resolve(final String href, String base) throws TransformerException {
                if (log.isDebugEnabled()) {
                    log.debug("request to resolve href " + href + " using base " + base);
                }
                InputStream in = null;
                try {
                    in = resourceSource.getResource(href);
                    if (in == null) {
                        throw new TransformerException("unable to resolve href " + href);
                    }

                    Document d = XMLUtil.parse(in);
                    if (log.isDebugEnabled()) {
                        log.debug("loaded " + XMLUtil.toString(d));
                    }
                    return new DOMSource(d);
                } catch (TransformerException ex) {
                    throw ex;
                } catch (Exception e) {
                    throw new TransformerException("unable to load " + href, e);
                }
            }
        };

        Source xmlSource = this.getXMLSource(xsltModel);

        Transformer t = null;
        try {
            final TransformerFactory tf = TransformerFactory.newInstance();
            tf.setErrorListener(errorListener);
            tf.setURIResolver(uriResolver);

            if (log.isDebugEnabled()) {
                log.debug("xslTemplate: \n" + XMLUtil.toString(xslTemplate));
            }

            t = tf.newTransformer(new DOMSource(xslTemplate));

            if (errors.size() != 0) {
                final StringBuilder msg = new StringBuilder("errors encountered creating tranformer ... \n");
                for (TransformerException te : errors) {
                    msg.append(te.getMessageAndLocation()).append("\n");
                }
                throw new TemplateException(msg.toString());
            }

            t.setErrorListener(errorListener);
            t.setURIResolver(uriResolver);
            t.setParameter("versionParam", "2.0");
        } catch (TransformerConfigurationException tce) {
            log.error(tce);
            throw new TemplateException(tce.getMessage(), tce);
        }

        try {
            t.transform(xmlSource, new StreamResult(out));
        } catch (TransformerException te) {
            log.error(te.getMessageAndLocation());
            throw new TemplateException(te.getMessageAndLocation(), te);
        } catch (Exception e) {
            log.error("unexpected error " + e);
            throw new TemplateException(e.getMessage(), e);
        } finally {
            //Clear out any scripts that were created for this transform
            if (!scriptIds.isEmpty()) {
                XSLTProcessorMethodInvoker.removeMethods(scriptIds);
            }
        }

        if (errors.size() != 0) {
            final StringBuilder msg = new StringBuilder("errors encountered during transformation ... \n");
            for (TransformerException te : errors) {
                msg.append(te.getMessageAndLocation()).append("\n");
            }
            throw new TemplateException(msg.toString());
        }
    }

    /**
     * Adds a script element to the xsl which makes static methods on this object available to the xsl tempalte.
     * 
     * @param xslTemplate
     *            the xsl template
     */
    protected List<String> addScripts(final XSLTemplateModel xsltModel, final Document xslTemplate) {
        final Map<QName, List<Map.Entry<QName, Object>>> methods = new HashMap<QName, List<Map.Entry<QName, Object>>>();
        for (final Map.Entry<QName, Object> entry : xsltModel.entrySet()) {
            if (entry.getValue() instanceof TemplateProcessorMethod) {
                final String prefix = QName.splitPrefixedQName(entry.getKey().toPrefixString())[0];
                final QName qn = QName.createQName(entry.getKey().getNamespaceURI(), prefix);
                if (!methods.containsKey(qn)) {
                    methods.put(qn, new LinkedList<Map.Entry<QName, Object>>());
                }
                methods.get(qn).add(entry);
            }
        }

        final Element docEl = xslTemplate.getDocumentElement();
        final String XALAN_NS = Constants.S_BUILTIN_EXTENSIONS_URL;
        final String XALAN_NS_PREFIX = "xalan";
        docEl.setAttribute("xmlns:" + XALAN_NS_PREFIX, XALAN_NS);

        final Set<String> excludePrefixes = new HashSet<String>();
        if (docEl.hasAttribute("exclude-result-prefixes")) {
            excludePrefixes.addAll(Arrays.asList(docEl.getAttribute("exclude-result-prefixes").split(" ")));
        }
        excludePrefixes.add(XALAN_NS_PREFIX);

        final List<String> result = new LinkedList<String>();
        for (QName ns : methods.keySet()) {
            final String prefix = ns.getLocalName();
            docEl.setAttribute("xmlns:" + prefix, ns.getNamespaceURI());
            excludePrefixes.add(prefix);

            final Element compEl = xslTemplate.createElementNS(XALAN_NS, XALAN_NS_PREFIX + ":component");
            compEl.setAttribute("prefix", prefix);
            docEl.appendChild(compEl);
            String functionNames = null;
            final Element scriptEl = xslTemplate.createElementNS(XALAN_NS, XALAN_NS_PREFIX + ":script");
            scriptEl.setAttribute("lang", "javascript");
            final StringBuilder js = new StringBuilder("var _xsltp_invoke = java.lang.Class.forName('"
                    + XSLTProcessorMethodInvoker.class.getName() + "').newInstance();\n"
                    + "function _xsltp_to_java_array(js_array) {\n"
                    + "var java_array = java.lang.reflect.Array.newInstance(java.lang.Object, js_array.length);\n"
                    + "for (var i = 0; i < js_array.length; i++) { java_array[i] = js_array[i]; }\n"
                    + "return java_array; }\n");
            for (final Map.Entry<QName, Object> entry : methods.get(ns)) {
                if (functionNames == null) {
                    functionNames = entry.getKey().getLocalName();
                } else {
                    functionNames += " " + entry.getKey().getLocalName();
                }
                final String id = entry.getKey().getLocalName() + entry.getValue().hashCode();
                js.append("function " + entry.getKey().getLocalName() + "() { return _xsltp_invoke.invokeMethod('"
                        + id + "', _xsltp_to_java_array(arguments)); }\n");
                XSLTProcessorMethodInvoker.addMethod(id, (TemplateProcessorMethod) entry.getValue());
                result.add(id);
            }
            log.debug("generated JavaScript bindings:\n" + js);
            scriptEl.appendChild(xslTemplate.createTextNode(js.toString()));
            compEl.setAttribute("functions", functionNames);
            compEl.appendChild(scriptEl);
        }
        docEl.setAttribute("exclude-result-prefixes",
                StringUtils.join(excludePrefixes.toArray(new String[excludePrefixes.size()]), " "));
        return result;
    }

    /**
     * Adds the specified parameters to the xsl template as variables within the alfresco namespace.
     * 
     * @param xsltModel
     *            the variables to place within the xsl template
     * @param xslTemplate
     *            the xsl template
     */
    protected void addParameters(final XSLTemplateModel xsltModel, final Document xslTemplate) {
        final Element docEl = xslTemplate.getDocumentElement();
        final String XSL_NS = docEl.getNamespaceURI();
        final String XSL_NS_PREFIX = docEl.getPrefix();

        for (Map.Entry<QName, Object> e : xsltModel.entrySet()) {
            if (ROOT_NAMESPACE.equals(e.getKey())) {
                continue;
            }
            final Element el = xslTemplate.createElementNS(XSL_NS, XSL_NS_PREFIX + ":variable");
            el.setAttribute("name", e.getKey().toPrefixString());
            final Object o = e.getValue();
            if (o instanceof String || o instanceof Number || o instanceof Boolean) {
                el.appendChild(xslTemplate.createTextNode(o.toString()));
                // ALF-15413. Add the variables at the end of the list of children
                docEl.insertBefore(el, null);
            }
        }
    }

    protected Source getXMLSource(final Map<QName, Object> model) {
        if (!model.containsKey(ROOT_NAMESPACE)) {
            return null;
        }
        final Object o = model.get(ROOT_NAMESPACE);
        if (!(o instanceof Document)) {
            throw new IllegalArgumentException("expected root namespace object to be a  " + Document.class.getName()
                    + ".  found a " + o.getClass().getName());
        }
        return new DOMSource((Document) o);
    }

    @Override
    public void process(String template, Object model, Writer out, Locale locale) {
        if (template.indexOf(StoreRef.URI_FILLER) != -1) {
            // If template is a node ref, ignore locale
            process(template, model, out);
        } else {
            //Otherwise try and locate a locale specific resource.
            TemplateSource templateSource = null;
            int lastDot = template.lastIndexOf('.');
            String prefix = lastDot == -1 ? template : template.substring(0, lastDot);
            String suffix = lastDot == -1 ? "" : template.substring(lastDot);
            String localeName = LOCALE_SEPARATOR + locale.toString();
            StringBuffer buf = new StringBuffer(template.length() + localeName.length());
            buf.append(prefix);
            for (;;) {
                buf.setLength(prefix.length());
                String path = buf.append(localeName).append(suffix).toString();
                try {
                    templateSource = (TemplateSource) templateLoader.findTemplateSource(path);
                } catch (IOException ex) {
                    throw new TemplateException(MSG_UNABLE_TO_READ_TEMPLATE, new Object[] { ex.getMessage() }, ex);
                }
                if (templateSource != null) {
                    break;
                }
                int lastUnderscore = localeName.lastIndexOf('_');
                if (lastUnderscore == -1) {
                    break;
                }
                localeName = localeName.substring(0, lastUnderscore);
            }
            if (templateSource == null) {
                throw new TemplateException(MSG_ERROR_NO_TEMPLATE, new Object[] { template });
            }
            process(templateSource, model, out);
        }
    }

}