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() + { + public Object doWork() throws Exception + { + StoreRef store = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "rhino_" + System.currentTimeMillis()); + NodeRef root = nodeService.getRootNode(store); + + try + { + // create a content object + ChildAssociationRef childRef = nodeService.createNode( + root, + BaseNodeServiceTest.ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName(BaseNodeServiceTest.NAMESPACE, "script_content"), + BaseNodeServiceTest.TYPE_QNAME_TEST_CONTENT, + null); + NodeRef contentNodeRef = childRef.getChildRef(); + ContentWriter writer = contentService.getWriter( + contentNodeRef, + BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, + true); + writer.setMimetype("application/x-javascript"); + writer.putContent(TESTSCRIPT1); + + + // create an Alfresco scriptable Node object + // the Node object is a wrapper similar to the TemplateNode concept + Map model = new HashMap(); + model.put("doc", new Node(childRef.getChildRef(), serviceRegistry, null)); + model.put("root", new Node(root, serviceRegistry, null)); + + // execute to add aspect via action + Object result = scriptService.executeScript(TESTSCRIPT_CLASSPATH2, model); + System.out.println("Result from TESTSCRIPT_CLASSPATH2: " + result.toString()); + assertTrue((Boolean)result); // we know the result is a boolean + + // ensure aspect has been added via script + assertTrue(nodeService.hasAspect(childRef.getChildRef(), ContentModel.ASPECT_LOCKABLE)); + } + catch (Throwable err) + { + err.printStackTrace(); + fail(err.getMessage()); + } + + return null; + } + }); + } + + + public void xtestScriptActionsMail() + { + TransactionUtil.executeInUserTransaction( + transactionService, + new TransactionUtil.TransactionWork() + { + public Object doWork() throws Exception + { + StoreRef store = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "rhino_" + System.currentTimeMillis()); + NodeRef root = nodeService.getRootNode(store); + + try + { + // create a content object + ChildAssociationRef childRef = nodeService.createNode( + root, + BaseNodeServiceTest.ASSOC_TYPE_QNAME_TEST_CHILDREN, + QName.createQName(BaseNodeServiceTest.NAMESPACE, "script_content"), + BaseNodeServiceTest.TYPE_QNAME_TEST_CONTENT, + null); + NodeRef contentNodeRef = childRef.getChildRef(); + ContentWriter writer = contentService.getWriter( + contentNodeRef, + BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, + true); + writer.setMimetype("application/x-javascript"); + writer.putContent(TESTSCRIPT1); + + // create an Alfresco scriptable Node object + // the Node object is a wrapper similar to the TemplateNode concept + Map model = new HashMap(); + model.put("doc", new Node(childRef.getChildRef(), serviceRegistry, null)); + model.put("root", new Node(root, serviceRegistry, null)); + + // execute to add aspect via action + Object result = scriptService.executeScript(TESTSCRIPT_CLASSPATH3, model); + System.out.println("Result from TESTSCRIPT_CLASSPATH3: " + result.toString()); + assertTrue((Boolean)result); // we know the result is a boolean + } + catch (Throwable err) + { + err.printStackTrace(); + fail(err.getMessage()); + } + + return null; + } + }); + } + private static final String TESTSCRIPT_CLASSPATH1 = "org/alfresco/repo/jscript/test_script1.js"; + private static final String TESTSCRIPT_CLASSPATH2 = "org/alfresco/repo/jscript/test_script2.js"; + private static final String TESTSCRIPT_CLASSPATH3 = "org/alfresco/repo/jscript/test_script3.js"; private static final String TESTSCRIPT1 = "var id = root.id;\r\n" + diff --git a/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java b/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java index 3f3f76766b..77bbba3483 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java +++ b/source/java/org/alfresco/repo/jscript/ScriptableHashMap.java @@ -24,8 +24,10 @@ import org.mozilla.javascript.Scriptable; /** * @author Kevin Roast */ -public class ScriptableHashMap extends LinkedHashMap implements Scriptable +public class ScriptableHashMap extends LinkedHashMap implements Scriptable { + private static final long serialVersionUID = 3664761893203964569L; + private Scriptable parentScope; private Scriptable prototype; @@ -88,10 +90,11 @@ public class ScriptableHashMap extends LinkedHashMap implements Scriptable /** * @see org.mozilla.javascript.Scriptable#put(java.lang.String, org.mozilla.javascript.Scriptable, java.lang.Object) */ + @SuppressWarnings("unchecked") public void put(String name, Scriptable start, Object value) { // add the property to the underlying QName map - put(name, value); + put((K)name, (V)value); } /** diff --git a/source/java/org/alfresco/repo/jscript/ValueConverter.java b/source/java/org/alfresco/repo/jscript/ValueConverter.java new file mode 100644 index 0000000000..9fb35452c9 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/ValueConverter.java @@ -0,0 +1,170 @@ +/* + * 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.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +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; + + +/** + * Value conversion allowing safe usage of values in Script and Java. + */ +public class 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 services Repository Services Registry + * @param scope Scripting scope + * @param qname QName of the property value for conversion + * @param value Property value + * + * @return Value safe for scripting usage + */ + public Serializable convertValueForScript(ServiceRegistry services, Scriptable scope, QName qname, Serializable value) + { + // perform conversions from Java objects to JavaScript scriptable instances + if (value == null) + { + return null; + } + else if (value instanceof NodeRef) + { + // NodeRef object properties are converted to new Node objects + // so they can be used as objects within a template + value = new Node(((NodeRef)value), services, null, scope); + } + else if (value instanceof Date) + { + // convert Date to JavaScript native Date object + // call the "Date" constructor on the root scope object - passing in the millisecond + // value from the Java date - this will construct a JavaScript Date with the same value + Date date = (Date)value; + Object val = ScriptRuntime.newObject( + Context.getCurrentContext(), scope, "Date", new Object[] {date.getTime()}); + value = (Serializable)val; + } + else if (value instanceof Collection) + { + // recursively convert each value in the collection + Collection collection = (Collection)value; + Serializable[] array = new Serializable[collection.size()]; + int index = 0; + for (Serializable obj : collection) + { + array[index++] = convertValueForScript(services, scope, qname, obj); + } + value = array; + } + // simple numbers and strings are wrapped automatically by Rhino + + return value; + } + + + /** + * 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 + */ + public 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 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