Java tutorial
/* * #%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); } } }