From 2b251c922ba44a623a0e24eeb47f0aee8e896ff4 Mon Sep 17 00:00:00 2001 From: Kevin Roast Date: Fri, 28 Apr 2006 10:56:48 +0000 Subject: [PATCH] . Checkpoint for the Rhino JavaScript engine integration: - Additions to the Alfresco JavaScript data-model . JUnit test for each entry point into Rhino and the ScriptService - tests for various API calls on the Scriptable Node object . More javadoc clean-up in templating and script services git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2726 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../java/org/alfresco/repo/jscript/Node.java | 225 ++++++++++++++++-- .../repo/jscript/RhinoScriptService.java | 22 +- .../repo/jscript/RhinoScriptTest.java | 168 ++++++++++--- .../repo/jscript/ScriptableQNameMap.java | 11 +- .../org/alfresco/repo/jscript/test_script1.js | 25 ++ .../service/cmr/repository/ScriptService.java | 6 +- .../cmr/repository/TemplateImageResolver.java | 4 +- .../org/alfresco/service/namespace/QName.java | 37 +-- 8 files changed, 422 insertions(+), 76 deletions(-) create mode 100644 source/java/org/alfresco/repo/jscript/test_script1.js diff --git a/source/java/org/alfresco/repo/jscript/Node.java b/source/java/org/alfresco/repo/jscript/Node.java index a338e4571c..bb0d9b58c4 100644 --- a/source/java/org/alfresco/repo/jscript/Node.java +++ b/source/java/org/alfresco/repo/jscript/Node.java @@ -23,6 +23,7 @@ import java.text.MessageFormat; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.StringTokenizer; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; @@ -37,6 +38,7 @@ 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.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateNode; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.apache.commons.logging.Log; @@ -44,11 +46,12 @@ import org.apache.commons.logging.LogFactory; import org.springframework.util.StringUtils; /** - * Node class specific for use by Template pages that support Bean objects as part of the model. - * The default template engine FreeMarker can use these objects and they are provided to support it. - * A single method is completely freemarker specific - getXmlNodeModel() + * Node class implementation specific for use by ScriptService as part of the object model. *

- * The class exposes Node properties, children as dynamically populated maps and lists. + *

+ * The class exposes Node properties, children and assocs as dynamically populated maps and lists. + * The various collection classes are mirrored as JavaScript properties. So can be accessed using + * standard JavaScript syntax, such as node.children[0].properties.name. *

* Various helper methods are provided to access common and useful node variables such * as the content url and type information. @@ -113,8 +116,6 @@ public final class Node implements Serializable this.id = nodeRef.getId(); this.services = services; this.imageResolver = resolver; - - this.properties = new ScriptableQNameMap(this.services.getNamespaceService()); } /** @@ -125,6 +126,11 @@ public final class Node implements Serializable return this.id; } + public String jsGet_id() + { + return getId(); + } + /** * @return Returns the NodeRef this Node object represents */ @@ -133,6 +139,11 @@ public final class Node implements Serializable return this.nodeRef; } + public NodeRef jsGet_nodeRef() + { + return getNodeRef(); + } + /** * @return Returns the type. */ @@ -146,6 +157,11 @@ public final class Node implements Serializable return type; } + public QName jsGet_type() + { + return getType(); + } + /** * @return The display name for the node */ @@ -174,6 +190,16 @@ public final class Node implements Serializable return this.name; } + public String jsGet_name() + { + return getName(); + } + + public void jsSet_name(String name) + { + this.getProperties().put(ContentModel.PROP_NAME.toString(), name); + } + /** * @return The children of this Node as Node wrappers */ @@ -194,21 +220,54 @@ public final class Node implements Serializable return this.children; } + public Node[] jsGet_children() + { + return getChildren(); + } + /** - * @return A map capable of returning the Node at the specified Path as a child of this node. + * @return Returns the Node at the specified 'cm:name' based Path walking the children of this Node. + * So a valid call might be mynode.childByNamePath("/QA/Testing/Docs"); */ - //public Map getChildByNamePath() + public Node childByNamePath(String path) + { + // convert the name based path to a valid XPath query + StringBuilder xpath = new StringBuilder(path.length() << 1); + for (StringTokenizer t = new StringTokenizer(path, "/"); t.hasMoreTokens(); /**/) + { + if (xpath.length() != 0) + { + xpath.append('/'); + } + xpath.append("*[@cm:name='") + .append(t.nextToken()) // TODO: use QueryParameterDefinition see FileFolderService.search() + .append("']"); + } + + Node[] nodes = getChildrenByXPath(xpath.toString(), true); + + return (nodes.length != 0) ? nodes[0] : null; + } + + // TODO: find out why this doesn't work - the function defs do not seem to get found + //public Node jsFunction_childByNamePath(String path) //{ - // return new NamePathResultsMap(this, this.services); + // return getChildByNamePath(path); //} /** - * @return A map capable of returning a List of Node objects from an XPath query - * as children of this node. + * @return Returns the Nodes at the specified XPath walking the children of this Node. + * So a valid call might be mynode.childrenByXPath("/*[@cm:name='Testing']/*"); */ - //public Map getChildrenByXPath() + public Node[] childrenByXPath(String xpath) + { + return getChildrenByXPath(xpath, false); + } + + // TODO: find out why this doesn't work - the function defs do not seem to get found + //public Node[] jsFunction_childrenByXPath(String xpath) //{ - // return new XPathResultsMap(this, this.services); + // return childrenByXPath(xpath); //} /** @@ -242,8 +301,10 @@ public final class Node implements Serializable { if (this.assocs == null) { - List refs = this.services.getNodeService().getTargetAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL); + // this Map implements the Scriptable interface for native JS syntax property access this.assocs = new ScriptableQNameMap(this.services.getNamespaceService()); + + List refs = this.services.getNodeService().getTargetAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL); for (AssociationRef ref : refs) { String qname = ref.getTypeQName().toString(); @@ -268,6 +329,11 @@ public final class Node implements Serializable return this.assocs; } + public Map jsGet_assocs() + { + return getAssocs(); + } + /** * Return all the properties known about this node. * @@ -281,8 +347,10 @@ public final class Node implements Serializable { if (this.propsRetrieved == false) { - Map props = this.services.getNodeService().getProperties(this.nodeRef); + // this Map implements the Scriptable interface for native JS syntax property access + this.properties = new ScriptableQNameMap(this.services.getNamespaceService()); + Map props = this.services.getNodeService().getProperties(this.nodeRef); for (QName qname : props.keySet()) { Serializable propValue = props.get(qname); @@ -307,6 +375,11 @@ public final class Node implements Serializable return this.properties; } + public Map jsGet_properties() + { + return getProperties(); + } + /** * @return true if this Node is a container (i.e. a folder) */ @@ -322,6 +395,11 @@ public final class Node implements Serializable return isContainer.booleanValue(); } + public boolean jsGet_isContainer() + { + return isContainer(); + } + /** * @return true if this Node is a Document (i.e. with content) */ @@ -336,6 +414,11 @@ public final class Node implements Serializable return isDocument.booleanValue(); } + public boolean jsGet_isDocument() + { + return isDocument(); + } + /** * @return The list of aspects applied to this node */ @@ -349,6 +432,11 @@ public final class Node implements Serializable return this.aspects; } + public QName[] jsGet_aspects() + { + return getAspects().toArray(new QName[getAspects().size()]); + } + /** * @param aspect The aspect name to test for * @@ -400,6 +488,11 @@ public final class Node implements Serializable return displayPath; } + public String jsGet_displayPath() + { + return getDisplayPath(); + } + /** * @return the small icon image for this node */ @@ -422,6 +515,11 @@ public final class Node implements Serializable } } + public String jsGet_icon16() + { + return getIcon16(); + } + /** * @return the large icon image for this node */ @@ -452,6 +550,11 @@ public final class Node implements Serializable } } + public String jsGet_icon32() + { + return getIcon32(); + } + /** * @return true if the node is currently locked */ @@ -471,6 +574,11 @@ public final class Node implements Serializable return locked; } + public boolean jsGet_isLocked() + { + return isLocked(); + } + /** * @return the parent node */ @@ -489,6 +597,11 @@ public final class Node implements Serializable return parent; } + public Node jsGet_parent() + { + return getParent(); + } + /** * * @return the primary parent association so we can get at the association QName and the association type QName. @@ -502,6 +615,11 @@ public final class Node implements Serializable return primaryParentAssoc; } + public ChildAssociationRef jsGet_primaryParentAssoc() + { + return getPrimaryParentAssoc(); + } + /** * @return the content String for this node from the default content property * (@see ContentModel.PROP_CONTENT) @@ -513,6 +631,11 @@ public final class Node implements Serializable return (reader != null && reader.exists()) ? reader.getContentString() : ""; } + public String jsGet_content() + { + return getContent(); + } + /** * @return For a content document, this method returns the URL to the content stream for * the default content property (@see ContentModel.PROP_CONTENT) @@ -545,6 +668,11 @@ public final class Node implements Serializable } } + public String jsGet_url() + { + return getUrl(); + } + /** * @return The mimetype encoding for content attached to the node from the default content property * (@see ContentModel.PROP_CONTENT) @@ -563,6 +691,11 @@ public final class Node implements Serializable return mimetype; } + public String jsGet_mimetype() + { + return getMimetype(); + } + /** * @return The size in bytes of the content attached to the node from the default content property * (@see ContentModel.PROP_CONTENT) @@ -581,6 +714,11 @@ public final class Node implements Serializable return size != null ? size.longValue() : 0L; } + public long jsGet_size() + { + return getSize(); + } + /** * @return the image resolver instance used by this node */ @@ -606,6 +744,63 @@ public final class Node implements Serializable } } + /** + * Return a list or a single Node from executing an xpath against the parent Node. + * + * @param xpath XPath to execute + * @param firstOnly True to return the first result only + * + * @return Node[] can be empty but never null + */ + private Node[] getChildrenByXPath(String xpath, boolean firstOnly) + { + Node[] result = null; + + if (xpath.length() != 0) + { + if (logger.isDebugEnabled()) + logger.debug("Executing xpath: " + xpath); + + NodeRef contextRef; + if (getParent() != null) + { + contextRef = getParent().getNodeRef(); + } + else + { + contextRef = this.services.getNodeService().getRootNode(nodeRef.getStoreRef()); + } + List nodes = this.services.getSearchService().selectNodes( + contextRef, + xpath, + null, + this.services.getNamespaceService(), + false); + + // see if we only want the first result + if (firstOnly == true) + { + if (nodes.size() != 0) + { + result = new Node[1]; + result[0] = new Node(nodes.get(0), this.services, this.imageResolver); + } + } + // or all the results + else + { + result = new Node[nodes.size()]; + for (int i=0; i model) throws ScriptException + public Object executeScript(String scriptClasspath, Map model) + throws ScriptException { if (scriptClasspath == null) { @@ -104,9 +106,10 @@ public class RhinoScriptService implements ScriptService } /** - * @see org.alfresco.service.cmr.repository.ScriptService#executeScript(org.alfresco.service.cmr.repository.NodeRef, java.util.Map) + * @see org.alfresco.service.cmr.repository.ScriptService#executeScript(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName, java.util.Map) */ - public Object executeScript(NodeRef scriptRef, Map model) throws ScriptException + public Object executeScript(NodeRef scriptRef, QName contentProp, Map model) + throws ScriptException { if (scriptRef == null) { @@ -120,7 +123,12 @@ public class RhinoScriptService implements ScriptService { throw new AlfrescoRuntimeException("Script Node does not exist: " + scriptRef); } - ContentReader cr = this.contentService.getReader(scriptRef, ContentModel.PROP_CONTENT); + + if (contentProp == null) + { + contentProp = ContentModel.PROP_CONTENT; + } + ContentReader cr = this.contentService.getReader(scriptRef, contentProp); if (cr == null || cr.exists() == false) { throw new AlfrescoRuntimeException("Script Node content not found: " + scriptRef); @@ -145,7 +153,8 @@ public class RhinoScriptService implements ScriptService /** * @see org.alfresco.service.cmr.repository.ScriptService#executeScriptString(java.lang.String, java.util.Map) */ - public Object executeScriptString(String script, Map model) throws ScriptException + public Object executeScriptString(String script, Map model) + throws ScriptException { if (script == null || script.length() == 0) { @@ -193,7 +202,8 @@ public class RhinoScriptService implements ScriptService { for (String key : model.keySet()) { - ScriptableObject.putProperty(scope, key, model.get(key)); + Object jsObject = Context.javaToJS(model.get(key), scope); + ScriptableObject.putProperty(scope, key, jsObject); } } diff --git a/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java b/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java index 72132b6c90..df3f5fe57c 100644 --- a/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java +++ b/source/java/org/alfresco/repo/jscript/RhinoScriptTest.java @@ -17,10 +17,14 @@ package org.alfresco.repo.jscript; import java.io.InputStream; +import java.io.Serializable; +import java.util.HashMap; import java.util.List; +import java.util.Map; import junit.framework.TestCase; +import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.dictionary.DictionaryComponent; import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.M2Model; @@ -29,11 +33,14 @@ import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.ScriptService; import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.repository.TemplateService; +import org.alfresco.service.namespace.QName; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ApplicationContextHelper; import org.mozilla.javascript.Context; @@ -49,11 +56,11 @@ public class RhinoScriptTest extends TestCase private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); private ContentService contentService; - private TemplateService templateService; private NodeService nodeService; private TransactionService transactionService; private ServiceRegistry serviceRegistry; private AuthenticationComponent authenticationComponent; + private ScriptService scriptService; /* * @see junit.framework.TestCase#setUp() @@ -65,7 +72,7 @@ public class RhinoScriptTest extends TestCase transactionService = (TransactionService)this.ctx.getBean("transactionComponent"); contentService = (ContentService)this.ctx.getBean("contentService"); nodeService = (NodeService)this.ctx.getBean("nodeService"); - templateService = (TemplateService)this.ctx.getBean("templateService"); + scriptService = (ScriptService)this.ctx.getBean("scriptService"); serviceRegistry = (ServiceRegistry)this.ctx.getBean("ServiceRegistry"); this.authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); @@ -98,7 +105,55 @@ public class RhinoScriptTest extends TestCase super.tearDown(); } - public void testRhino() + public void testRhinoIntegration() + { + TransactionUtil.executeInUserTransaction( + transactionService, + new TransactionUtil.TransactionWork() + { + public Object doWork() throws Exception + { + // check that rhino script engine is available + Context cx = Context.enter(); + try + { + // The easiest way to embed Rhino is just to create a new scope this way whenever + // you need one. However, initStandardObjects is an expensive method to call and it + // allocates a fair amount of memory. + Scriptable scope = cx.initStandardObjects(); + + // Now we can evaluate a script. Let's create a new associative array object + // using the object literal notation to create the members + Object result = cx.evaluateString(scope, "obj = {a:1, b:['x','y']}", "TestJS1", 1, null); + + Scriptable obj = (Scriptable)scope.get("obj", scope); + + // Should resolve a non-null value + assertNotNull(obj.get("a", obj)); + + // should resolve "obj.a == 1" - JavaScript objects come back as Number + assertEquals(new Double(1.0), obj.get("a", obj)); + + // try another script eval - this time a function call returning a result + result = cx.evaluateString(scope, "function f(x) {return x+1} f(7)", "TestJS2", 1, null); + assertEquals(8.0, cx.toNumber(result)); + } + catch (Throwable err) + { + err.printStackTrace(); + fail(err.getMessage()); + } + finally + { + cx.exit(); + } + + return null; + } + }); + } + + public void testJSObjectWrapping() { TransactionUtil.executeInUserTransaction( transactionService, @@ -110,44 +165,27 @@ public class RhinoScriptTest extends TestCase NodeRef root = nodeService.getRootNode(store); BaseNodeServiceTest.buildNodeGraph(nodeService, root); - // check that rhino script engine is available Context cx = Context.enter(); try { - // The easiest way to embed Rhino is just to create a new scope this way whenever - // you need one. However, initStandardObjects is an expensive method to call and it - // allocates a fair amount of memory. Scriptable scope = cx.initStandardObjects(); + // wrap System.out so we can perform println() from within scripts Object wrappedOut = Context.javaToJS(System.out, scope); ScriptableObject.putProperty(scope, "out", wrappedOut); - // Now we can evaluate a script. Let's create a new associative array object - // using the object literal notation to create the members - Object result = cx.evaluateString(scope, "obj = {a:1, b:['x','y']}", "TestJS1", 1, null); - - Scriptable obj = (Scriptable)scope.get("obj", scope); - - // Should resolve a non-null value - assertNotNull(obj.get("a", obj)); - // should resolve "obj.a == 1" - JavaScript objects come back as Number - assertEquals(new Double(1.0), obj.get("a", obj)); - - // try another script eval - result = cx.evaluateString(scope, "function f(x) {return x+1} f(7)", "TestJS2", 1, null); - assertEquals(8.0, cx.toNumber(result)); - - // wrap a simple Alfresco NodeRef object + // wrap a simple NodeRef Java object + // we can use Java style method calls within the script to access it's properties List childAssocs = nodeService.getChildAssocs(root); NodeRef ref1 = childAssocs.get(0).getChildRef(); Object wrappedNodeRef = Context.javaToJS(ref1, scope); ScriptableObject.putProperty(scope, "rootref", wrappedNodeRef); // evaluate script that touches the wrapped NodeRef - result = cx.evaluateString(scope, "obj = rootref.getId()", "TestJS3", 1, null); + Object result = cx.evaluateString(scope, "obj = rootref.getId()", "TestJS3", 1, null); assertEquals(ref1.getId(), cx.toString(result)); - // wrap a script Alfresco Node object - the Node object is a wrapper like TemplateNode + // wrap a scriptable Alfresco Node object - the Node object is a wrapper like TemplateNode Node node1 = new Node(root, serviceRegistry, null); Object wrappedNode = Context.javaToJS(node1, scope); ScriptableObject.putProperty(scope, "root", wrappedNode); @@ -170,15 +208,79 @@ public class RhinoScriptTest extends TestCase }); } - //private static final String TEMPLATE_1 = "org/alfresco/repo/template/test_template1.ftl"; + public void testScriptService() + { + 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); + BaseNodeServiceTest.buildNodeGraph(nodeService, root); + + try + { + Map model = new HashMap(); + model.put("out", System.out); + + // create an Alfresco scriptable Node object + // the Node object is a wrapper similar to the TemplateNode concept + Node rootNode = new Node(root, serviceRegistry, null); + model.put("root", rootNode); + + // execute test scripts using the various entry points of the ScriptService + + // test executing a script file via classpath lookup + Object result = scriptService.executeScript(TESTSCRIPT_CLASSPATH1, model); + System.out.println("Result from TESTSCRIPT_CLASSPATH1: " + result.toString()); + assertTrue((Boolean)result); // we know the result is a boolean + + // test executing a script embedded inside Node content + 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); + scriptService.executeScript(contentNodeRef, BaseNodeServiceTest.PROP_QNAME_TEST_CONTENT, model); + + // test executing a script directly as a String + scriptService.executeScriptString(TESTSCRIPT1, model); + } + 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 TESTSCRIPT1 = - "var id = root.getId();\r\n" + + "var id = root.id;\r\n" + "out.println(id);\r\n" + - "var name = root.getName();\r\n" + + "var name = root.name;\r\n" + "out.println(name);\r\n" + - "var type = root.getType();\r\n" + + "var type = root.type;\r\n" + "out.println(type);\r\n" + - "var childList = root.getChildren();\r\n" + - "out.println(\"zero index node: \" + childList[0].getName());\r\n" + - "out.println(\"properties: \" + childList[0].getProperties()[\"name\"] );"; + "var childList = root.children;\r\n" + + "out.println(\"zero index node name: \" + childList[0].name);\r\n" + + "out.println(\"name property access: \" + childList[0].properties.name );\r\n" + + "var childByNameNode = root.childByNamePath(\"/\" + childList[0].name);\r\n" + + "out.println(\"child by name path: \" + childByNameNode.name);\r\n" + + "var xpathResults = root.childrenByXPath(\"/*\");\r\n" + + "out.println(\"children of root from xpath: \" + xpathResults.length);\r\n"; } diff --git a/source/java/org/alfresco/repo/jscript/ScriptableQNameMap.java b/source/java/org/alfresco/repo/jscript/ScriptableQNameMap.java index 2830e667bd..d44da1891b 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptableQNameMap.java +++ b/source/java/org/alfresco/repo/jscript/ScriptableQNameMap.java @@ -40,14 +40,21 @@ public class ScriptableQNameMap extends QNameMap implements Scriptable { return "ScriptableQNameMap"; } - + /** * @see org.mozilla.javascript.Scriptable#get(java.lang.String, org.mozilla.javascript.Scriptable) */ public Object get(String name, Scriptable start) { // get the property from the underlying QName map - return get(name); + if ("length".equals(name)) + { + return this.size(); + } + else + { + return get(name); + } } /** diff --git a/source/java/org/alfresco/repo/jscript/test_script1.js b/source/java/org/alfresco/repo/jscript/test_script1.js new file mode 100644 index 0000000000..4c50ce9e97 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/test_script1.js @@ -0,0 +1,25 @@ +var id = root.id; +var name = root.name; +out.println("Name: " + name); +var type = root.type; +out.println("ID: " + id + " of type: " + type); +var noderef = root.nodeRef; +out.println("NodeRef: " + noderef); +var childList = root.children; +out.println("Has " + childList.length + " child nodes"); +var properties = root.properties; +out.println("Property Count: " + properties.length); +var assocs = root.assocs; +out.println("Assoc Count: " + assocs.length); + +// test various access mechanisms +var childname1 = childList[0].name; +var childname2 = childList[0].properties.name +var childname3 = childList[0].properties["name"]; +var childname4 = childList[0].properties["cm:name"]; + +function result() +{ + return (childname1 == childname2 && childname2 == childname3 && childname3 == childname4); +} +result(); diff --git a/source/java/org/alfresco/service/cmr/repository/ScriptService.java b/source/java/org/alfresco/service/cmr/repository/ScriptService.java index 0586120787..fc8cf67762 100644 --- a/source/java/org/alfresco/service/cmr/repository/ScriptService.java +++ b/source/java/org/alfresco/service/cmr/repository/ScriptService.java @@ -19,6 +19,8 @@ package org.alfresco.service.cmr.repository; import java.io.Writer; import java.util.Map; +import org.alfresco.service.namespace.QName; + /** * Script Service. *

