mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
. 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
This commit is contained in:
@@ -31,6 +31,7 @@ import org.alfresco.model.ContentModel;
|
|||||||
import org.alfresco.repo.action.executer.TransformActionExecuter;
|
import org.alfresco.repo.action.executer.TransformActionExecuter;
|
||||||
import org.alfresco.repo.content.transform.magick.ImageMagickContentTransformer;
|
import org.alfresco.repo.content.transform.magick.ImageMagickContentTransformer;
|
||||||
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
||||||
|
import org.alfresco.repo.template.FreeMarkerProcessor;
|
||||||
import org.alfresco.repo.version.VersionModel;
|
import org.alfresco.repo.version.VersionModel;
|
||||||
import org.alfresco.service.ServiceRegistry;
|
import org.alfresco.service.ServiceRegistry;
|
||||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
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.NodeRef;
|
||||||
import org.alfresco.service.cmr.repository.NodeService;
|
import org.alfresco.service.cmr.repository.NodeService;
|
||||||
import org.alfresco.service.cmr.repository.TemplateImageResolver;
|
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.AccessStatus;
|
||||||
import org.alfresco.service.cmr.security.PermissionService;
|
import org.alfresco.service.cmr.security.PermissionService;
|
||||||
import org.alfresco.service.cmr.version.Version;
|
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.apache.commons.logging.LogFactory;
|
||||||
import org.mozilla.javascript.Scriptable;
|
import org.mozilla.javascript.Scriptable;
|
||||||
import org.mozilla.javascript.ScriptableObject;
|
import org.mozilla.javascript.ScriptableObject;
|
||||||
|
import org.mozilla.javascript.Wrapper;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -632,7 +635,7 @@ public class Node implements Serializable, Scopeable
|
|||||||
{
|
{
|
||||||
if (parent == null)
|
if (parent == null)
|
||||||
{
|
{
|
||||||
NodeRef parentRef = this.nodeService.getPrimaryParent(nodeRef).getParentRef();
|
NodeRef parentRef = getPrimaryParentAssoc().getParentRef();
|
||||||
// handle root node (no parent!)
|
// handle root node (no parent!)
|
||||||
if (parentRef != null)
|
if (parentRef != null)
|
||||||
{
|
{
|
||||||
@@ -905,7 +908,6 @@ public class Node implements Serializable, Scopeable
|
|||||||
this.nodeService.setProperties(this.nodeRef, props);
|
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.
|
* 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.
|
* Add an aspect to the Node.
|
||||||
*
|
*
|
||||||
* @param type Type name of the aspect to add
|
* @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!
|
* 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.
|
* @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;
|
boolean success = false;
|
||||||
|
|
||||||
@@ -1192,12 +1194,11 @@ public class Node implements Serializable, Scopeable
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Map<QName, Serializable> aspectProps = null;
|
Map<QName, Serializable> aspectProps = null;
|
||||||
if (properties instanceof ScriptableObject)
|
if (properties != null)
|
||||||
{
|
{
|
||||||
ScriptableObject props = (ScriptableObject)properties;
|
|
||||||
// we need to get all the keys to the properties provided
|
// we need to get all the keys to the properties provided
|
||||||
// and convert them to a Map of QName to Serializable objects
|
// and convert them to a Map of QName to Serializable objects
|
||||||
Object[] propIds = props.getIds();
|
Object[] propIds = properties.getIds();
|
||||||
aspectProps = new HashMap<QName, Serializable>(propIds.length);
|
aspectProps = new HashMap<QName, Serializable>(propIds.length);
|
||||||
for (int i=0; i<propIds.length; i++)
|
for (int i=0; i<propIds.length; i++)
|
||||||
{
|
{
|
||||||
@@ -1208,7 +1209,7 @@ public class Node implements Serializable, Scopeable
|
|||||||
if (propId instanceof String)
|
if (propId instanceof String)
|
||||||
{
|
{
|
||||||
// get the value out for the specified key - make sure it is Serializable
|
// get the value out for the specified key - make sure it is Serializable
|
||||||
Object value = props.get((String)propId, props);
|
Object value = properties.get((String)propId, properties);
|
||||||
value = getValueConverter().convertValueForRepo((Serializable)value);
|
value = getValueConverter().convertValueForRepo((Serializable)value);
|
||||||
aspectProps.put(createQName((String)propId), (Serializable)value);
|
aspectProps.put(createQName((String)propId), (Serializable)value);
|
||||||
}
|
}
|
||||||
@@ -1524,6 +1525,113 @@ public class Node implements Serializable, Scopeable
|
|||||||
return transformNode(transformer, mimetype, destination);
|
return transformNode(transformer, mimetype, destination);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a FreeMarker Template against the current node.
|
||||||
|
*
|
||||||
|
* @param template Node of the template to execute
|
||||||
|
*
|
||||||
|
* @return output of the template execution
|
||||||
|
*/
|
||||||
|
public String processTemplate(Node template)
|
||||||
|
{
|
||||||
|
return processTemplate(template.getContent(), null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a FreeMarker Template against the current node.
|
||||||
|
*
|
||||||
|
* @param template Node of the template to execute
|
||||||
|
* @param args Scriptable object (generally an associative array) containing the
|
||||||
|
* name/value pairs of arguments to be passed to the template
|
||||||
|
*
|
||||||
|
* @return output of the template execution
|
||||||
|
*/
|
||||||
|
public String processTemplate(Node template, ScriptableObject args)
|
||||||
|
{
|
||||||
|
return processTemplate(template.getContent(), null, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a FreeMarker Template against the current node.
|
||||||
|
*
|
||||||
|
* @param template The template to execute
|
||||||
|
*
|
||||||
|
* @return output of the template execution
|
||||||
|
*/
|
||||||
|
public String processTemplate(String template)
|
||||||
|
{
|
||||||
|
return processTemplate(template, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a FreeMarker Template against the current node.
|
||||||
|
*
|
||||||
|
* @param template The template to execute
|
||||||
|
* @param args Scriptable object (generally an associative array) containing the
|
||||||
|
* name/value pairs of arguments to be passed to the template
|
||||||
|
*
|
||||||
|
* @return output of the template execution
|
||||||
|
*/
|
||||||
|
public String processTemplate(String template, ScriptableObject args)
|
||||||
|
{
|
||||||
|
return processTemplate(template, null, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String processTemplate(String template, NodeRef templateRef, ScriptableObject args)
|
||||||
|
{
|
||||||
|
// build default model for the template processing
|
||||||
|
Map<String, Object> 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<String, String> templateArgs = new HashMap<String, String>(propIds.length);
|
||||||
|
for (int i=0; i<propIds.length; i++)
|
||||||
|
{
|
||||||
|
// work on each key in turn
|
||||||
|
Object propId = propIds[i];
|
||||||
|
|
||||||
|
// we are only interested in keys that are formed of Strings i.e. QName.toString()
|
||||||
|
if (propId instanceof String)
|
||||||
|
{
|
||||||
|
// get the value out for the specified key - make sure it is Serializable
|
||||||
|
Object value = args.get((String)propId, args);
|
||||||
|
value = getValueConverter().convertValueForRepo((Serializable)value);
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
templateArgs.put((String)propId, value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add the args to the model as the 'args' root object
|
||||||
|
model.put("args", templateArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute template!
|
||||||
|
// TODO: check that script modified nodes are reflected...
|
||||||
|
return this.services.getTemplateService().processTemplateString(
|
||||||
|
null, template, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
// Helper methods
|
// Helper methods
|
||||||
|
@@ -18,10 +18,17 @@ package org.alfresco.repo.template;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.alfresco.service.ServiceRegistry;
|
||||||
import org.alfresco.service.cmr.repository.ContentService;
|
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.NodeService;
|
||||||
import org.alfresco.service.cmr.repository.TemplateException;
|
import org.alfresco.service.cmr.repository.TemplateException;
|
||||||
|
import org.alfresco.service.cmr.repository.TemplateImageResolver;
|
||||||
|
import org.alfresco.service.cmr.repository.TemplateNode;
|
||||||
import org.alfresco.service.cmr.repository.TemplateProcessor;
|
import org.alfresco.service.cmr.repository.TemplateProcessor;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
@@ -32,7 +39,17 @@ import freemarker.template.Template;
|
|||||||
import freemarker.template.TemplateExceptionHandler;
|
import freemarker.template.TemplateExceptionHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FreeMarker implementation the template processor interface
|
* FreeMarker implementation of the template processor interface.
|
||||||
|
* <p>
|
||||||
|
* Service to process FreeMarker template files loaded from various sources including
|
||||||
|
* the classpath, repository and directly from a String.
|
||||||
|
* <p>
|
||||||
|
* 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
|
* @author Kevin Roast
|
||||||
*/
|
*/
|
||||||
@@ -47,9 +64,6 @@ public class FreeMarkerProcessor implements TemplateProcessor
|
|||||||
/** Pseudo path to String based template */
|
/** Pseudo path to String based template */
|
||||||
private static final String PATH = "string://fixed";
|
private static final String PATH = "string://fixed";
|
||||||
|
|
||||||
/** FreeMarker processor configuration */
|
|
||||||
private Configuration config = null;
|
|
||||||
|
|
||||||
/** The permission-safe node service */
|
/** The permission-safe node service */
|
||||||
private NodeService nodeService;
|
private NodeService nodeService;
|
||||||
|
|
||||||
@@ -82,13 +96,11 @@ public class FreeMarkerProcessor implements TemplateProcessor
|
|||||||
* @return FreeMarker configuration
|
* @return FreeMarker configuration
|
||||||
*/
|
*/
|
||||||
private Configuration getConfig()
|
private Configuration getConfig()
|
||||||
{
|
|
||||||
if (this.config == null)
|
|
||||||
{
|
{
|
||||||
Configuration config = new Configuration();
|
Configuration config = new Configuration();
|
||||||
|
|
||||||
// setup template cache
|
// setup template cache
|
||||||
config.setCacheStorage(new MruCacheStorage(20, 0));
|
config.setCacheStorage(new MruCacheStorage(2, 0));
|
||||||
|
|
||||||
// use our custom loader to find templates on the ClassPath
|
// use our custom loader to find templates on the ClassPath
|
||||||
config.setTemplateLoader(new ClassPathRepoTemplateLoader(nodeService, contentService));
|
config.setTemplateLoader(new ClassPathRepoTemplateLoader(nodeService, contentService));
|
||||||
@@ -99,9 +111,7 @@ public class FreeMarkerProcessor implements TemplateProcessor
|
|||||||
// rethrow any exception so we can deal with them
|
// rethrow any exception so we can deal with them
|
||||||
config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
|
config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
|
||||||
|
|
||||||
this.config = config;
|
return config;
|
||||||
}
|
|
||||||
return this.config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,6 +126,9 @@ public class FreeMarkerProcessor implements TemplateProcessor
|
|||||||
{
|
{
|
||||||
Configuration config = new Configuration();
|
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
|
// use our custom loader to load a template directly from a String
|
||||||
StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
|
StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
|
||||||
stringTemplateLoader.putTemplate(path, template);
|
stringTemplateLoader.putTemplate(path, template);
|
||||||
@@ -243,4 +256,56 @@ public class FreeMarkerProcessor implements TemplateProcessor
|
|||||||
throw new TemplateException(MSG_ERROR_TEMPLATE_IO, new Object[] {template}, ioerr);
|
throw new TemplateException(MSG_ERROR_TEMPLATE_IO, new Object[] {template}, ioerr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the default data-model available to templates as global objects.
|
||||||
|
* <p>
|
||||||
|
* 'companyhome' - the Company Home node<br>
|
||||||
|
* 'userhome' - the current user home space node<br>
|
||||||
|
* 'person' - the node representing the current user Person<br>
|
||||||
|
* 'template' - the node representing the template itself (may not be available)
|
||||||
|
* <p>
|
||||||
|
* 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<String, Object> buildDefaultModel(
|
||||||
|
ServiceRegistry services,
|
||||||
|
NodeRef person, NodeRef companyHome, NodeRef userHome, NodeRef template,
|
||||||
|
TemplateImageResolver imageResolver)
|
||||||
|
{
|
||||||
|
Map<String, Object> model = new HashMap<String, Object>(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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user