diff --git a/source/java/org/alfresco/web/templating/AbstractFormDataRenderer.java b/source/java/org/alfresco/web/templating/AbstractFormDataRenderer.java new file mode 100644 index 0000000000..146e7641ef --- /dev/null +++ b/source/java/org/alfresco/web/templating/AbstractFormDataRenderer.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.web.templating; + +import javax.faces.context.FacesContext; +import org.alfresco.model.WCMModel; +import org.alfresco.repo.avm.AVMRemote; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.web.bean.wcm.AVMConstants; +import org.alfresco.web.templating.extension.ExtensionFunctions; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.jsf.FacesContextUtils; + +/** + * Provides helper functions for form data renderers. + */ +// XXXarielb this class is probably temporary - but useful to +// reduce redundant code until i find a better place to put this stuff +public abstract class AbstractFormDataRenderer + implements FormDataRenderer +{ + protected static final String ALFRESCO_NS = "http://www.alfresco.org/alfresco"; + protected static final String ALFRESCO_NS_PREFIX = "alfresco"; + + private final NodeRef nodeRef; + protected final NodeService nodeService; + protected final ContentService contentService; + + protected AbstractFormDataRenderer(final NodeRef nodeRef, + final NodeService nodeService, + final ContentService contentService) + { + this.nodeRef = nodeRef; + this.nodeService = nodeService; + this.contentService = contentService; + } + + public NodeRef getNodeRef() + { + return this.nodeRef; + } + + public String getFileExtension() + { + return (String) + this.nodeService.getProperty(this.nodeRef, + WCMModel.PROP_FORM_TRANSFORMER_DERIVED_FILE_EXTENSION); + } + + protected static AVMRemote getAVMRemote() + { + final FacesContext fc = + FacesContext.getCurrentInstance(); + final WebApplicationContext wac = + FacesContextUtils.getRequiredWebApplicationContext(fc); + return (AVMRemote)wac.getBean("avmRemote"); + } + + protected static ExtensionFunctions getExtensionFunctions() + { + return new ExtensionFunctions(AbstractFormDataRenderer.getAVMRemote()); + } + + protected static String toAVMPath(String parentAVMPath, String path) + { + if (path != null && path.length() != 0 && path.charAt(0) == '/') + { + parentAVMPath = parentAVMPath.substring(0, + parentAVMPath.indexOf(':') + + ('/' + AVMConstants.DIR_APPBASE + + '/' + AVMConstants.DIR_WEBAPPS).length() + 1); + } + return parentAVMPath + (parentAVMPath.endsWith("/") ? path : '/' + path); + } + +} \ No newline at end of file diff --git a/source/java/org/alfresco/web/templating/TemplateOutputMethod.java b/source/java/org/alfresco/web/templating/FormDataRenderer.java similarity index 92% rename from source/java/org/alfresco/web/templating/TemplateOutputMethod.java rename to source/java/org/alfresco/web/templating/FormDataRenderer.java index 4f60651cd8..337b7cb236 100644 --- a/source/java/org/alfresco/web/templating/TemplateOutputMethod.java +++ b/source/java/org/alfresco/web/templating/FormDataRenderer.java @@ -23,9 +23,11 @@ import java.util.Map; import org.w3c.dom.Document; /** - * Serializes the xml data to a writer. + * Serializes the xml instance data collected by a form to a writer. + * + * @author Ariel Backenroth */ -public interface TemplateOutputMethod +public interface FormDataRenderer extends Serializable { /** the noderef associated with this output method */ diff --git a/source/java/org/alfresco/web/templating/OutputUtil.java b/source/java/org/alfresco/web/templating/OutputUtil.java index 53ebf0a6aa..7c79f918de 100644 --- a/source/java/org/alfresco/web/templating/OutputUtil.java +++ b/source/java/org/alfresco/web/templating/OutputUtil.java @@ -40,8 +40,10 @@ import org.w3c.dom.Document; /** * temporary home of generate and regenerate functionality until i figure - * out a more general way of triggering generate in TemplateOutputMethod + * out a more general way of triggering generate in FormDataRenderer * every time the xml file is saved. + * + * @author Ariel Backenroth */ public class OutputUtil { @@ -63,7 +65,7 @@ public class OutputUtil { try { - for (TemplateOutputMethod tom : tt.getOutputMethods()) + for (FormDataRenderer tom : tt.getFormDataRenderers()) { // get the node ref of the node that will contain the content final String generatedFileName = stripExtension(fileName) + "." + tom.getFileExtension(); @@ -132,7 +134,7 @@ public class OutputUtil final String avmStore = avmPath.substring(0, avmPath.indexOf(":/")); final String sandBoxUrl = AVMConstants.buildAVMStoreUrl(avmStore); final String parentPath = AVMNodeConverter.SplitBase(avmPath)[0]; - for (TemplateOutputMethod tom : tt.getOutputMethods()) + for (FormDataRenderer tom : tt.getFormDataRenderers()) { final String generatedFileName = stripExtension(fileName) + "." + tom.getFileExtension(); diff --git a/source/java/org/alfresco/web/templating/TemplateType.java b/source/java/org/alfresco/web/templating/TemplateType.java index 9a8f9b1e20..3b8b2b9637 100644 --- a/source/java/org/alfresco/web/templating/TemplateType.java +++ b/source/java/org/alfresco/web/templating/TemplateType.java @@ -53,10 +53,10 @@ public interface TemplateType /** * adds an output method to this template type. */ - public void addOutputMethod(TemplateOutputMethod output); + public void addFormDataRenderer(FormDataRenderer output); /** * Provides the set of output methods for this template. */ - public List getOutputMethods(); + public List getFormDataRenderers(); } diff --git a/source/java/org/alfresco/web/templating/TemplatingService.java b/source/java/org/alfresco/web/templating/TemplatingService.java index 4bc11b7049..f7e161325c 100644 --- a/source/java/org/alfresco/web/templating/TemplatingService.java +++ b/source/java/org/alfresco/web/templating/TemplatingService.java @@ -51,6 +51,8 @@ import org.alfresco.web.bean.repository.Repository; /** * Provides management of template types. + * + * @author Ariel Backenroth */ public final class TemplatingService implements Serializable { @@ -213,11 +215,11 @@ public final class TemplatingService implements Serializable WCMModel.PROP_FORM_TRANSFORMER_TYPE)); final Constructor c = templateOutputMethodType.getConstructor(NodeRef.class, NodeService.class, ContentService.class); - final TemplateOutputMethod tom = (TemplateOutputMethod) + final FormDataRenderer tom = (FormDataRenderer) c.newInstance(tomNodeRef, this.nodeService, this.contentService); LOGGER.debug("loaded template output method type " + tom.getClass().getName() + " for extension " + tom.getFileExtension() + ", " + tomNodeRef); - tt.addOutputMethod(tom); + tt.addFormDataRenderer(tom); } catch (Exception e) { diff --git a/source/java/org/alfresco/web/templating/extension/ExtensionFunctions.java b/source/java/org/alfresco/web/templating/extension/ExtensionFunctions.java index a7626b8d85..7f0d555833 100644 --- a/source/java/org/alfresco/web/templating/extension/ExtensionFunctions.java +++ b/source/java/org/alfresco/web/templating/extension/ExtensionFunctions.java @@ -31,6 +31,13 @@ import java.io.*; import java.util.Map; import java.util.HashMap; +/** + * Common implementation of functions called in the context of FormDataRenderers. + * This uses AVMRemote rather than AVMService so that in can be used in the context + * of both the alfresco webapp and the virtualization server. + * + * @author Ariel Backenroth + */ public class ExtensionFunctions { private static final Log LOGGER = LogFactory.getLog(ExtensionFunctions.class); @@ -44,6 +51,12 @@ public class ExtensionFunctions this.avmRemote = avmRemote; } + /** + * Loads and parses an xml document at the specified path using avm remote. + * + * @param avmPath a path within the avm repository. + * @return the parsed document. + */ public Document getXMLDocument(final String avmPath) throws IOException, SAXException @@ -64,12 +77,20 @@ public class ExtensionFunctions } } - public Map getXMLDocuments(final String templateTypeName, final String avmPath) + /** + * Loads and parses all xml documents at the specified path generated by the + * specified form using avm remote. + * + * @param formName a form name + * @param avmPath a path within the avm repository. + * @return the parsed document. + */ + public Map getXMLDocuments(final String formName, final String avmPath) throws IOException, SAXException { - final Map entries = - this.avmRemote.getDirectoryListing(-1, avmPath); + final Map entries = + this.avmRemote.getDirectoryListing(-1, avmPath); final DocumentBuilder db = this.getDocumentBuilder(); final Map result = new HashMap(); for (Map.Entry entry : entries.entrySet()) @@ -84,7 +105,7 @@ public class ExtensionFunctions WCMModel.PROP_FORM_DERIVED_FROM_NAME); if (pv == null || pv.getStringValue() == null || - !((String)pv.getStringValue()).equals(templateTypeName)) + !((String)pv.getStringValue()).equals(formName)) { // it's not generated by the same template type continue; diff --git a/source/java/org/alfresco/web/templating/xforms/FreeMarkerOutputMethod.java b/source/java/org/alfresco/web/templating/xforms/FreeMarkerOutputMethod.java index ad39743d16..ee85b33906 100644 --- a/source/java/org/alfresco/web/templating/xforms/FreeMarkerOutputMethod.java +++ b/source/java/org/alfresco/web/templating/xforms/FreeMarkerOutputMethod.java @@ -17,46 +17,51 @@ package org.alfresco.web.templating.xforms; import freemarker.ext.dom.NodeModel; -import freemarker.template.Configuration; -import freemarker.template.Template; -import freemarker.template.TemplateException; -import freemarker.template.TemplateExceptionHandler; +import freemarker.template.*; import java.io.*; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; -import org.alfresco.model.WCMModel; 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.web.templating.*; +import org.alfresco.web.templating.extension.ExtensionFunctions; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.chiba.xml.util.DOMUtil; import org.w3c.dom.Document; +import org.w3c.dom.Element; import org.w3c.dom.Node; +/** + * Implementation of a form data renderer for processing xml instance data + * using a freemarker template. + * + * @author Ariel Backenroth + */ public class FreeMarkerOutputMethod - implements TemplateOutputMethod + extends AbstractFormDataRenderer { - private final NodeRef nodeRef; - private final NodeService nodeService; - private final ContentService contentService; + private static final Log LOGGER = LogFactory.getLog(FreeMarkerOutputMethod.class); public FreeMarkerOutputMethod(final NodeRef nodeRef, final NodeService nodeService, final ContentService contentService) { - this.nodeRef = nodeRef; - this.nodeService = nodeService; - this.contentService = contentService; + super(nodeRef, nodeService, contentService); } - public NodeRef getNodeRef() - { - return this.nodeRef; - } - + /** + * Generates the rendition using the configured freemarker template. This + * provides a root map to the freemarker template which places the xml document, and + * a variable named alfresco at the root. the alfresco variable contains a hash of + * all parameters and all extension functions. + */ public void generate(final Document xmlContent, final TemplateType tt, final Map parameters, @@ -65,22 +70,105 @@ public class FreeMarkerOutputMethod TemplateException { final ContentReader contentReader = - this.contentService.getReader(nodeRef, ContentModel.TYPE_CONTENT); + this.contentService.getReader(this.getNodeRef(), ContentModel.TYPE_CONTENT); final Reader reader = new InputStreamReader(contentReader.getContentInputStream()); final Configuration cfg = new Configuration(); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); final Template t = new Template("freemarker_template", reader, cfg); - final Map root = new HashMap(); - root.put("doc", NodeModel.wrap(xmlContent)); - t.process(root, out); + + // wrap the xml instance in a model + final TemplateHashModel instanceDataModel = NodeModel.wrap(xmlContent); + + // build models for each of the extension functions + final TemplateModel getXMLDocumentModel = new TemplateMethodModel() + { + public Object exec(final List args) + throws TemplateModelException + { + try + { + final ExtensionFunctions ef = FreeMarkerOutputMethod.getExtensionFunctions(); + final String path = FreeMarkerOutputMethod.toAVMPath(parameters.get("parent_path"), + (String)args.get(0)); + return ef.getXMLDocument(path); + } + catch (Exception e) + { + throw new TemplateModelException(e); + } + } + }; + final TemplateModel getXMLDocumentsModel = new TemplateMethodModel() + { + public Object exec(final List args) + throws TemplateModelException + { + try + { + final ExtensionFunctions ef = FreeMarkerOutputMethod.getExtensionFunctions(); + final String path = FreeMarkerOutputMethod.toAVMPath(parameters.get("parent_path"), + args.size() == 1 ? "" : (String)args.get(1)); + final Map resultMap = ef.getXMLDocuments((String)args.get(0), path); + LOGGER.debug("received " + resultMap.size() + " documents in " + path); + final List result = new ArrayList(resultMap.size()); + for (Map.Entry e : resultMap.entrySet()) + { + final Document d = e.getValue(); + final Element documentEl = d.getDocumentElement(); + documentEl.setAttribute("xmlns:" + ALFRESCO_NS_PREFIX, ALFRESCO_NS); + documentEl.setAttributeNS(ALFRESCO_NS, + ALFRESCO_NS_PREFIX + ":file-name", + e.getKey()); + result.add(NodeModel.wrap(d)); + } + return result; + } + catch (Exception e) + { + throw new TemplateModelException(e); + } + } + }; + + // build a wrapper for the parameters. this also wraps the extension functions + // so they appear in the namespace alfresco. + final TemplateHashModel parameterModel = new SimpleHash(parameters) + { + public TemplateModel get(final String key) + throws TemplateModelException + { + if ("getXMLDocument".equals(key)) + { + return getXMLDocumentModel; + } + if ("getXMLDocuments".equals(key)) + { + return getXMLDocumentsModel; + } + return super.get(key); + } + }; + + // build the root model. anything not in the falsey alfresco namespace will be + // retrieved from the xml file in order to make it behave as close as possible to + // the xsl environment + final TemplateHashModel rootModel = new TemplateHashModel() + { + public TemplateModel get(final String key) + throws TemplateModelException + { + return ALFRESCO_NS_PREFIX.equals(key) ? parameterModel : instanceDataModel.get(key); + } + + public boolean isEmpty() + { + return false; + } + }; + + // process the form + t.process(rootModel, out); out.flush(); } - - public String getFileExtension() - { - return (String) - this.nodeService.getProperty(this.nodeRef, - WCMModel.PROP_FORM_TRANSFORMER_DERIVED_FILE_EXTENSION); - } } diff --git a/source/java/org/alfresco/web/templating/xforms/TemplateTypeImpl.java b/source/java/org/alfresco/web/templating/xforms/TemplateTypeImpl.java index 86caaa24a8..0aff7ef010 100644 --- a/source/java/org/alfresco/web/templating/xforms/TemplateTypeImpl.java +++ b/source/java/org/alfresco/web/templating/xforms/TemplateTypeImpl.java @@ -37,8 +37,8 @@ public class TemplateTypeImpl private final NodeRef schemaNodeRef; private final String name; private final String rootTagName; - private final LinkedList outputMethods = - new LinkedList(); + private final LinkedList formDataRenderers = + new LinkedList(); private final static LinkedList INPUT_METHODS = new LinkedList(); @@ -94,14 +94,14 @@ public class TemplateTypeImpl return INPUT_METHODS; } - public void addOutputMethod(TemplateOutputMethod output) + public void addFormDataRenderer(final FormDataRenderer output) { - this.outputMethods.add(output); + this.formDataRenderers.add(output); } - public List getOutputMethods() + public List getFormDataRenderers() { - return this.outputMethods; + return this.formDataRenderers; } public int hashCode() diff --git a/source/java/org/alfresco/web/templating/xforms/XSLTOutputMethod.java b/source/java/org/alfresco/web/templating/xforms/XSLTOutputMethod.java index 892e77da0e..91af804263 100644 --- a/source/java/org/alfresco/web/templating/xforms/XSLTOutputMethod.java +++ b/source/java/org/alfresco/web/templating/xforms/XSLTOutputMethod.java @@ -34,11 +34,9 @@ import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.alfresco.model.WCMModel; -import org.alfresco.repo.avm.AVMRemote; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.web.bean.wcm.AVMConstants; import org.alfresco.web.templating.*; import org.alfresco.web.templating.extension.ExtensionFunctions; import org.apache.commons.logging.Log; @@ -52,60 +50,23 @@ import org.w3c.dom.traversal.NodeIterator; import org.xml.sax.SAXException; public class XSLTOutputMethod - implements TemplateOutputMethod + extends AbstractFormDataRenderer { - //XXXarielb these should go into a more common location - private static final String ALFRESCO_NS = "http://www.alfresco.org/alfresco"; - private static final String ALFRESCO_NS_PREFIX = "alfresco"; private static final Log LOGGER = LogFactory.getLog(XSLTOutputMethod.class); - - private final NodeRef nodeRef; - private final NodeService nodeService; public XSLTOutputMethod(final NodeRef nodeRef, final NodeService nodeService, final ContentService contentService) { - this.nodeRef = nodeRef; - this.nodeService = nodeService; - } - - public NodeRef getNodeRef() - { - return this.nodeRef; - } - - //XXXarielb this is totally dirty - need to figure a better way to do this - private static AVMRemote getAVMRemote() - { - final javax.faces.context.FacesContext fc = - javax.faces.context.FacesContext.getCurrentInstance(); - final org.springframework.web.context.WebApplicationContext wac = - org.springframework.web.jsf.FacesContextUtils.getRequiredWebApplicationContext(fc); - return (AVMRemote)wac.getBean("avmRemote"); - } - - private static ExtensionFunctions getExtensionFunctions() - { - return new ExtensionFunctions(XSLTOutputMethod.getAVMRemote()); + super(nodeRef, nodeService, contentService); } private static String toAVMPath(final ExpressionContext ec, String path) throws TransformerException { final XObject o = ec.getVariableOrParam(new QName(ALFRESCO_NS, ALFRESCO_NS_PREFIX, "parent_path")); - if (o == null) - return null; - String avmPath = o.toString(); - if (path != null && path.length() != 0 && path.charAt(0) == '/') - { - avmPath = avmPath.substring(0, - avmPath.indexOf(':') + - ('/' + AVMConstants.DIR_APPBASE + - '/' + AVMConstants.DIR_WEBAPPS).length() + 1); - } - return avmPath + (avmPath.endsWith("/") ? path : '/' + path); + return o == null ? null : XSLTOutputMethod.toAVMPath(o.toString(), path); } public static Document getXMLDocument(final ExpressionContext ec, final String path) @@ -263,7 +224,7 @@ public class XSLTOutputMethod final String sandBoxUrl = (String)parameters.get("avm_store_url"); final TransformerFactory tf = TransformerFactory.newInstance(); final TemplatingService ts = TemplatingService.getInstance(); - final Document xslDocument = ts.parseXML(this.nodeRef); + final Document xslDocument = ts.parseXML(this.getNodeRef()); this.addScript(xslDocument); this.addParameters(parameters, xslDocument); @@ -310,11 +271,4 @@ public class XSLTOutputMethod throw e; } } - - public String getFileExtension() - { - return (String) - this.nodeService.getProperty(this.nodeRef, - WCMModel.PROP_FORM_TRANSFORMER_DERIVED_FILE_EXTENSION); - } } diff --git a/source/test-resources/xforms/unit-tests/output-method-callout-test/output-method-callout.ftl b/source/test-resources/xforms/unit-tests/output-method-callout-test/output-method-callout.ftl new file mode 100644 index 0000000000..f8f769cf27 --- /dev/null +++ b/source/test-resources/xforms/unit-tests/output-method-callout-test/output-method-callout.ftl @@ -0,0 +1,35 @@ +<#ftl ns_prefixes={"alfresco", "http://www.alfresco.org/alfresco"}> + + + + Simple Test + + +
Generated by output-method-callout.ftl
+
My value accessed using <#noparse>${simple.string}:
+ ${simple.string} +
My value accessed using <#noparse>$alfresco.getXMLDocument(${alfresco.derived_from_file_name}):
+ ${alfresco.getXMLDocument(alfresco.derived_from_file_name).simple.string} +
Values from xml files generated by in ${alfresco.parent_path}:
+
    + <#list alfresco.getXMLDocuments('output-method-callout') as d> +
  • ${d["/simple/@alfresco:file-name"]} = ${d.simple.string}
  • + +
+ +