From 6cadd8573a6ccd2c368a1a65be35f4399b76240f Mon Sep 17 00:00:00 2001 From: Kevin Roast Date: Wed, 6 Jun 2007 11:16:55 +0000 Subject: [PATCH] All arrays returned by JavaScript API calls (i.e. node.children) now returned as proper JavaScript native Array objects. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5866 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../alfresco/repo/jscript/Classification.java | 20 +-- .../java/org/alfresco/repo/jscript/Node.java | 129 ++++++++++-------- .../org/alfresco/repo/jscript/People.java | 38 +++--- .../org/alfresco/repo/jscript/Search.java | 98 +++++++------ .../repo/template/NodeSearchResultsMap.java | 2 +- 5 files changed, 164 insertions(+), 123 deletions(-) diff --git a/source/java/org/alfresco/repo/jscript/Classification.java b/source/java/org/alfresco/repo/jscript/Classification.java index 116f02103a..3d1d629c4f 100644 --- a/source/java/org/alfresco/repo/jscript/Classification.java +++ b/source/java/org/alfresco/repo/jscript/Classification.java @@ -31,6 +31,8 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.CategoryService; import org.alfresco.service.namespace.QName; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; /** * Support class for finding categories, finding root nodes for categories and creating root categories. @@ -69,10 +71,11 @@ public final class Classification extends BaseScopableProcessorExtension * @param aspect * @return */ - public CategoryNode[] getAllCategoryNodes(String aspect) + public Scriptable getAllCategoryNodes(String aspect) { - return buildCategoryNodes(services.getCategoryService().getCategories(storeRef, createQName(aspect), - CategoryService.Depth.ANY)); + Object[] cats = buildCategoryNodes(services.getCategoryService().getCategories( + storeRef, createQName(aspect), CategoryService.Depth.ANY)); + return Context.getCurrentContext().newArray(getScope(), cats); } /** @@ -114,14 +117,16 @@ public final class Classification extends BaseScopableProcessorExtension * @param aspect * @return */ - public CategoryNode[] getRootCategories(String aspect) + public Scriptable getRootCategories(String aspect) { - return buildCategoryNodes(services.getCategoryService().getRootCategories(storeRef, createQName(aspect))); + Object[] cats = buildCategoryNodes(services.getCategoryService().getRootCategories( + storeRef, createQName(aspect))); + return Context.getCurrentContext().newArray(getScope(), cats); } - private CategoryNode[] buildCategoryNodes(Collection cars) + private Object[] buildCategoryNodes(Collection cars) { - CategoryNode[] categoryNodes = new CategoryNode[cars.size()]; + Object[] categoryNodes = new Object[cars.size()]; int i = 0; for (ChildAssociationRef car : cars) { @@ -143,5 +148,4 @@ public final class Classification extends BaseScopableProcessorExtension } return qname; } - } diff --git a/source/java/org/alfresco/repo/jscript/Node.java b/source/java/org/alfresco/repo/jscript/Node.java index 7f195937da..7ac0779c37 100644 --- a/source/java/org/alfresco/repo/jscript/Node.java +++ b/source/java/org/alfresco/repo/jscript/Node.java @@ -28,6 +28,7 @@ import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -70,6 +71,7 @@ import org.alfresco.util.GUID; import org.alfresco.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.mozilla.javascript.Context; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.ScriptableObject; import org.mozilla.javascript.Wrapper; @@ -113,13 +115,13 @@ public class Node implements Serializable, Scopeable private Set aspects = null; /** The target associations for this node */ - private ScriptableQNameMap assocs = null; + private ScriptableQNameMap assocs = null; /** The child associations for this node */ - private ScriptableQNameMap childAssocs = null; + private ScriptableQNameMap childAssocs = null; /** The children of this node */ - private Node[] children = null; + private Scriptable children = null; /** The properties of this node */ private ScriptableQNameMap properties = null; @@ -330,26 +332,26 @@ public class Node implements Serializable, Scopeable } /** - * @return The children of this Node as Node wrappers + * @return The children of this Node as JavaScript array of Node object wrappers */ - public Node[] getChildren() + public Scriptable getChildren() { if (this.children == null) { List childRefs = this.nodeService.getChildAssocs(this.nodeRef); - this.children = new Node[childRefs.size()]; + Object[] children = new Object[childRefs.size()]; for (int i = 0; i < childRefs.size(); i++) { // create our Node representation from the NodeRef - Node child = newInstance(childRefs.get(i).getChildRef(), this.services, this.scope); - this.children[i] = child; + children[i] = newInstance(childRefs.get(i).getChildRef(), this.services, this.scope); } + this.children = Context.getCurrentContext().newArray(this.scope, children); } return this.children; } - public Node[] jsGet_children() + public Scriptable jsGet_children() { return getChildren(); } @@ -386,9 +388,9 @@ public class Node implements Serializable, Scopeable t.nextToken()); } - Node[] nodes = getChildrenByXPath(xpath.toString(), params, true); + Object[] nodes = getChildrenByXPath(xpath.toString(), params, true); - return (nodes.length != 0) ? nodes[0] : null; + return (nodes.length != 0) ? (Node)nodes[0] : null; } // TODO: find out why this doesn't work - the function defs do not seem to get found @@ -398,101 +400,116 @@ public class Node implements Serializable, Scopeable // } /** - * @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']/*"); + * @return Returns a JavaScript array of Nodes at the specified XPath starting at this Node. + * So a valid call might be mynode.childrenByXPath("*[@cm:name='Testing']/*"); */ - public Node[] childrenByXPath(String xpath) + public Scriptable childrenByXPath(String xpath) { - return getChildrenByXPath(xpath, null, false); + return Context.getCurrentContext().newArray(this.scope, getChildrenByXPath(xpath, null, false)); } /** - * Return the target associations from this Node. As a Map of assoc name to an Array of Nodes. + * Return the target associations from this Node. As a Map of assoc name to a JavaScript array of Nodes. * The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript * associative array access. This means associations of this node can be access thus: * node.assocs["translations"][0] * - * @return target associations as a Map of assoc name to an Array of Nodes. + * @return target associations as a Map of assoc name to a JavaScript array of Nodes. */ @SuppressWarnings("unchecked") - public Map getAssocs() + public Map getAssocs() { if (this.assocs == null) { // this Map implements the Scriptable interface for native JS syntax property access - this.assocs = new ScriptableQNameMap(this.services.getNamespaceService()); - + this.assocs = new ScriptableQNameMap(this.services.getNamespaceService()); + + // get the list of target nodes for each association type List refs = this.nodeService.getTargetAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL); for (AssociationRef ref : refs) { String qname = ref.getTypeQName().toString(); - Node[] nodes = (Node[]) this.assocs.get(qname); + List nodes = (List)this.assocs.get(qname); if (nodes == null) { - // first access for the list for this qname - nodes = new Node[1]; + // first access of the list for this qname + nodes = new ArrayList(4); + this.assocs.put(ref.getTypeQName().toString(), nodes); } - else - { - Node[] newNodes = new Node[nodes.length + 1]; - System.arraycopy(nodes, 0, newNodes, 0, nodes.length); - nodes = newNodes; - } - nodes[nodes.length - 1] = newInstance(ref.getTargetRef(), this.services, this.scope); - - this.assocs.put(ref.getTypeQName().toString(), nodes); + nodes.add(newInstance(ref.getTargetRef(), this.services, this.scope)); + } + + // convert each Node list into a JavaScript array object + for (String qname : this.assocs.keySet()) + { + List nodes = (List)this.assocs.get(qname); + Object[] objs = nodes.toArray(new Object[nodes.size()]); + this.assocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs)); } } - + return this.assocs; } - public Map jsGet_assocs() + public Map jsGet_assocs() { return getAssocs(); } + public Map jsGet_associations() + { + return getAssocs(); + } + /** - * Return the child associations from this Node. As a Map of assoc name to an Array of Nodes. + * Return the child associations from this Node. As a Map of assoc name to a JavaScript array of Nodes. * The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript * associative array access. This means associations of this node can be access thus: * node.childAssocs["contains"][0] * - * @return child associations as a Map of assoc name to an Array of Nodes. + * @return child associations as a Map of assoc name to a JavaScript array of Nodes. */ @SuppressWarnings("unchecked") - public Map getChildAssocs() + public Map getChildAssocs() { if (this.childAssocs == null) { // this Map implements the Scriptable interface for native JS syntax property access - this.childAssocs = new ScriptableQNameMap(this.services.getNamespaceService()); + this.childAssocs = new ScriptableQNameMap(this.services.getNamespaceService()); + // get the list of child assoc nodes for each association type List refs = this.nodeService.getChildAssocs(nodeRef); for (ChildAssociationRef ref : refs) { String qname = ref.getTypeQName().toString(); - Node[] nodes = (Node[]) this.childAssocs.get(qname); + List nodes = (List)this.childAssocs.get(qname); if (nodes == null) { - // first access for the list for this qname - nodes = new Node[1]; + // first access of the list for this qname + nodes = new ArrayList(4); + this.childAssocs.put(ref.getTypeQName().toString(), nodes); } - else - { - Node[] newNodes = new Node[nodes.length + 1]; - System.arraycopy(nodes, 0, newNodes, 0, nodes.length); - nodes = newNodes; - } - nodes[nodes.length - 1] = newInstance(ref.getChildRef(), this.services, this.scope); - - this.childAssocs.put(ref.getTypeQName().toString(), nodes); + nodes.add(newInstance(ref.getChildRef(), this.services, this.scope)); + } + + // convert each Node list into a JavaScript array object + for (String qname : this.childAssocs.keySet()) + { + List nodes = (List)this.childAssocs.get(qname); + Object[] objs = nodes.toArray(new Object[nodes.size()]); + this.childAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs)); } } return this.childAssocs; } - public Map jsGet_childAssocs() + public Map jsGet_childAssocs() + { + return getChildAssocs(); + } + + public Map jsGet_childAssociations() { return getChildAssocs(); } @@ -1865,11 +1882,11 @@ public class Node implements Serializable, Scopeable * @param xpath XPath to execute * @param firstOnly True to return the first result only * - * @return Node[] can be empty but never null + * @return Object[] can be empty but never null */ - private Node[] getChildrenByXPath(String xpath, QueryParameterDefinition[] params, boolean firstOnly) + private Object[] getChildrenByXPath(String xpath, QueryParameterDefinition[] params, boolean firstOnly) { - Node[] result = null; + Object[] result = null; if (xpath.length() != 0) { @@ -1883,14 +1900,14 @@ public class Node implements Serializable, Scopeable { if (nodes.size() != 0) { - result = new Node[1]; + result = new Object[1]; result[0] = newInstance(nodes.get(0), this.services, this.scope); } } // or all the results else { - result = new Node[nodes.size()]; + result = new Object[nodes.size()]; for (int i = 0; i < nodes.size(); i++) { NodeRef ref = nodes.get(i); @@ -1899,7 +1916,7 @@ public class Node implements Serializable, Scopeable } } - return result != null ? result : new Node[0]; + return result != null ? result : new Object[0]; } diff --git a/source/java/org/alfresco/repo/jscript/People.java b/source/java/org/alfresco/repo/jscript/People.java index 3b19359997..a10160c886 100644 --- a/source/java/org/alfresco/repo/jscript/People.java +++ b/source/java/org/alfresco/repo/jscript/People.java @@ -34,6 +34,8 @@ import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.util.ParameterCheck; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; /** * Scripted People service for describing and executing actions against People & Groups. @@ -226,12 +228,13 @@ public final class People extends BaseScopableProcessorExtension * @param group the group to retrieve members for * @param recurse recurse into sub-groups * - * @return the members of the group + * @return members of the group as a JavaScript array */ - public Node[] getMembers(Node group) + public Scriptable getMembers(Node group) { ParameterCheck.mandatory("Group", group); - return getContainedAuthorities(group, AuthorityType.USER, true); + Object[] members = getContainedAuthorities(group, AuthorityType.USER, true); + return Context.getCurrentContext().newArray(getScope(), members); } /** @@ -240,12 +243,13 @@ public final class People extends BaseScopableProcessorExtension * @param group the group to retrieve members for * @param recurse recurse into sub-groups * - * @return the members of the group + * @return the members of the group as a JavaScript array */ - public Node[] getMembers(Node group, boolean recurse) + public Scriptable getMembers(Node group, boolean recurse) { ParameterCheck.mandatory("Group", group); - return getContainedAuthorities(group, AuthorityType.USER, recurse); + Object[] members = getContainedAuthorities(group, AuthorityType.USER, recurse); + return Context.getCurrentContext().newArray(getScope(), members); } /** @@ -253,17 +257,17 @@ public final class People extends BaseScopableProcessorExtension * * @param person the user (cm:person) to get the containing groups for * - * @return the containing groups, can be null + * @return the containing groups as a JavaScript array, can be null */ - public Node[] getContainerGroups(Node person) + public Scriptable getContainerGroups(Node person) { ParameterCheck.mandatory("Person", person); - Node[] parents = null; + Object[] parents = null; Set authorities = this.authorityService.getContainingAuthorities( AuthorityType.GROUP, (String)person.getProperties().get(ContentModel.PROP_USERNAME), false); - parents = new Node[authorities.size()]; + parents = new Object[authorities.size()]; int i = 0; for (String authority : authorities) { @@ -273,7 +277,7 @@ public final class People extends BaseScopableProcessorExtension parents[i++] = group; } } - return parents; + return Context.getCurrentContext().newArray(getScope(), parents); } /** @@ -283,16 +287,17 @@ public final class People extends BaseScopableProcessorExtension * @param type authority type to filter by * @param recurse recurse into sub-containers * - * @return contained authorities or null if none found + * @return contained authorities */ - private Node[] getContainedAuthorities(Node container, AuthorityType type, boolean recurse) + private Object[] getContainedAuthorities(Node container, AuthorityType type, boolean recurse) { - Node[] members = null; + Object[] members = null; + if (container.getType().equals(ContentModel.TYPE_AUTHORITY_CONTAINER)) { String groupName = (String)container.getProperties().get(ContentModel.PROP_AUTHORITY_NAME); Set authorities = authorityService.getContainedAuthorities(type, groupName, !recurse); - members = new Node[authorities.size()]; + members = new Object[authorities.size()]; int i = 0; for (String authority : authorities) { @@ -315,6 +320,7 @@ public final class People extends BaseScopableProcessorExtension } } } - return members; + + return members != null ? members : new Object[0]; } } diff --git a/source/java/org/alfresco/repo/jscript/Search.java b/source/java/org/alfresco/repo/jscript/Search.java index 0f2933600f..a1c4d5a635 100644 --- a/source/java/org/alfresco/repo/jscript/Search.java +++ b/source/java/org/alfresco/repo/jscript/Search.java @@ -40,6 +40,8 @@ import org.alfresco.service.cmr.search.SearchService; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; /** * Search component for use by the ScriptService. @@ -58,10 +60,10 @@ public final class Search extends BaseScopableProcessorExtension { /** Service registry */ private ServiceRegistry services; - + /** Default store reference */ private StoreRef storeRef; - + /** * Set the default store reference * @@ -71,7 +73,7 @@ public final class Search extends BaseScopableProcessorExtension { this.storeRef = new StoreRef(storeRef); } - + /** * Set the service registry * @@ -81,7 +83,7 @@ public final class Search extends BaseScopableProcessorExtension { this.services = services; } - + /** * Find a single Node by the Node reference * @@ -93,7 +95,7 @@ public final class Search extends BaseScopableProcessorExtension { return findNode(ref.toString()); } - + /** * Find a single Node by the Node reference * @@ -104,80 +106,82 @@ public final class Search extends BaseScopableProcessorExtension public Node findNode(String ref) { String query = "ID:" + LuceneQueryParser.escape(ref); - Node[] result = query(query, SearchService.LANGUAGE_LUCENE); - if (result.length == 1) + Object[] result = query(query, SearchService.LANGUAGE_LUCENE); + if (result.length != 0) { - return result[0]; + return (Node)result[0]; } else { return null; } } - + /** * Execute a XPath search * * @param search XPath search string to execute * - * @return Node[] of results from the search - can be empty but not null + * @return JavaScript array of Node results from the search - can be empty but not null */ - public Node[] xpathSearch(String search) + public Scriptable xpathSearch(String search) { if (search != null && search.length() != 0) { - return query(search, SearchService.LANGUAGE_XPATH); + Object[] results = query(search, SearchService.LANGUAGE_XPATH); + return Context.getCurrentContext().newArray(getScope(), results); } else { - return new Node[0]; + return Context.getCurrentContext().newArray(getScope(), 0); } } - + /** * Execute a Lucene search * * @param search Lucene search string to execute * - * @return Node[] of results from the search - can be empty but not null + * @return JavaScript array of Node results from the search - can be empty but not null */ - public Node[] luceneSearch(String search) + public Scriptable luceneSearch(String search) { if (search != null && search.length() != 0) { - return query(search, SearchService.LANGUAGE_LUCENE); + Object[] results = query(search, SearchService.LANGUAGE_LUCENE); + return Context.getCurrentContext().newArray(getScope(), results); } else { - return new Node[0]; + return Context.getCurrentContext().newArray(getScope(), 0); } } - + /** * Execute a saved Lucene search * * @param savedSearch Node that contains the saved search XML content * - * @return Node[] of results from the search - can be empty but not null + * @return JavaScript array of Node results from the search - can be empty but not null */ - public Node[] savedSearch(Node savedSearch) + public Scriptable savedSearch(Node savedSearch) { String search = null; - + // read the Saved Search XML on the specified node - and get the Lucene search from it try { if (savedSearch != null) { ContentReader content = this.services.getContentService().getReader( - savedSearch.getNodeRef(), ContentModel.PROP_CONTENT); + savedSearch.getNodeRef(), ContentModel.PROP_CONTENT); if (content != null && content.exists()) { // get the root element SAXReader reader = new SAXReader(); Document document = reader.read(new StringReader(content.getContentString())); Element rootElement = document.getRootElement(); - + Element queryElement = rootElement.element("query"); if (queryElement != null) { @@ -191,17 +195,25 @@ public final class Search extends BaseScopableProcessorExtension throw new AlfrescoRuntimeException("Failed to find or load saved Search: " + savedSearch.getNodeRef(), err); } - return search != null ? query(search, SearchService.LANGUAGE_LUCENE) : new Node[0]; + if (search != null) + { + Object[] results = query(search, SearchService.LANGUAGE_LUCENE); + return Context.getCurrentContext().newArray(getScope(), results); + } + else + { + return Context.getCurrentContext().newArray(getScope(), 0); + } } - + /** * Execute a saved Lucene search * * @param searchRef NodeRef string that points to the node containing saved search XML content * - * @return Node[] of results from the search - can be empty but not null + * @return JavaScript array of Node results from the search - can be empty but not null */ - public Node[] savedSearch(String searchRef) + public Scriptable savedSearch(String searchRef) { if (searchRef != null) { @@ -209,37 +221,39 @@ public final class Search extends BaseScopableProcessorExtension } else { - return new Node[0]; + return Context.getCurrentContext().newArray(getScope(), 0); } } /** * Execute the query * - * Removes any duplicates that may be present (ID can cause duplicates - it is better to remove them here) + * Removes any duplicates that may be present (ID search can cause duplicates - it is better to remove them here) * - * @param search - * @return + * @param search Lucene search to execute + * @param language Search language to use e.g. SearchService.LANGUAGE_LUCENE + * + * @return Array of Node objects */ - private Node[] query(String search, String language) + private Object[] query(String search, String language) { - LinkedHashSet set = new LinkedHashSet (); - + LinkedHashSet set = new LinkedHashSet(); + // perform the search against the repo ResultSet results = null; try { results = this.services.getSearchService().query( - this.storeRef, - language, - search); - + this.storeRef, + language, + search); + if (results.length() != 0) { for (ResultSetRow row: results) { NodeRef nodeRef = row.getNodeRef(); - set.add(new Node(nodeRef, services, getScope())); + set.add(new Node(nodeRef, this.services, getScope())); } } } @@ -254,7 +268,7 @@ public final class Search extends BaseScopableProcessorExtension results.close(); } } - - return set.toArray(new Node[(set.size())]); + + return set.toArray(new Object[(set.size())]); } } diff --git a/source/java/org/alfresco/repo/template/NodeSearchResultsMap.java b/source/java/org/alfresco/repo/template/NodeSearchResultsMap.java index 3c16b7e69b..985516478d 100644 --- a/source/java/org/alfresco/repo/template/NodeSearchResultsMap.java +++ b/source/java/org/alfresco/repo/template/NodeSearchResultsMap.java @@ -59,7 +59,7 @@ public class NodeSearchResultsMap extends BaseSearchResultsMap List results = query(ref); - if (results.size() == 1) + if (results.size() != 0) { result = results.get(0); }