diff --git a/config/alfresco/script-services-context.xml b/config/alfresco/script-services-context.xml
index 464564d7de..fbbd6777a0 100644
--- a/config/alfresco/script-services-context.xml
+++ b/config/alfresco/script-services-context.xml
@@ -3,11 +3,8 @@
-
-
-
-
-
+
+
diff --git a/source/java/org/alfresco/repo/action/ParameterDefinitionImpl.java b/source/java/org/alfresco/repo/action/ParameterDefinitionImpl.java
index 99e0f57556..51c6f27be1 100644
--- a/source/java/org/alfresco/repo/action/ParameterDefinitionImpl.java
+++ b/source/java/org/alfresco/repo/action/ParameterDefinitionImpl.java
@@ -43,6 +43,11 @@ public class ParameterDefinitionImpl implements ParameterDefinition, Serializabl
*/
private QName type;
+ /**
+ * Is this a multi-valued parameter?
+ */
+ private boolean isMultiValued;
+
/**
* The display label
*/
@@ -70,8 +75,30 @@ public class ParameterDefinitionImpl implements ParameterDefinition, Serializabl
this.type = type;
this.displayLabel = displayLabel;
this.isMandatory = isMandatory;
+ this.isMultiValued = false;
}
-
+
+ /**
+ * Constructor
+ *
+ * @param name the name of the parameter
+ * @param type the type of the parameter
+ * @param displayLabel the display label
+ */
+ public ParameterDefinitionImpl(
+ String name,
+ QName type,
+ boolean isMandatory,
+ String displayLabel,
+ boolean isMultiValued)
+ {
+ this.name = name;
+ this.type = type;
+ this.displayLabel = displayLabel;
+ this.isMandatory = isMandatory;
+ this.isMultiValued = isMultiValued;
+ }
+
/**
* @see org.alfresco.service.cmr.action.ParameterDefinition#getName()
*/
@@ -96,6 +123,14 @@ public class ParameterDefinitionImpl implements ParameterDefinition, Serializabl
return this.isMandatory;
}
+ /**
+ * @see org.alfresco.service.cmr.action.ParameterDefinition#isMultiValued()
+ */
+ public boolean isMultiValued()
+ {
+ return this.isMultiValued;
+ }
+
/**
* @see org.alfresco.service.cmr.action.ParameterDefinition#getDisplayLabel()
*/
diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java
index e3ff6e4590..c256f07916 100644
--- a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java
+++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java
@@ -311,7 +311,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase
protected void addParameterDefinitions(List paramList)
{
paramList.add(new ParameterDefinitionImpl(PARAM_TO, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_TO)));
- paramList.add(new ParameterDefinitionImpl(PARAM_TO_MANY, DataTypeDefinition.ANY, false, getParamDisplayLabel(PARAM_TO_MANY)));
+ paramList.add(new ParameterDefinitionImpl(PARAM_TO_MANY, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_TO_MANY), true));
paramList.add(new ParameterDefinitionImpl(PARAM_SUBJECT, DataTypeDefinition.TEXT, true, getParamDisplayLabel(PARAM_SUBJECT)));
paramList.add(new ParameterDefinitionImpl(PARAM_TEXT, DataTypeDefinition.TEXT, true, getParamDisplayLabel(PARAM_TEXT)));
paramList.add(new ParameterDefinitionImpl(PARAM_FROM, DataTypeDefinition.TEXT, false, getParamDisplayLabel(PARAM_FROM)));
diff --git a/source/java/org/alfresco/repo/jscript/Actions.java b/source/java/org/alfresco/repo/jscript/Actions.java
new file mode 100644
index 0000000000..41f566e2de
--- /dev/null
+++ b/source/java/org/alfresco/repo/jscript/Actions.java
@@ -0,0 +1,319 @@
+/*
+ * 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.repo.jscript;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.action.Action;
+import org.alfresco.service.cmr.action.ActionDefinition;
+import org.alfresco.service.cmr.action.ActionService;
+import org.alfresco.service.cmr.action.ParameterDefinition;
+import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
+import org.alfresco.service.namespace.QName;
+import org.mozilla.javascript.Scriptable;
+
+
+/**
+ * Scripted Action service for describing and executing actions against Nodes.
+ *
+ * @author davidc
+ */
+public final class Actions implements Scopeable
+{
+ /** Repository Service Registry */
+ private ServiceRegistry services;
+
+ /** Root scope for this object */
+ private Scriptable scope;
+
+
+ /**
+ * Constructor
+ *
+ * @param services repository service registry
+ */
+ public Actions(ServiceRegistry services)
+ {
+ this.services = services;
+ }
+
+ /**
+ * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable)
+ */
+ public void setScope(Scriptable scope)
+ {
+ this.scope = scope;
+ }
+
+ /**
+ * Gets the list of registered action names
+ *
+ * @return the registered action names
+ */
+ public String[] getRegistered()
+ {
+ ActionService actionService = services.getActionService();
+ List defs = actionService.getActionDefinitions();
+ String[] registered = new String[defs.size()];
+ int i = 0;
+ for (ActionDefinition def : defs)
+ {
+ registered[i++] = def.getName();
+ }
+ return registered;
+ }
+
+ public String[] jsGet_registered()
+ {
+ return getRegistered();
+ }
+
+ /**
+ * Create an Action
+ *
+ * @param actionName the action name
+ * @return the action
+ */
+ public ScriptAction createAction(String actionName)
+ {
+ ScriptAction scriptAction = null;
+ ActionService actionService = services.getActionService();
+ ActionDefinition actionDef = actionService.getActionDefinition(actionName);
+ if (actionDef != null)
+ {
+ Action action = actionService.createAction(actionName);
+ scriptAction = new ScriptAction(action, actionDef);
+ scriptAction.setScope(scope);
+ }
+ return scriptAction;
+ }
+
+
+ /**
+ * Scriptable Action
+ *
+ * @author davidc
+ */
+ public final class ScriptAction implements Serializable, Scopeable
+ {
+ private static final long serialVersionUID = 5794161358406531996L;
+
+ /** Root scope for this object */
+ private Scriptable scope;
+
+ /** Converter with knowledge of action parameter values */
+ private ActionValueConverter converter;
+
+ /** Action state */
+ private Action action;
+ private ActionDefinition actionDef;
+ private ScriptableParameterMap parameters = null;
+
+
+ /**
+ * Construct
+ *
+ * @param action Alfresco action
+ */
+ public ScriptAction(Action action, ActionDefinition actionDef)
+ {
+ this.action = action;
+ this.actionDef = actionDef;
+ this.converter = new ActionValueConverter();
+ }
+
+ /**
+ * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable)
+ */
+ public void setScope(Scriptable scope)
+ {
+ this.scope = scope;
+ }
+
+ /**
+ * Return all the properties known about this node.
+ *
+ * The Map returned implements the Scriptable interface to allow access to the properties via
+ * JavaScript associative array access. This means properties of a node can be access thus:
+ * node.properties["name"]
+ *
+ * @return Map of properties for this Node.
+ */
+ @SuppressWarnings("synthetic-access")
+ public Map getParameters()
+ {
+ if (this.parameters == null)
+ {
+ // this Map implements the Scriptable interface for native JS syntax property access
+ this.parameters = new ScriptableParameterMap();
+ Map actionParams = this.action.getParameterValues();
+ for (Map.Entry entry : actionParams.entrySet())
+ {
+ String name = entry.getKey();
+ this.parameters.put(name, converter.convertActionParamForScript(name, entry.getValue()));
+ }
+ this.parameters.setModified(false);
+ }
+ return this.parameters;
+ }
+
+ public MapjsGet_parameters()
+ {
+ return getParameters();
+ }
+
+ /**
+ * Execute action
+ *
+ * @param node the node to execute action upon
+ */
+ @SuppressWarnings("synthetic-access")
+ public void execute(Node node)
+ {
+ if (this.parameters.isModified())
+ {
+ Map actionParams = action.getParameterValues();
+ actionParams.clear();
+
+ for (Map.Entry entry : this.parameters.entrySet())
+ {
+ // perform the conversion from script wrapper object to repo serializable values
+ String name = entry.getKey();
+ Serializable value = converter.convertActionParamForRepo(name, entry.getValue());
+ actionParams.put(name, value);
+ }
+ }
+ services.getActionService().executeAction(action, node.getNodeRef());
+ }
+
+ /**
+ * Value converter with specific knowledge of action parameters
+ *
+ * @author davidc
+ */
+ private class ActionValueConverter extends ValueConverter
+ {
+ /**
+ * Convert Action Parameter for Script usage
+ *
+ * @param paramName parameter name
+ * @param value value to convert
+ * @return converted value
+ */
+ @SuppressWarnings("synthetic-access")
+ public Serializable convertActionParamForScript(String paramName, Serializable value)
+ {
+ ParameterDefinition paramDef = actionDef.getParameterDefintion(paramName);
+ if (paramDef != null && paramDef.getType().equals(DataTypeDefinition.QNAME))
+ {
+ return ((QName)value).toPrefixString(services.getNamespaceService());
+ }
+ else
+ {
+ return convertValueForScript(services, scope, null, value);
+ }
+ }
+
+ /**
+ * Convert Action Parameter for Java usage
+ *
+ * @param paramName parameter name
+ * @param value value to convert
+ * @return converted value
+ */
+ @SuppressWarnings("synthetic-access")
+ public Serializable convertActionParamForRepo(String paramName, Serializable value)
+ {
+ ParameterDefinition paramDef = actionDef.getParameterDefintion(paramName);
+ if (paramDef != null && paramDef.getType().equals(DataTypeDefinition.QNAME))
+ {
+ return QName.createQName((String)value, services.getNamespaceService());
+ }
+ else
+ {
+ return convertValueForRepo(value);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Scripted Parameter map with modified flag.
+ *
+ * @author davidc
+ */
+ public static final class ScriptableParameterMap extends ScriptableHashMap
+ {
+ private static final long serialVersionUID = 574661815973241554L;
+ private boolean modified = false;
+
+
+ /**
+ * Is this a modified parameter map?
+ *
+ * @return true => modified
+ */
+ /*package*/ boolean isModified()
+ {
+ return modified;
+ }
+
+ /**
+ * Set explicitly whether this map is modified
+ *
+ * @param modified true => modified, false => not modified
+ */
+ /*package*/ void setModified(boolean modified)
+ {
+ this.modified = modified;
+ }
+
+ /* (non-Javadoc)
+ * @see org.mozilla.javascript.Scriptable#getClassName()
+ */
+ @Override
+ public String getClassName()
+ {
+ return "ScriptableParameterMap";
+ }
+
+ /* (non-Javadoc)
+ * @see org.mozilla.javascript.Scriptable#delete(java.lang.String)
+ */
+ @Override
+ public void delete(String name)
+ {
+ super.delete(name);
+ setModified(true);
+ }
+
+ /* (non-Javadoc)
+ * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object)
+ */
+ @Override
+ public void put(String name, Scriptable start, Object value)
+ {
+ super.put(name, start, value);
+ setModified(true);
+ }
+ }
+
+}
diff --git a/source/java/org/alfresco/repo/jscript/Node.java b/source/java/org/alfresco/repo/jscript/Node.java
index 7d2f379f56..cce8c71aa7 100644
--- a/source/java/org/alfresco/repo/jscript/Node.java
+++ b/source/java/org/alfresco/repo/jscript/Node.java
@@ -20,9 +20,6 @@ import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -61,12 +58,8 @@ import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.mozilla.javascript.Context;
-import org.mozilla.javascript.NativeArray;
-import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
-import org.mozilla.javascript.Wrapper;
import org.springframework.util.StringUtils;
/**
@@ -93,6 +86,9 @@ public final class Node implements Serializable, Scopeable
/** Root scope for this object */
private Scriptable scope;
+ /** Node Value Converter */
+ private NodeValueConverter converter = null;
+
/** Cached values */
private NodeRef nodeRef;
private String name;
@@ -117,6 +113,7 @@ public final class Node implements Serializable, Scopeable
// NOTE: see the reset() method when adding new cached members!
+
// ------------------------------------------------------------------------------
// Construction
@@ -158,6 +155,7 @@ public final class Node implements Serializable, Scopeable
this.nodeService = services.getNodeService();
this.imageResolver = resolver;
this.scope = scope;
+ this.converter = new NodeValueConverter();
}
/**
@@ -407,7 +405,7 @@ public final class Node implements Serializable, Scopeable
Serializable propValue = props.get(qname);
// perform the conversion to a script safe value and store
- this.properties.put(qname.toString(), convertValueForScript(qname, propValue));
+ this.properties.put(qname.toString(), converter.convertValueForScript(qname, propValue));
}
}
@@ -900,148 +898,13 @@ public final class Node implements Serializable, Scopeable
Serializable value = (Serializable)this.properties.get(key);
// perform the conversion from script wrapper object to repo serializable values
- value = convertValueForRepo(value);
+ value = converter.convertValueForRepo(value);
props.put(createQName(key), value);
}
this.nodeService.setProperties(this.nodeRef, props);
}
- /**
- * Convert an object from any script wrapper value to a valid repository serializable value.
- * This includes converting JavaScript Array objects to Lists of valid objects.
- *
- * @param value Value to convert from script wrapper object to repo serializable value
- *
- * @return valid repo value
- */
- private static Serializable convertValueForRepo(Serializable value)
- {
- if (value == null)
- {
- return null;
- }
- else if (value instanceof Node)
- {
- // convert back to NodeRef
- value = ((Node)value).getNodeRef();
- }
- else if (value instanceof ScriptContentData)
- {
- // convert back to ContentData
- value = ((ScriptContentData)value).contentData;
- }
- else if (value instanceof Wrapper)
- {
- // unwrap a Java object from a JavaScript wrapper
- // recursively call this method to convert the unwrapped value
- value = convertValueForRepo((Serializable)((Wrapper)value).unwrap());
- }
- else if (value instanceof ScriptableObject)
- {
- // a scriptable object will probably indicate a multi-value property
- // set using a JavaScript Array object
- ScriptableObject values = (ScriptableObject)value;
-
- if (value instanceof NativeArray)
- {
- // convert JavaScript array of values to a List of Serializable objects
- Object[] propIds = values.getIds();
- List propValues = new ArrayList(propIds.length);
- for (int i=0; i list = new ArrayList(array.length);
- for (int i=0; i collection = (Collection)value;
- Serializable[] array = new Serializable[collection.size()];
- int index = 0;
- for (Serializable obj : collection)
- {
- array[index++] = convertValueForScript(qname, obj);
- }
- value = array;
- }
- // simple numbers and strings are wrapped automatically by Rhino
-
- return value;
- }
/**
* Re-sets the type of the node. Can be called in order specialise a node to a sub-type.
@@ -1346,7 +1209,7 @@ public final class Node implements Serializable, Scopeable
{
// get the value out for the specified key - make sure it is Serializable
Object value = props.get((String)propId, props);
- value = convertValueForRepo((Serializable)value);
+ value = converter.convertValueForRepo((Serializable)value);
aspectProps.put(createQName((String)propId), (Serializable)value);
}
}
@@ -1770,6 +1633,68 @@ public final class Node implements Serializable, Scopeable
}
+ // ------------------------------------------------------------------------------
+ // Value Conversion
+
+
+ /**
+ * Value converter with knowledge of Node specific value types
+ */
+ private final class NodeValueConverter extends ValueConverter
+ {
+ /**
+ * Convert an object from any repository serialized value to a valid script object.
+ * This includes converting Collection multi-value properties into JavaScript Array objects.
+ *
+ * @param qname QName of the property value for conversion
+ * @param value Property value
+ *
+ * @return Value safe for scripting usage
+ */
+ public Serializable convertValueForScript(QName qname, Serializable value)
+ {
+ return convertValueForScript(services, scope, qname, value);
+ }
+
+ /* (non-Javadoc)
+ * @see org.alfresco.repo.jscript.ValueConverter#convertValueForScript(org.alfresco.service.ServiceRegistry, org.mozilla.javascript.Scriptable, org.alfresco.service.namespace.QName, java.io.Serializable)
+ */
+ @Override
+ public Serializable convertValueForScript(ServiceRegistry services, Scriptable scope, QName qname, Serializable value)
+ {
+ if (value instanceof ContentData)
+ {
+ // ContentData object properties are converted to ScriptContentData objects
+ // so the content and other properties of those objects can be accessed
+ value = new ScriptContentData((ContentData)value, qname);
+ }
+ else
+ {
+ value = super.convertValueForScript(services, scope, qname, value);
+ }
+ return value;
+ }
+
+ /* (non-Javadoc)
+ * @see org.alfresco.repo.jscript.ValueConverter#convertValueForRepo(java.io.Serializable)
+ */
+ @Override
+ public Serializable convertValueForRepo(Serializable value)
+ {
+ if (value instanceof ScriptContentData)
+ {
+ // convert back to ContentData
+ value = ((ScriptContentData)value).contentData;
+ }
+ else
+ {
+ value = super.convertValueForRepo(value);
+ }
+ return value;
+ }
+ }
+
+
// ------------------------------------------------------------------------------
// Inner Classes
@@ -1789,7 +1714,7 @@ public final class Node implements Serializable, Scopeable
this.contentData = contentData;
this.property = property;
}
-
+
/**
* @return the content stream
*/
diff --git a/source/java/org/alfresco/repo/jscript/RhinoScriptService.java b/source/java/org/alfresco/repo/jscript/RhinoScriptService.java
index 77dafd45cd..2935ee4285 100644
--- a/source/java/org/alfresco/repo/jscript/RhinoScriptService.java
+++ b/source/java/org/alfresco/repo/jscript/RhinoScriptService.java
@@ -50,30 +50,18 @@ public class RhinoScriptService implements ScriptService
{
private static final Logger logger = Logger.getLogger(RhinoScriptService.class);
- /** The permission-safe node service */
- private NodeService nodeService;
-
- /** The Content Service to use */
- private ContentService contentService;
+ /** Repository Service Registry */
+ private ServiceRegistry services;
+
/**
- * Set the node service
+ * Set the Service Registry
*
- * @param nodeService The permission-safe node service
+ * @param service registry
*/
- public void setNodeService(NodeService nodeService)
+ public void setServiceRegistry(ServiceRegistry services)
{
- this.nodeService = nodeService;
- }
-
- /**
- * Set the content service
- *
- * @param contentService The ContentService to use
- */
- public void setContentService(ContentService contentService)
- {
- this.contentService = contentService;
+ this.services = services;
}
/**
@@ -136,7 +124,7 @@ public class RhinoScriptService implements ScriptService
Reader reader = null;
try
{
- if (this.nodeService.exists(scriptRef) == false)
+ if (this.services.getNodeService().exists(scriptRef) == false)
{
throw new AlfrescoRuntimeException("Script Node does not exist: " + scriptRef);
}
@@ -145,7 +133,7 @@ public class RhinoScriptService implements ScriptService
{
contentProp = ContentModel.PROP_CONTENT;
}
- ContentReader cr = this.contentService.getReader(scriptRef, contentProp);
+ ContentReader cr = this.services.getContentService().getReader(scriptRef, contentProp);
if (cr == null || cr.exists() == false)
{
throw new AlfrescoRuntimeException("Script Node content not found: " + scriptRef);
@@ -224,9 +212,18 @@ public class RhinoScriptService implements ScriptService
// you need one. However, initStandardObjects is an expensive method to call and it
// allocates a fair amount of memory.
Scriptable scope = cx.initStandardObjects();
+
+ // there's always a model, if only to hold the util objects
+ if (model == null)
+ {
+ model = new HashMap();
+ }
+
+ // add useful util objects
+ model.put("actions", new Actions(services));
+ model.put("logger", new ScriptLogger());
// insert supplied object model into root of the default scope
- if (model != null)
{
for (String key : model.keySet())
{
@@ -346,9 +343,7 @@ public class RhinoScriptService implements ScriptService
model.put("space", new Node(space, services, resolver));
}
- // add other useful util objects
model.put("search", new Search(services, companyHome.getStoreRef(), resolver));
- model.put("logger", new ScriptLogger());
return model;
}
diff --git a/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java b/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java
index df3f5fe57c..00a46022bf 100644
--- a/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java
+++ b/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java
@@ -24,6 +24,7 @@ import java.util.Map;
import junit.framework.TestCase;
+import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.dictionary.DictionaryComponent;
import org.alfresco.repo.dictionary.DictionaryDAO;
@@ -267,7 +268,114 @@ public class RhinoScriptTest extends TestCase
});
}
+ public void testScriptActions()
+ {
+ TransactionUtil.executeInUserTransaction(
+ transactionService,
+ new TransactionUtil.TransactionWork