From 8473e7fc4f8475f6d12a51db436f44c33f53f221 Mon Sep 17 00:00:00 2001 From: Kevin Roast Date: Tue, 22 Aug 2006 08:24:59 +0000 Subject: [PATCH] . Added ability to execute FreeMarker templates from within Alfresco JavaScript git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@3561 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../java/org/alfresco/repo/jscript/Node.java | 126 ++++++++++++++++-- .../repo/template/FreeMarkerProcessor.java | 111 +++++++++++---- 2 files changed, 205 insertions(+), 32 deletions(-) diff --git a/source/java/org/alfresco/repo/jscript/Node.java b/source/java/org/alfresco/repo/jscript/Node.java index e73c1e19fa..581d866fed 100644 --- a/source/java/org/alfresco/repo/jscript/Node.java +++ b/source/java/org/alfresco/repo/jscript/Node.java @@ -31,6 +31,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.action.executer.TransformActionExecuter; import org.alfresco.repo.content.transform.magick.ImageMagickContentTransformer; import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.template.FreeMarkerProcessor; import org.alfresco.repo.version.VersionModel; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DictionaryService; @@ -49,6 +50,7 @@ import org.alfresco.service.cmr.repository.NoTransformerException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.version.Version; @@ -60,6 +62,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.Wrapper; import org.springframework.util.StringUtils; /** @@ -632,7 +635,7 @@ public class Node implements Serializable, Scopeable { if (parent == null) { - NodeRef parentRef = this.nodeService.getPrimaryParent(nodeRef).getParentRef(); + NodeRef parentRef = getPrimaryParentAssoc().getParentRef(); // handle root node (no parent!) if (parentRef != null) { @@ -905,7 +908,6 @@ public class Node implements Serializable, Scopeable this.nodeService.setProperties(this.nodeRef, props); } - /** * Re-sets the type of the node. Can be called in order specialise a node to a sub-type. * @@ -1178,12 +1180,12 @@ public class Node implements Serializable, Scopeable * Add an aspect to the Node. * * @param type Type name of the aspect to add - * @param props Object (generally an assocative array) providing the named properties + * @param props ScriptableObject (generally an assocative array) providing the named properties * for the aspect - any mandatory properties for the aspect must be provided! - * + * * @return true if the aspect was added successfully, false if an error occured. */ - public boolean addAspect(String type, Object properties) + public boolean addAspect(String type, ScriptableObject properties) { boolean success = false; @@ -1192,12 +1194,11 @@ public class Node implements Serializable, Scopeable try { Map aspectProps = null; - if (properties instanceof ScriptableObject) + if (properties != null) { - ScriptableObject props = (ScriptableObject)properties; // we need to get all the keys to the properties provided // and convert them to a Map of QName to Serializable objects - Object[] propIds = props.getIds(); + Object[] propIds = properties.getIds(); aspectProps = new HashMap(propIds.length); for (int i=0; i model = FreeMarkerProcessor.buildDefaultModel(services, + ((Node)((Wrapper)scope.get("person", scope)).unwrap()).getNodeRef(), + ((Node)((Wrapper)scope.get("companyhome", scope)).unwrap()).getNodeRef(), + ((Node)((Wrapper)scope.get("userhome", scope)).unwrap()).getNodeRef(), + templateRef, + this.imageResolver); + + // add the current node as either the document/space as appropriate + if (this.isDocument()) + { + model.put("document", new TemplateNode(this.nodeRef, this.services, this.imageResolver)); + model.put("space", new TemplateNode(getPrimaryParentAssoc().getParentRef(), this.services, this.imageResolver)); + } + else + { + model.put("space", new TemplateNode(this.nodeRef, this.services, this.imageResolver)); + } + + // add the supplied args to the 'args' root object + if (args != null) + { + // we need to get all the keys to the properties provided + // and convert them to a Map of QName to Serializable objects + Object[] propIds = args.getIds(); + Map templateArgs = new HashMap(propIds.length); + for (int i=0; i + * Service to process FreeMarker template files loaded from various sources including + * the classpath, repository and directly from a String. + *

+ * The template is processed against a data model generally consisting of a map of + * named objects. FreeMarker can natively handle any POJO objects using standard bean + * notation syntax. It has support for walking List objects. A 'standard' data model + * helper is provided to help generate an object model containing well known objects + * such as the Company Home, User Home and current User nodes. It also provides helpful + * util classes to process Date objects and repository specific custom methods. * * @author Kevin Roast */ @@ -47,9 +64,6 @@ public class FreeMarkerProcessor implements TemplateProcessor /** Pseudo path to String based template */ private static final String PATH = "string://fixed"; - /** FreeMarker processor configuration */ - private Configuration config = null; - /** The permission-safe node service */ private NodeService nodeService; @@ -83,25 +97,21 @@ public class FreeMarkerProcessor implements TemplateProcessor */ private Configuration getConfig() { - if (this.config == null) - { - Configuration config = new Configuration(); - - // setup template cache - config.setCacheStorage(new MruCacheStorage(20, 0)); - - // use our custom loader to find templates on the ClassPath - config.setTemplateLoader(new ClassPathRepoTemplateLoader(nodeService, contentService)); - - // use our custom object wrapper that can deal with QNameMap objects directly - config.setObjectWrapper(new QNameAwareObjectWrapper()); - - // rethrow any exception so we can deal with them - config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); - - this.config = config; - } - return this.config; + Configuration config = new Configuration(); + + // setup template cache + config.setCacheStorage(new MruCacheStorage(2, 0)); + + // use our custom loader to find templates on the ClassPath + config.setTemplateLoader(new ClassPathRepoTemplateLoader(nodeService, contentService)); + + // use our custom object wrapper that can deal with QNameMap objects directly + config.setObjectWrapper(new QNameAwareObjectWrapper()); + + // rethrow any exception so we can deal with them + config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + + return config; } /** @@ -116,6 +126,9 @@ public class FreeMarkerProcessor implements TemplateProcessor { Configuration config = new Configuration(); + // setup template cache + config.setCacheStorage(new MruCacheStorage(2, 0)); + // use our custom loader to load a template directly from a String StringTemplateLoader stringTemplateLoader = new StringTemplateLoader(); stringTemplateLoader.putTemplate(path, template); @@ -243,4 +256,56 @@ public class FreeMarkerProcessor implements TemplateProcessor throw new TemplateException(MSG_ERROR_TEMPLATE_IO, new Object[] {template}, ioerr); } } + + /** + * Create the default data-model available to templates as global objects. + *

+ * 'companyhome' - the Company Home node
+ * 'userhome' - the current user home space node
+ * 'person' - the node representing the current user Person
+ * 'template' - the node representing the template itself (may not be available) + *

+ * Also adds various helper util objects and methods. + * + * @param services ServiceRegistry + * @param person The current user Person Node + * @param companyHome The CompanyHome ref + * @param userHome The User home space ref + * @param template Optional ref to the template itself + * @param resolver Image resolver to resolve icon images etc. + * + * @return A Map of Templatable Node objects and util objects. + */ + public static Map buildDefaultModel( + ServiceRegistry services, + NodeRef person, NodeRef companyHome, NodeRef userHome, NodeRef template, + TemplateImageResolver imageResolver) + { + Map model = new HashMap(16, 1.0f); + + // supply the Company Home space as "companyhome" + model.put("companyhome", new TemplateNode(companyHome, services, imageResolver)); + + // supply the users Home Space as "userhome" + model.put("userhome", new TemplateNode(userHome, services, imageResolver)); + + // supply the current user Node as "person" + model.put("person", new TemplateNode(person, services, imageResolver)); + + // add the template itself as "template" if it comes from content on a node + if (template != null) + { + model.put("template", new TemplateNode(template, services, imageResolver)); + } + + // current date/time is useful to have and isn't supplied by FreeMarker by default + model.put("date", new Date()); + + // add custom method objects + model.put("hasAspect", new HasAspectMethod()); + model.put("message", new I18NMessageMethod()); + model.put("dateCompare", new DateCompareMethod()); + + return model; + } }