@@ -53,13 +55,15 @@ public interface ScriptService * Process a script against the supplied data model. * * @param scriptRef Script NodeRef location + * @param contentProp QName of the property on the node that contains the content, null can + * be passed to indicate the default property of 'cm:content' * @param model Object model to process script against * * @return output of the script (may be null or any valid wrapped JavaScript object) * * @throws ScriptException */ - public Object executeScript(NodeRef scriptRef, Map model) + public Object executeScript(NodeRef scriptRef, QName contentProp, Map model) throws ScriptException; /** diff --git a/source/java/org/alfresco/service/cmr/repository/TemplateImageResolver.java b/source/java/org/alfresco/service/cmr/repository/TemplateImageResolver.java index 7cb0878552..23fc78ba96 100644 --- a/source/java/org/alfresco/service/cmr/repository/TemplateImageResolver.java +++ b/source/java/org/alfresco/service/cmr/repository/TemplateImageResolver.java @@ -18,8 +18,8 @@ package org.alfresco.service.cmr.repository; /** * Interface contract for the conversion of file name to a fully qualified icon image path for use by - * the templating engine. - * + * templating and scripting engines executing within the repository context. + *

* Generally this contract will be implemented by classes that have access to say the webserver * context which can be used to generate an icon image for a specific filename. * diff --git a/source/java/org/alfresco/service/namespace/QName.java b/source/java/org/alfresco/service/namespace/QName.java index 61c9bf8ed2..64b7ab1db4 100644 --- a/source/java/org/alfresco/service/namespace/QName.java +++ b/source/java/org/alfresco/service/namespace/QName.java @@ -450,27 +450,30 @@ public final class QName implements QNamePattern, Serializable, Cloneable throw new IllegalArgumentException("str parameter is mandatory"); } - if (str.charAt(0) != NAMESPACE_BEGIN && str.indexOf(NAMESPACE_PREFIX) != -1) + if (str.charAt(0) != NAMESPACE_BEGIN) { - // get the prefix and resolve to the uri - int end = str.indexOf(NAMESPACE_PREFIX); - String prefix = str.substring(0, end); - String localName = str.substring(end + 1); - String uri = prefixResolver.getNamespaceURI(prefix); - - if (uri != null) + if (str.indexOf(NAMESPACE_PREFIX) != -1) { - result = new StringBuilder(64).append(NAMESPACE_BEGIN).append(uri).append(NAMESPACE_END).append( - localName).toString(); + // get the prefix and resolve to the uri + int end = str.indexOf(NAMESPACE_PREFIX); + String prefix = str.substring(0, end); + String localName = str.substring(end + 1); + String uri = prefixResolver.getNamespaceURI(prefix); + + if (uri != null) + { + result = new StringBuilder(64).append(NAMESPACE_BEGIN).append(uri).append(NAMESPACE_END) + .append(localName).toString(); + } + } + else + { + // there's no namespace so prefix with Alfresco's Content Model + result = new StringBuilder(64).append(NAMESPACE_BEGIN).append(NamespaceService.CONTENT_MODEL_1_0_URI) + .append(NAMESPACE_END).append(str).toString(); } } - else if (str.charAt(0) != NAMESPACE_BEGIN) - { - // there's no namespace so prefix with Alfresco's Content Model - result = new StringBuilder(64).append(NAMESPACE_BEGIN).append(NamespaceService.CONTENT_MODEL_1_0_URI) - .append(NAMESPACE_END).append(str).toString(); - } - + return result; } }