diff --git a/config/alfresco/rendition-services-context.xml b/config/alfresco/rendition-services-context.xml
index 3f7849ad8a..74719d0d30 100644
--- a/config/alfresco/rendition-services-context.xml
+++ b/config/alfresco/rendition-services-context.xml
@@ -1,147 +1,164 @@
-
-
-
-
-
-
-
-
-
- org.alfresco.service.cmr.rendition.RenditionService
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${server.transaction.mode.default}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- alfresco.messages.rendition-config
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {http://www.alfresco.org/model/content/1.0}content
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.rendition.RenditionService
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${server.transaction.mode.default}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ alfresco.messages.rendition-config
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {http://www.alfresco.org/model/content/1.0}content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/alfresco/template-services-context.xml b/config/alfresco/template-services-context.xml
index 7571db139b..82ab907b57 100644
--- a/config/alfresco/template-services-context.xml
+++ b/config/alfresco/template-services-context.xml
@@ -31,6 +31,15 @@
+
+
+ xslt
+
+
+ xsl
+
+
+
diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java
index ebddb7325f..889f030b5c 100644
--- a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java
+++ b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java
@@ -235,6 +235,35 @@ public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest
});
}
+ public void testRenderFreeMarkerTemplateOneTransaction() throws Exception
+ {
+ this.setComplete();
+ this.endTransaction();
+ final QName renditionName = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI,
+ TemplatingRenderingEngine.NAME);
+
+ this.renditionNode = transactionHelper
+ .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback()
+ {
+ public NodeRef execute() throws Throwable
+ {
+ // create test model
+ RenditionDefinition definition = renditionService.createRenditionDefinition(renditionName,
+ TemplatingRenderingEngine.NAME);
+ definition.setParameterValue(TemplatingRenderingEngine.PARAM_TEMPLATE_NODE,
+ nodeWithFreeMarkerContent);
+ ChildAssociationRef renditionAssoc = renditionService
+ .render(nodeWithDocContent, definition);
+ assertNotNull("The rendition association was null", renditionAssoc);
+ String output = readTextContent(renditionAssoc.getChildRef());
+ assertNotNull("The rendition content was null.", output);
+ // check the output contains root node Id as expected.
+ assertTrue(output.contains(nodeWithDocContent.getId()));
+ return null;
+ }
+ });
+ }
+
public void testRenderFreemarkerTemplatePath() throws Exception
{
//TODO displayName paths.
diff --git a/source/java/org/alfresco/repo/rendition/executer/XSLTFunctions.java b/source/java/org/alfresco/repo/rendition/executer/XSLTFunctions.java
new file mode 100644
index 0000000000..a9581ded93
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/executer/XSLTFunctions.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2005-2010 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have received a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+
+package org.alfresco.repo.rendition.executer;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.dictionary.DictionaryService;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.model.FileInfo;
+import org.alfresco.service.cmr.model.FileNotFoundException;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.ContentService;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.util.XMLUtil;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+/**
+ * @author Brian Remmington
+ * @since 3.3
+ *
+ */
+public class XSLTFunctions
+{
+ private static final Log log = LogFactory.getLog(XSLTFunctions.class);
+
+ private FileFolderService fileService;
+ private ContentService contentService;
+ private NodeService nodeService;
+ private NamespaceService namespaceService;
+ private DictionaryService dictionaryService;
+
+ public XSLTFunctions()
+ {
+ }
+
+ public Document parseXMLDocument(final NodeRef root, String repoPath) throws IOException, SAXException,
+ FileNotFoundException
+ {
+ String[] pathElements = breakDownPath(repoPath);
+ FileInfo file = fileService.resolveNamePath(root, Arrays.asList(pathElements));
+ return XMLUtil.parse(file.getNodeRef(), contentService);
+ }
+
+ public Map parseXMLDocuments(final String typeName, NodeRef rootNode, String repoPath)
+ throws IOException, SAXException
+ {
+ final Map result = new TreeMap();
+
+ String[] pathElements = breakDownPath(repoPath);
+
+ try
+ {
+ FileInfo file = fileService.resolveNamePath(rootNode, Arrays.asList(pathElements));
+
+ if (file.isFolder())
+ {
+ QName typeQName = QName.createQName(typeName, namespaceService);
+ Set types = new HashSet(dictionaryService.getSubTypes(typeQName, true));
+ types.add(typeQName);
+ List children = nodeService.getChildAssocs(file.getNodeRef(), types);
+ for (ChildAssociationRef child : children)
+ {
+ String name = (String) nodeService.getProperty(child.getChildRef(), ContentModel.PROP_NAME);
+ Document doc = XMLUtil.parse(child.getChildRef(), contentService);
+ result.put(name, doc);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ log.warn("Unexpected exception caught in call to parseXMLDocuments", ex);
+ }
+ return result;
+ }
+
+ /**
+ * @param repoPath
+ * @return
+ */
+ private String[] breakDownPath(String repoPath)
+ {
+ if (repoPath.startsWith("/"))
+ {
+ repoPath = repoPath.substring(1);
+ }
+ String[] pathElements = repoPath.split("/");
+ return pathElements;
+ }
+
+ /**
+ * Encodes invalid HTML characters. (Fix for ETWOTWO-504 issue) This code was adopted from WebDAVHelper.encodeHTML()
+ * method with some restrictions.
+ *
+ * @see press-release.xsl for pattern.
+ *
+ * @param text
+ * to encode
+ * @return encoded text
+ * @throws IOException
+ * @throws SAXException
+ */
+ public String encodeQuotes(String text) throws IOException, SAXException
+ {
+ if (text == null)
+ {
+ return "";
+ }
+
+ StringBuilder sb = null; // create on demand
+ String enc;
+ char c;
+ for (int i = 0; i < text.length(); i++)
+ {
+ enc = null;
+ c = text.charAt(i);
+ switch (c)
+ {
+ case '"':
+ enc = """;
+ break; // "
+ // case '&': enc = "&"; break; //&
+ // case '<': enc = "<"; break; //<
+ // case '>': enc = ">"; break; //>
+
+ // german umlauts
+ case '\u00E4':
+ enc = "ä";
+ break;
+ case '\u00C4':
+ enc = "Ä";
+ break;
+ case '\u00F6':
+ enc = "ö";
+ break;
+ case '\u00D6':
+ enc = "Ö";
+ break;
+ case '\u00FC':
+ enc = "ü";
+ break;
+ case '\u00DC':
+ enc = "Ü";
+ break;
+ case '\u00DF':
+ enc = "ß";
+ break;
+
+ // misc
+ // case 0x80: enc = "€"; break; sometimes euro symbol is ascii 128, should we suport it?
+ case '\u20AC':
+ enc = "€";
+ break;
+ case '\u00AB':
+ enc = "«";
+ break;
+ case '\u00BB':
+ enc = "»";
+ break;
+ case '\u00A0':
+ enc = " ";
+ break;
+
+ // case '': enc = "&trade"; break;
+
+ default:
+ if (((int) c) >= 0x80)
+ {
+ // encode all non basic latin characters
+ enc = "" + ((int) c) + ";";
+ }
+ break;
+ }
+
+ if (enc != null)
+ {
+ if (sb == null)
+ {
+ String soFar = text.substring(0, i);
+ sb = new StringBuilder(i + 8);
+ sb.append(soFar);
+ }
+ sb.append(enc);
+ }
+ else
+ {
+ if (sb != null)
+ {
+ sb.append(c);
+ }
+ }
+ }
+
+ if (sb == null)
+ {
+ return text;
+ }
+ else
+ {
+ return sb.toString();
+ }
+ }
+
+ /**
+ * @param fileService the fileService to set
+ */
+ public void setFileService(FileFolderService fileService)
+ {
+ this.fileService = fileService;
+ }
+
+ /**
+ * @param contentService the contentService to set
+ */
+ public void setContentService(ContentService contentService)
+ {
+ this.contentService = contentService;
+ }
+
+ /**
+ * @param nodeService the nodeService to set
+ */
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ /**
+ * @param namespaceService the namespaceService to set
+ */
+ public void setNamespaceService(NamespaceService namespaceService)
+ {
+ this.namespaceService = namespaceService;
+ }
+
+ /**
+ * @param dictionaryService the dictionaryService to set
+ */
+ public void setDictionaryService(DictionaryService dictionaryService)
+ {
+ this.dictionaryService = dictionaryService;
+ }
+}
diff --git a/source/java/org/alfresco/repo/rendition/executer/XSLTFunctionsTest.java b/source/java/org/alfresco/repo/rendition/executer/XSLTFunctionsTest.java
new file mode 100644
index 0000000000..89bb43c0c4
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/executer/XSLTFunctionsTest.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2005-2010 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have received a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+
+package org.alfresco.repo.rendition.executer;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.model.filefolder.FileFolderServiceImpl;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.model.FileInfo;
+import org.alfresco.service.cmr.repository.ContentService;
+import org.alfresco.service.cmr.repository.ContentWriter;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.search.ResultSet;
+import org.alfresco.service.cmr.search.SearchService;
+import org.alfresco.util.BaseAlfrescoSpringTest;
+import org.alfresco.util.GUID;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+
+/**
+ * @author Brian
+ *
+ */
+public class XSLTFunctionsTest extends BaseAlfrescoSpringTest
+{
+ private final static Log log = LogFactory.getLog(XSLTFunctionsTest.class);
+ private XSLTFunctions xsltFunctions;
+ private SearchService searchService;
+ private NodeRef companyHome;
+ private FileFolderService fileFolderService;
+
+ /* (non-Javadoc)
+ * @see org.alfresco.util.BaseAlfrescoSpringTest#onSetUpInTransaction()
+ */
+ @SuppressWarnings("deprecation")
+ @Override
+ protected void onSetUpInTransaction() throws Exception
+ {
+ super.onSetUpInTransaction();
+ this.searchService = (SearchService) this.applicationContext.getBean("SearchService");
+ this.xsltFunctions = (XSLTFunctions) this.applicationContext.getBean("xsltFunctions");
+ this.nodeService = (NodeService) this.applicationContext.getBean("NodeService");
+ this.contentService = (ContentService) this.applicationContext.getBean("ContentService");
+ this.fileFolderService = (FileFolderService) this.applicationContext.getBean("FileFolderService");
+ ResultSet rs = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, "/app:company_home");
+ this.companyHome = rs.getNodeRef(0);
+ }
+
+ public void testSimplestParseXMLDocument()
+ {
+ FileInfo file = createXmlFile(companyHome);
+ try
+ {
+ Document doc = xsltFunctions.parseXMLDocument(companyHome, file.getName());
+ NodeList foodNodes = doc.getElementsByTagName("food");
+ assertEquals(10, foodNodes.getLength());
+ }
+ catch (Exception ex)
+ {
+ log.error("Error!", ex);
+ fail(ex.getMessage());
+ }
+ }
+
+ public void testPathParseXMLDocument()
+ {
+ String path = "path/to/xml/files";
+ List pathElements = Arrays.asList(path.split("/"));
+ FileInfo folder = FileFolderServiceImpl.makeFolders(fileFolderService, companyHome, pathElements, ContentModel.TYPE_FOLDER);
+ FileInfo file = createXmlFile(folder.getNodeRef());
+
+ try
+ {
+ Document doc = xsltFunctions.parseXMLDocument(companyHome, path + "/" + file.getName());
+ NodeList foodNodes = doc.getElementsByTagName("food");
+ assertEquals(10, foodNodes.getLength());
+ }
+ catch (Exception ex)
+ {
+ log.error("Error!", ex);
+ fail(ex.getMessage());
+ }
+ }
+
+ public void testParseXMLDocuments()
+ {
+ String path = "path/to/xml/files";
+ List pathElements = Arrays.asList(path.split("/"));
+ FileInfo folder = FileFolderServiceImpl.makeFolders(fileFolderService, companyHome, pathElements, ContentModel.TYPE_FOLDER);
+ FileInfo file1 = createXmlFile(folder.getNodeRef());
+ FileInfo file2 = createXmlFile(folder.getNodeRef());
+ FileInfo file3 = createXmlFile(folder.getNodeRef());
+ FileInfo file4 = createXmlFile(folder.getNodeRef());
+ FileInfo file5 = createXmlFile(folder.getNodeRef());
+
+ try
+ {
+ Map xmlFileMap = xsltFunctions.parseXMLDocuments("cm:content",
+ companyHome, "/" + path);
+ assertEquals(5, xmlFileMap.size());
+ Set names = new TreeSet(Arrays.asList(new String[] {file1.getName(), file2.getName(),
+ file3.getName(), file4.getName(), file5.getName()}));
+ names.removeAll(xmlFileMap.keySet());
+ assertEquals(0, names.size());
+
+ NodeList foodNodes = xmlFileMap.get(file3.getName()).getElementsByTagName("food");
+ assertEquals(10, foodNodes.getLength());
+ }
+ catch (Exception ex)
+ {
+ log.error("Error!", ex);
+ fail(ex.getMessage());
+ }
+ }
+
+ /**
+ *
+ */
+ private FileInfo createXmlFile(NodeRef folder)
+ {
+ String name = GUID.generate();
+ FileInfo testXmlFile = fileFolderService.create(folder, name + ".xml", ContentModel.TYPE_CONTENT);
+ ContentWriter writer = contentService.getWriter(testXmlFile.getNodeRef(), ContentModel.PROP_CONTENT, true);
+ writer.setMimetype("text/xml");
+ writer.setEncoding("UTF-8");
+ writer.putContent(sampleXML);
+ return testXmlFile;
+ }
+
+
+ private String sampleXML = "" +
+ "" +
+
+ "" +
+ "65" +
+ "20" +
+ "300" +
+ "2400" +
+ "300" +
+ "25" +
+ "50" +
+ "" +
+
+ "" +
+ "Avocado Dip" +
+ "Sunnydale" +
+ "29" +
+ "" +
+ "11" +
+ "3" +
+ "5" +
+ "210" +
+ "2" +
+ "0" +
+ "1" +
+ "" +
+ "0" +
+ "0" +
+ "" +
+ "" +
+ "0" +
+ "0" +
+ "" +
+ "" +
+
+ "" +
+ "Bagels, New York Style " +
+ "Thompson" +
+ "104" +
+ "" +
+ "4" +
+ "1" +
+ "0" +
+ "510" +
+ "54" +
+ "3" +
+ "11" +
+ "" +
+ "0" +
+ "0" +
+ "" +
+ "" +
+ "8" +
+ "20" +
+ "" +
+ "" +
+
+ "" +
+ "Beef Frankfurter, Quarter Pound " +
+ "Armitage" +
+ "115" +
+ "" +
+ "32" +
+ "15" +
+ "65" +
+ "1100" +
+ "8" +
+ "0" +
+ "13" +
+ "" +
+ "0" +
+ "2" +
+ "" +
+ "" +
+ "1" +
+ "6" +
+ "" +
+ "" +
+
+ "" +
+ "Chicken Pot Pie" +
+ "Lakeson" +
+ "198" +
+ "" +
+ "22" +
+ "9" +
+ "25" +
+ "810" +
+ "42" +
+ "2" +
+ "10" +
+ "" +
+ "20" +
+ "2" +
+ "" +
+ "" +
+ "2" +
+ "10" +
+ "" +
+ "" +
+
+ "" +
+ "Cole Slaw" +
+ "Fresh Quick" +
+ "1.5" +
+ "" +
+ "0" +
+ "0" +
+ "0" +
+ "15" +
+ "5" +
+ "2" +
+ "1" +
+ "" +
+ "30" +
+ "45" +
+ "" +
+ "" +
+ "4" +
+ "2" +
+ "" +
+ "" +
+
+ "" +
+ "Eggs" +
+ "Goodpath" +
+ "50" +
+ "" +
+ "4.5" +
+ "1.5" +
+ "215" +
+ "65" +
+ "1" +
+ "0" +
+ "6" +
+ "" +
+ "6" +
+ "0" +
+ "" +
+ "" +
+ "2" +
+ "4" +
+ "" +
+ "" +
+
+ "" +
+ "Hazelnut Spread" +
+ "Ferreira" +
+ "2" +
+ "" +
+ "10" +
+ "2" +
+ "0" +
+ "20" +
+ "23" +
+ "2" +
+ "3" +
+ "" +
+ "0" +
+ "0" +
+ "" +
+ "" +
+ "6" +
+ "4" +
+ "" +
+ "" +
+
+ "" +
+ "Potato Chips" +
+ "Lees" +
+ "28" +
+ "" +
+ "10" +
+ "3" +
+ "0" +
+ "180" +
+ "15" +
+ "1" +
+ "2" +
+ "" +
+ "0" +
+ "10" +
+ "" +
+ "" +
+ "0" +
+ "0" +
+ "" +
+ "" +
+
+ "" +
+ "Soy Patties, Grilled" +
+ "Gardenproducts" +
+ "96" +
+ "" +
+ "5" +
+ "0" +
+ "0" +
+ "420" +
+ "10" +
+ "4" +
+ "9" +
+ "" +
+ "0" +
+ "0" +
+ "" +
+ "" +
+ "0" +
+ "0" +
+ "" +
+ "" +
+
+ "" +
+ "Truffles, Dark Chocolate" +
+ "Lyndon's" +
+ "39" +
+ "" +
+ "19" +
+ "14" +
+ "25" +
+ "10" +
+ "16" +
+ "1" +
+ "1" +
+ "" +
+ "0" +
+ "0" +
+ "" +
+ "" +
+ "0" +
+ "0" +
+ "" +
+ "" +
+
+ "";
+}
diff --git a/source/java/org/alfresco/repo/rendition/executer/XSLTRenderingEngine.java b/source/java/org/alfresco/repo/rendition/executer/XSLTRenderingEngine.java
new file mode 100644
index 0000000000..c633b2d27f
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/executer/XSLTRenderingEngine.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2005-2010 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+
+package org.alfresco.repo.rendition.executer;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.action.ParameterDefinitionImpl;
+import org.alfresco.repo.template.TemplateProcessorMethod;
+import org.alfresco.repo.template.XSLTProcessor;
+import org.alfresco.repo.template.XSLTemplateModel;
+import org.alfresco.service.cmr.action.ParameterDefinition;
+import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.model.FileInfo;
+import org.alfresco.service.cmr.model.FileNotFoundException;
+import org.alfresco.service.cmr.rendition.RenditionServiceException;
+import org.alfresco.service.cmr.repository.ContentWriter;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.repository.TemplateService;
+import org.alfresco.service.cmr.search.ResultSet;
+import org.alfresco.service.cmr.search.SearchService;
+import org.alfresco.service.namespace.NamespacePrefixResolver;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.util.XMLUtil;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+/**
+ * @author Brian Remmington
+ * @since 3.3
+ */
+public class XSLTRenderingEngine extends AbstractRenderingEngine
+{
+ private static final Log log = LogFactory.getLog(XSLTRenderingEngine.class);
+
+ public static final String NAME = "xsltRenderingEngine";
+ public static final String PARAM_MODEL = "model";
+ public static final String PARAM_TEMPLATE = "template_string";
+ public static final String PARAM_TEMPLATE_NODE = "template_node";
+ public static final String PARAM_TEMPLATE_PATH = "template_path";
+
+ private TemplateService templateService;
+ private XSLTFunctions xsltFunctions;
+ private NamespacePrefixResolver namespacePrefixResolver;
+ private FileFolderService fileFolderService;
+ private SearchService searchService;
+
+ /*
+ * @see org.alfresco.repo.rendition.executer.AbstractRenderingEngine#render(org
+ * .alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.rendition.RenditionDefinition,
+ * org.alfresco.service.cmr.repository.ContentReader, org.alfresco.service.cmr.repository.ChildAssociationRef)
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ protected void render(RenderingContext context)
+ {
+ NodeRef templateNode = getTemplateNode(context);
+ Map paramMap = context.getCheckedParam(PARAM_MODEL, Map.class);
+ try
+ {
+ XSLTemplateModel model = buildModel(context, paramMap);
+ ContentWriter contentWriter = context.makeContentWriter();
+ Writer writer = new OutputStreamWriter(contentWriter.getContentOutputStream());
+ processTemplate(context, templateNode, model, writer);
+ writer.flush();
+ writer.close();
+ }
+ catch (Exception ex)
+ {
+ log.warn("Unexpected error while rendering through XSLT rendering engine.", ex);
+ }
+ }
+
+ private void processTemplate(RenderingContext context, NodeRef templateNode, XSLTemplateModel model, Writer out)
+ {
+ String template = context.getCheckedParam(PARAM_TEMPLATE, String.class);
+ if (template != null)
+ {
+ templateService.processTemplateString("xslt", (String) template, model, out);
+ }
+ else if (templateNode != null)
+ {
+ templateService.processTemplate("xslt", templateNode.toString(), model, out);
+ }
+ else
+ {
+ throwTemplateParamsNotFoundException();
+ }
+ }
+
+ private void throwTemplateParamsNotFoundException()
+ {
+ StringBuilder msg = new StringBuilder("This action requires that either the ");
+ msg.append(PARAM_TEMPLATE);
+ msg.append(" parameter or the ");
+ msg.append(PARAM_TEMPLATE_NODE);
+ msg.append(" parameter be specified. ");
+ throw new RenditionServiceException(msg.toString());
+ }
+
+ private NodeRef getTemplateNode(RenderingContext context)
+ {
+ NodeRef node = context.getCheckedParam(PARAM_TEMPLATE_NODE, NodeRef.class);
+ if (node == null)
+ {
+ String path = context.getCheckedParam(PARAM_TEMPLATE_PATH, String.class);
+ if (path != null && path.length() > 0)
+ {
+ StoreRef storeRef = context.getDestinationNode().getStoreRef();
+ ResultSet result = searchService.query(storeRef, SearchService.LANGUAGE_XPATH, path);
+ if (result.length() != 1)
+ {
+ throw new RenditionServiceException("Could not find template node for path: " + path);
+ }
+ node = result.getNodeRef(0);
+ }
+ }
+ return node;
+ }
+
+ @SuppressWarnings("serial")
+ protected XSLTemplateModel buildModel(RenderingContext context, Map suppliedParams)
+ throws IOException, SAXException
+ {
+ final NodeRef sourceNode = context.getSourceNode();
+ final NodeRef parentNode = nodeService.getPrimaryParent(context.getSourceNode()).getParentRef();
+ final String sourcePath = getPath(sourceNode);
+ final String parentPath = getPath(parentNode);
+
+ XSLTemplateModel model = new XSLTemplateModel();
+
+ // add simple scalar parameters
+ model.put(QName.createQName(NamespaceService.ALFRESCO_URI, "date"), new Date());
+
+ model.put(QName.createQName(NamespaceService.ALFRESCO_PREFIX, "source_file_name", namespacePrefixResolver),
+ nodeService.getProperty(context.getSourceNode(), ContentModel.PROP_NAME));
+ model.put(QName.createQName(NamespaceService.ALFRESCO_PREFIX, "source_path", namespacePrefixResolver),
+ sourcePath);
+ model.put(QName.createQName(NamespaceService.ALFRESCO_PREFIX, "parent_path", namespacePrefixResolver),
+ parentPath);
+
+ // add methods
+ model.put(QName.createQName(NamespaceService.ALFRESCO_PREFIX, "encodeQuotes", namespacePrefixResolver),
+ new TemplateProcessorMethod()
+ {
+ public Object exec(final Object[] arguments) throws IOException, SAXException
+ {
+ if (arguments.length != 1)
+ {
+ throw new IllegalArgumentException("expected 1 argument to encodeQuotes. got "
+ + arguments.length);
+
+ }
+ if (!(arguments[0] instanceof String))
+ {
+ throw new ClassCastException("expected arguments[0] to be a " + String.class.getName()
+ + ". got a " + arguments[0].getClass().getName() + ".");
+ }
+ String text = (String) arguments[0];
+
+ if (log.isDebugEnabled())
+ {
+ log.debug("tpm_encodeQuotes('" + text + "'), parentPath = " + parentPath);
+ }
+
+ final String result = xsltFunctions.encodeQuotes(text);
+ return result;
+ }
+ });
+
+ model.put(QName.createQName(NamespaceService.ALFRESCO_PREFIX, "parseXMLDocument", namespacePrefixResolver),
+ new TemplateProcessorMethod()
+ {
+ public Object exec(final Object[] arguments) throws IOException, SAXException
+ {
+ if (arguments.length != 1)
+ {
+ throw new IllegalArgumentException("expected 1 argument to parseXMLDocument. got "
+ + arguments.length);
+
+ }
+ if (!(arguments[0] instanceof String))
+ {
+ throw new ClassCastException("expected arguments[0] to be a " + String.class.getName()
+ + ". got a " + arguments[0].getClass().getName() + ".");
+ }
+ String path = (String) arguments[0];
+
+ if (log.isDebugEnabled())
+ {
+ log.debug("parseXMLDocument('" + path + "'), parentPath = " + parentPath);
+ }
+
+ Document d = null;
+ try
+ {
+ d = xsltFunctions.parseXMLDocument(parentNode, path);
+ }
+ catch (Exception ex)
+ {
+
+ }
+ return d == null ? null : d.getDocumentElement();
+ }
+ });
+ model.put(QName.createQName(NamespaceService.ALFRESCO_PREFIX, "parseXMLDocuments", namespacePrefixResolver),
+ new TemplateProcessorMethod()
+ {
+ public Object exec(final Object[] arguments) throws IOException, SAXException
+ {
+ if (arguments.length > 2)
+ {
+ throw new IllegalArgumentException("expected one or two arguments to "
+ + "parseXMLDocuments. got " + arguments.length);
+ }
+ if (!(arguments[0] instanceof String))
+ {
+ throw new ClassCastException("expected arguments[0] to be a " + String.class.getName()
+ + ". got a " + arguments[0].getClass().getName() + ".");
+ }
+
+ if (arguments.length == 2 && !(arguments[1] instanceof String))
+ {
+ throw new ClassCastException("expected arguments[1] to be a " + String.class.getName()
+ + ". got a " + arguments[1].getClass().getName() + ".");
+ }
+
+ String path = arguments.length == 2 ? (String) arguments[1] : "";
+ final String typeName = (String) arguments[0];
+
+ if (log.isDebugEnabled())
+ {
+ log.debug("tpm_parseXMLDocuments('" + typeName + "','" + path + "'), parentPath = "
+ + parentPath);
+ }
+
+ final Map resultMap = xsltFunctions.parseXMLDocuments(typeName, parentNode,
+ path);
+
+ if (log.isDebugEnabled())
+ {
+ log.debug("received " + resultMap.size() + " documents in " + path + " with form name "
+ + typeName);
+ }
+
+ // create a root document for rooting all the results. we do this
+ // so that each document root element has a common parent node
+ // and so that xpath axes work properly
+ final Document rootNodeDocument = XMLUtil.newDocument();
+ final Element rootNodeDocumentEl = rootNodeDocument.createElementNS(
+ NamespaceService.ALFRESCO_URI, NamespaceService.ALFRESCO_PREFIX + ":file_list");
+ rootNodeDocumentEl.setAttribute("xmlns:" + NamespaceService.ALFRESCO_PREFIX,
+ NamespaceService.ALFRESCO_URI);
+ rootNodeDocument.appendChild(rootNodeDocumentEl);
+
+ final List result = new ArrayList(resultMap.size());
+ for (Map.Entry e : resultMap.entrySet())
+ {
+ final Element documentEl = e.getValue().getDocumentElement();
+ documentEl.setAttribute("xmlns:" + NamespaceService.ALFRESCO_PREFIX,
+ NamespaceService.ALFRESCO_URI);
+ documentEl.setAttributeNS(NamespaceService.ALFRESCO_URI, NamespaceService.ALFRESCO_PREFIX
+ + ":file_name", e.getKey());
+ final Node n = rootNodeDocument.importNode(documentEl, true);
+ rootNodeDocumentEl.appendChild(n);
+ result.add(n);
+ }
+ return result.toArray(new Node[result.size()]);
+ }
+ });
+
+ if (suppliedParams != null)
+ {
+ for (Map.Entry suppliedParam : suppliedParams.entrySet())
+ {
+ model.put(QName.createQName(suppliedParam.getKey()), suppliedParam.getValue());
+ }
+ }
+
+ // add the xml document
+ model.put(XSLTProcessor.ROOT_NAMESPACE, XMLUtil.parse(sourceNode, contentService));
+ return model;
+ }
+
+ /**
+ * @param nodeRef
+ * @return
+ * @throws FileNotFoundException
+ */
+ private String getPath(NodeRef nodeRef)
+ {
+ StringBuilder sb = new StringBuilder();
+ try
+ {
+ List parentFileInfoList = fileFolderService.getNamePath(null, nodeRef);
+ for (FileInfo fileInfo : parentFileInfoList)
+ {
+ sb.append('/');
+ sb.append(fileInfo.getName());
+ }
+ }
+ catch (FileNotFoundException ex)
+ {
+ log.info("Unexpected problem: error while calculating path to node " + nodeRef, ex);
+ }
+ String path = sb.toString();
+ return path;
+ }
+
+ /*
+ * @seeorg.alfresco.repo.rendition.executer.AbstractRenderingEngine# getParameterDefinitions()
+ */
+ @Override
+ protected Collection getParameterDefinitions()
+ {
+ Collection paramList = super.getParameterDefinitions();
+ ParameterDefinitionImpl modelParamDef = new ParameterDefinitionImpl(PARAM_MODEL, DataTypeDefinition.ANY, false,
+ getParamDisplayLabel(PARAM_MODEL));
+ ParameterDefinitionImpl templateParamDef = new ParameterDefinitionImpl(//
+ PARAM_TEMPLATE, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_TEMPLATE));
+ ParameterDefinitionImpl templateNodeParamDef = new ParameterDefinitionImpl(PARAM_TEMPLATE_NODE,
+ DataTypeDefinition.NODE_REF, false, getParamDisplayLabel(PARAM_TEMPLATE_NODE));
+ ParameterDefinitionImpl templatePathParamDef = new ParameterDefinitionImpl(PARAM_TEMPLATE_PATH,
+ DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_TEMPLATE_PATH));
+ paramList.add(modelParamDef);
+ paramList.add(templateParamDef);
+ paramList.add(templateNodeParamDef);
+ paramList.add(templatePathParamDef);
+ return paramList;
+ }
+
+ /**
+ * @param templateService
+ * the templateService to set
+ */
+ public void setTemplateService(TemplateService templateService)
+ {
+ this.templateService = templateService;
+ }
+
+ /**
+ * @param xsltFunctions
+ * the xsltFunctions to set
+ */
+ public void setXsltFunctions(XSLTFunctions xsltFunctions)
+ {
+ this.xsltFunctions = xsltFunctions;
+ }
+
+ /**
+ * @param namespacePrefixResolver
+ * the namespacePrefixResolver to set
+ */
+ public void setNamespacePrefixResolver(NamespacePrefixResolver namespacePrefixResolver)
+ {
+ this.namespacePrefixResolver = namespacePrefixResolver;
+ }
+
+ /**
+ * @param fileFolderService
+ * the fileFolderService to set
+ */
+ public void setFileFolderService(FileFolderService fileFolderService)
+ {
+ this.fileFolderService = fileFolderService;
+ }
+
+ /**
+ * @param searchService
+ * the searchService to set
+ */
+ public void setSearchService(SearchService searchService)
+ {
+ this.searchService = searchService;
+ }
+}
diff --git a/source/java/org/alfresco/repo/rendition/executer/XSLTRenderingEngineTest.java b/source/java/org/alfresco/repo/rendition/executer/XSLTRenderingEngineTest.java
new file mode 100644
index 0000000000..afb01c25fa
--- /dev/null
+++ b/source/java/org/alfresco/repo/rendition/executer/XSLTRenderingEngineTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2005-2010 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have received a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+
+package org.alfresco.repo.rendition.executer;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.repo.model.filefolder.FileFolderServiceImpl;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.model.FileInfo;
+import org.alfresco.service.cmr.rendition.RenditionDefinition;
+import org.alfresco.service.cmr.rendition.RenditionService;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.ContentReader;
+import org.alfresco.service.cmr.repository.ContentService;
+import org.alfresco.service.cmr.repository.ContentWriter;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.repository.TemplateProcessor;
+import org.alfresco.service.cmr.repository.TemplateService;
+import org.alfresco.service.cmr.search.ResultSet;
+import org.alfresco.service.cmr.search.SearchService;
+import org.alfresco.service.namespace.QName;
+import org.alfresco.util.BaseAlfrescoSpringTest;
+import org.alfresco.util.GUID;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * @author Brian
+ *
+ */
+public class XSLTRenderingEngineTest extends BaseAlfrescoSpringTest
+{
+ private final static Log log = LogFactory.getLog(XSLTRenderingEngineTest.class);
+ private XSLTFunctions xsltFunctions;
+ private SearchService searchService;
+ private NodeRef companyHome;
+ private FileFolderService fileFolderService;
+ private TemplateProcessor xsltProcessor;
+ private TemplateService templateService;
+ private RenditionService renditionService;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.alfresco.util.BaseAlfrescoSpringTest#onSetUpInTransaction()
+ */
+ @SuppressWarnings("deprecation")
+ @Override
+ protected void onSetUpInTransaction() throws Exception
+ {
+ super.onSetUpInTransaction();
+ this.searchService = (SearchService) this.applicationContext.getBean("SearchService");
+ this.xsltFunctions = (XSLTFunctions) this.applicationContext.getBean("xsltFunctions");
+ this.nodeService = (NodeService) this.applicationContext.getBean("NodeService");
+ this.contentService = (ContentService) this.applicationContext.getBean("ContentService");
+ this.fileFolderService = (FileFolderService) this.applicationContext.getBean("FileFolderService");
+ this.xsltProcessor = (TemplateProcessor) this.applicationContext.getBean("xsltProcessor");
+ this.templateService = (TemplateService) this.applicationContext.getBean("TemplateService");
+ this.renditionService = (RenditionService) this.applicationContext.getBean("RenditionService");
+ ResultSet rs = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH,
+ "/app:company_home");
+ this.companyHome = rs.getNodeRef(0);
+ }
+
+ public void testSimplestStringTemplate() throws Exception
+ {
+ try
+ {
+ FileInfo file = createXmlFile(companyHome);
+ FileInfo xslFile = createXmlFile(companyHome, verySimpleXSLT);
+
+ RenditionDefinition def = renditionService.createRenditionDefinition(QName.createQName("Test"), XSLTRenderingEngine.NAME);
+ def.setParameterValue(XSLTRenderingEngine.PARAM_TEMPLATE_NODE, xslFile.getNodeRef());
+
+ ChildAssociationRef rendition = renditionService.render(file.getNodeRef(), def);
+
+ assertNotNull(rendition);
+
+ ContentReader reader = contentService.getReader(rendition.getChildRef(), ContentModel.PROP_CONTENT);
+ assertNotNull(reader);
+ String output = reader.getContentString();
+
+ log.debug("XSLT Processor output: " + output);
+ assertEquals("Avocado DipBagels, New York StyleBeef Frankfurter, Quarter PoundChicken Pot PieCole SlawEggsHazelnut SpreadPotato ChipsSoy Patties, GrilledTruffles, Dark Chocolate", output);
+ }
+ catch (Exception ex)
+ {
+ log.error("Error!", ex);
+ fail();
+ }
+ }
+
+
+ public void testParseXMLDocument() throws Exception
+ {
+ try
+ {
+ FileInfo file = createXmlFile(companyHome);
+ createXmlFile(companyHome, "TestXML.xml", sampleXML);
+ FileInfo xslFile = createXmlFile(companyHome, callParseXmlDocument);
+
+ RenditionDefinition def = renditionService.createRenditionDefinition(QName.createQName("Test"), XSLTRenderingEngine.NAME);
+ def.setParameterValue(XSLTRenderingEngine.PARAM_TEMPLATE_NODE, xslFile.getNodeRef());
+
+ ChildAssociationRef rendition = renditionService.render(file.getNodeRef(), def);
+
+ assertNotNull(rendition);
+
+ ContentReader reader = contentService.getReader(rendition.getChildRef(), ContentModel.PROP_CONTENT);
+ assertNotNull(reader);
+ String output = reader.getContentString();
+
+ log.debug("XSLT Processor output: " + output);
+ assertEquals("Avocado DipBagels, New York StyleBeef Frankfurter, Quarter PoundChicken Pot PieCole SlawEggsHazelnut SpreadPotato ChipsSoy Patties, GrilledTruffles, Dark Chocolate", output);
+ }
+ catch (Exception ex)
+ {
+ log.error("Error!", ex);
+ fail();
+ }
+ }
+
+ public void testParseXMLDocuments() throws Exception
+ {
+ try
+ {
+ FileInfo file = createXmlFile(companyHome);
+ String path = "path/to/xml/files";
+ List pathElements = Arrays.asList(path.split("/"));
+ FileInfo folder = FileFolderServiceImpl.makeFolders(fileFolderService, companyHome, pathElements, ContentModel.TYPE_FOLDER);
+ createXmlFile(folder.getNodeRef());
+ createXmlFile(folder.getNodeRef());
+ createXmlFile(folder.getNodeRef());
+ createXmlFile(folder.getNodeRef());
+ createXmlFile(folder.getNodeRef());
+ FileInfo xslFile = createXmlFile(companyHome, callParseXmlDocuments);
+
+ RenditionDefinition def = renditionService.createRenditionDefinition(QName.createQName("Test"), XSLTRenderingEngine.NAME);
+ def.setParameterValue(XSLTRenderingEngine.PARAM_TEMPLATE_NODE, xslFile.getNodeRef());
+
+ ChildAssociationRef rendition = renditionService.render(file.getNodeRef(), def);
+
+ assertNotNull(rendition);
+
+ ContentReader reader = contentService.getReader(rendition.getChildRef(), ContentModel.PROP_CONTENT);
+ assertNotNull(reader);
+ String output = reader.getContentString();
+
+ log.debug("XSLT Processor output: " + output);
+ assertEquals(
+ "Avocado DipBagels, New York StyleBeef Frankfurter, Quarter PoundChicken Pot PieCole SlawEggsHazelnut SpreadPotato ChipsSoy Patties, GrilledTruffles, Dark Chocolate"+
+ "Avocado DipBagels, New York StyleBeef Frankfurter, Quarter PoundChicken Pot PieCole SlawEggsHazelnut SpreadPotato ChipsSoy Patties, GrilledTruffles, Dark Chocolate"+
+ "Avocado DipBagels, New York StyleBeef Frankfurter, Quarter PoundChicken Pot PieCole SlawEggsHazelnut SpreadPotato ChipsSoy Patties, GrilledTruffles, Dark Chocolate"+
+ "Avocado DipBagels, New York StyleBeef Frankfurter, Quarter PoundChicken Pot PieCole SlawEggsHazelnut SpreadPotato ChipsSoy Patties, GrilledTruffles, Dark Chocolate"+
+ "Avocado DipBagels, New York StyleBeef Frankfurter, Quarter PoundChicken Pot PieCole SlawEggsHazelnut SpreadPotato ChipsSoy Patties, GrilledTruffles, Dark Chocolate"
+ , output);
+ }
+ catch (Exception ex)
+ {
+ log.error("Error!", ex);
+ fail();
+ }
+ }
+
+
+ private FileInfo createXmlFile(NodeRef folder)
+ {
+ return createXmlFile(folder, sampleXML);
+ }
+
+ private FileInfo createXmlFile(NodeRef folder, String content)
+ {
+ String name = GUID.generate() + ".xml";
+ return createXmlFile(folder, name, content);
+ }
+
+ private FileInfo createXmlFile(NodeRef folder, String filename, String content)
+ {
+ FileInfo testXmlFile = fileFolderService.create(folder, filename, ContentModel.TYPE_CONTENT);
+ ContentWriter writer = contentService.getWriter(testXmlFile.getNodeRef(), ContentModel.PROP_CONTENT, true);
+ writer.setMimetype("text/xml");
+ writer.setEncoding("UTF-8");
+ writer.putContent(content);
+ return testXmlFile;
+ }
+
+ private String sampleXML = "" + "" +
+
+ "" + "65" + "20"
+ + "300" + "2400"
+ + "300" + "25" + "50"
+ + "" +
+
+ "" + "Avocado Dip" + "Sunnydale" + "29"
+ + "" + "11"
+ + "3" + "5" + "210"
+ + "2" + "0" + "1" + "" + "0" + "0"
+ + "" + "" + "0" + "0" + "" + "" +
+
+ "" + "Bagels, New York Style" + "Thompson"
+ + "104" + ""
+ + "4" + "1" + "0"
+ + "510" + "54" + "3" + "11" + ""
+ + "0" + "0" + "" + "" + "8" + "20" + ""
+ + "" +
+
+ "" + "Beef Frankfurter, Quarter Pound" + "Armitage"
+ + "115" + ""
+ + "32" + "15" + "65"
+ + "1100" + "8" + "0" + "13" + ""
+ + "0" + "2" + "" + "" + "1" + "6" + ""
+ + "" +
+
+ "" + "Chicken Pot Pie" + "Lakeson" + "198"
+ + "" + "22"
+ + "9" + "25" + "810"
+ + "42" + "2" + "10" + "" + "20"
+ + "2" + "" + "" + "2" + "10" + "" + "" +
+
+ "" + "Cole Slaw" + "Fresh Quick" + "1.5"
+ + "" + "0" + "0"
+ + "0" + "15" + "5" + "2"
+ + "1" + "" + "30" + "45" + "" + ""
+ + "4" + "2" + "" + "" +
+
+ "" + "Eggs" + "Goodpath" + "50"
+ + "" + "4.5"
+ + "1.5" + "215" + "65"
+ + "1" + "0" + "6" + "" + "6" + "0"
+ + "" + "" + "2" + "4" + "" + "" +
+
+ "" + "Hazelnut Spread" + "Ferreira" + "2"
+ + "" + "10" + "2"
+ + "0" + "20" + "23" + "2"
+ + "3" + "" + "0" + "0" + "" + ""
+ + "6" + "4" + "" + "" +
+
+ "" + "Potato Chips" + "Lees" + "28"
+ + "" + "10" + "3"
+ + "0" + "180" + "15" + "1"
+ + "2" + "" + "0" + "10" + "" + ""
+ + "0" + "0" + "" + "" +
+
+ "" + "Soy Patties, Grilled" + "Gardenproducts"
+ + "96" + "" + "5"
+ + "0" + "0" + "420"
+ + "10" + "4" + "9" + "" + "0" + "0"
+ + "" + "" + "0" + "0" + "" + "" +
+
+ "" + "Truffles, Dark Chocolate" + "Lyndon's"
+ + "39" + ""
+ + "19" + "14" + "25"
+ + "10" + "16" + "1" + "1" + ""
+ + "0" + "0" + "" + "" + "0" + "0" + ""
+ + "" +
+
+ "";
+
+
+ private String verySimpleXSLT = "" + " " + "" +
+
+ "" +
+
+ "" + ""
+ + "" + "" + "" + "";
+
+ private String callParseXmlDocument = "" + " " + "" +
+
+ "" +
+
+ "" +
+ "" + ""
+ + "" + "" + "" + "";
+
+ private String callParseXmlDocuments = "" + " " + "" +
+
+ "" +
+
+ "" +
+ "" + "" + ""
+ + "" + "" + "" + "" + "";
+
+
+}
diff --git a/source/java/org/alfresco/repo/template/ClassPathRepoTemplateLoader.java b/source/java/org/alfresco/repo/template/ClassPathRepoTemplateLoader.java
index b9d57e5245..766746e06a 100644
--- a/source/java/org/alfresco/repo/template/ClassPathRepoTemplateLoader.java
+++ b/source/java/org/alfresco/repo/template/ClassPathRepoTemplateLoader.java
@@ -26,13 +26,11 @@ import java.net.URL;
import java.net.URLConnection;
import org.alfresco.model.ContentModel;
-import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
-import org.alfresco.util.ApplicationContextHelper;
import freemarker.cache.TemplateLoader;
@@ -85,8 +83,14 @@ public class ClassPathRepoTemplateLoader implements TemplateLoader
}
else
{
- URL url = this.getClass().getClassLoader().getResource(name);
- return url == null ? null : new ClassPathTemplateSource(url, encoding);
+ //Fix a common problem: classpath resource paths should not start with "/"
+ if (name.startsWith("/"))
+ {
+ name = name.substring(1);
+ }
+ ClassLoader classLoader = getClass().getClassLoader();
+ URL url = classLoader.getResource(name);
+ return url == null ? null : new ClassPathTemplateSource(classLoader, name, encoding);
}
}
@@ -116,13 +120,20 @@ public class ClassPathRepoTemplateLoader implements TemplateLoader
/**
* Class used as a base for custom Template Source objects
*/
- abstract class BaseTemplateSource
+ abstract class BaseTemplateSource implements TemplateSource
{
public abstract Reader getReader(String encoding) throws IOException;
public abstract void close() throws IOException;
public abstract long lastModified();
+
+ public InputStream getResource(String name)
+ {
+ return getRelativeResource(name);
+ }
+
+ protected abstract InputStream getRelativeResource(String name);
}
@@ -135,10 +146,14 @@ public class ClassPathRepoTemplateLoader implements TemplateLoader
private URLConnection conn;
private InputStream inputStream;
private String encoding;
+ private ClassLoader classLoader;
+ private String resourceName;
- ClassPathTemplateSource(URL url, String encoding) throws IOException
+ ClassPathTemplateSource(ClassLoader classLoader, String name, String encoding) throws IOException
{
- this.url = url;
+ this.classLoader = classLoader;
+ this.resourceName = name;
+ this.url = classLoader.getResource(name);
this.conn = url.openConnection();
this.encoding = encoding;
}
@@ -198,6 +213,32 @@ public class ClassPathRepoTemplateLoader implements TemplateLoader
conn = null;
}
}
+
+ /* (non-Javadoc)
+ * @see org.alfresco.repo.template.ClassPathRepoTemplateLoader.BaseTemplateSource#getRelativeResource(java.lang.String)
+ */
+ @Override
+ protected InputStream getRelativeResource(String name)
+ {
+ String newResourceName = name;
+ if (!name.startsWith("/"))
+ {
+ int lastSlash = resourceName.lastIndexOf('/');
+ if (lastSlash != -1)
+ {
+ newResourceName = name.substring(0, lastSlash) + "/" + name;
+ }
+ }
+ URL url = classLoader.getResource(newResourceName);
+ try
+ {
+ return (url == null) ? null : url.openConnection().getInputStream();
+ }
+ catch (IOException e)
+ {
+ return null;
+ }
+ }
}
/**
@@ -263,5 +304,25 @@ public class ClassPathRepoTemplateLoader implements TemplateLoader
conn = null;
}
}
+
+ /* (non-Javadoc)
+ * @see org.alfresco.repo.template.ClassPathRepoTemplateLoader.BaseTemplateSource#getRelativeResource(java.lang.String)
+ */
+ @Override
+ protected InputStream getRelativeResource(String name)
+ {
+ InputStream stream = null;
+ NodeRef parentRef = nodeService.getPrimaryParent(nodeRef).getParentRef();
+ NodeRef child = nodeService.getChildByName(parentRef, ContentModel.ASSOC_CONTAINS, name);
+ if (child != null)
+ {
+ ContentReader contentReader = contentService.getReader(child, ContentModel.PROP_CONTENT);
+ if (contentReader.exists())
+ {
+ stream = contentReader.getContentInputStream();
+ }
+ }
+ return stream;
+ }
}
}
diff --git a/source/java/org/alfresco/repo/template/TemplateProcessorMethod.java b/source/java/org/alfresco/repo/template/TemplateProcessorMethod.java
new file mode 100644
index 0000000000..03dcf3e01e
--- /dev/null
+++ b/source/java/org/alfresco/repo/template/TemplateProcessorMethod.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2005-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+
+package org.alfresco.repo.template;
+
+import java.io.Serializable;
+
+/**
+ * @author Brian
+ *
+ */
+public interface TemplateProcessorMethod extends Serializable
+{
+ Object exec(final Object[] arguments) throws Exception;
+}
diff --git a/source/java/org/alfresco/repo/template/TemplateSource.java b/source/java/org/alfresco/repo/template/TemplateSource.java
new file mode 100644
index 0000000000..3b7baf0755
--- /dev/null
+++ b/source/java/org/alfresco/repo/template/TemplateSource.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2005-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+
+package org.alfresco.repo.template;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+public interface TemplateSource
+{
+ Reader getReader(String encoding) throws IOException;
+
+ void close() throws IOException;
+
+ long lastModified();
+
+ InputStream getResource(String name);
+}
diff --git a/source/java/org/alfresco/repo/template/XSLTProcessor.java b/source/java/org/alfresco/repo/template/XSLTProcessor.java
new file mode 100644
index 0000000000..e988073d36
--- /dev/null
+++ b/source/java/org/alfresco/repo/template/XSLTProcessor.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2005-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+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.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.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 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 xsltModel
+ * @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 scriptIds = addScripts(xsltModel, xslTemplate);
+ addParameters(xsltModel, xslTemplate);
+
+ final LinkedList errors = new LinkedList();
+ 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 addScripts(final XSLTemplateModel xsltModel, final Document xslTemplate)
+ {
+ final Map>> methods = new HashMap>>();
+ for (final Map.Entry 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>());
+ }
+ 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 excludePrefixes = new HashSet();
+ if (docEl.hasAttribute("exclude-result-prefixes"))
+ {
+ excludePrefixes.addAll(Arrays.asList(docEl.getAttribute("exclude-result-prefixes").split(" ")));
+ }
+ excludePrefixes.add(XALAN_NS_PREFIX);
+
+ final List result = new LinkedList();
+ 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 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 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()));
+ docEl.insertBefore(el, docEl.getFirstChild());
+ }
+ }
+ }
+
+ protected Source getXMLSource(final Map 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);
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/template/XSLTProcessorMethodInvoker.java b/source/java/org/alfresco/repo/template/XSLTProcessorMethodInvoker.java
new file mode 100644
index 0000000000..355d8b2bd4
--- /dev/null
+++ b/source/java/org/alfresco/repo/template/XSLTProcessorMethodInvoker.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2005-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+
+package org.alfresco.repo.template;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.xml.dtm.ref.DTMNodeProxy;
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Node;
+import org.w3c.dom.traversal.NodeFilter;
+import org.w3c.dom.traversal.NodeIterator;
+
+/**
+ * @author Brian
+ *
+ */
+public class XSLTProcessorMethodInvoker
+{
+ private final static Log log = LogFactory.getLog(XSLTProcessorMethodInvoker.class);
+
+ private final static Map PROCESSOR_METHODS = new TreeMap();
+
+ public XSLTProcessorMethodInvoker()
+ {
+ }
+
+ public static void addMethod(String name, TemplateProcessorMethod method)
+ {
+ PROCESSOR_METHODS.put(name, method);
+ }
+
+ public static void removeMethods(Collection methodNames)
+ {
+ for (String methodName : methodNames)
+ {
+ PROCESSOR_METHODS.remove(methodName);
+ }
+ }
+
+ private Object[] convertArguments(final Object[] arguments)
+ {
+ final List