diff --git a/source/java/org/alfresco/repo/action/executer/LinkCategoryActionExecuter.java b/source/java/org/alfresco/repo/action/executer/LinkCategoryActionExecuter.java index 88a96a15c4..a4e15bbeb9 100644 --- a/source/java/org/alfresco/repo/action/executer/LinkCategoryActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/LinkCategoryActionExecuter.java @@ -130,12 +130,20 @@ public class LinkCategoryActionExecuter extends ActionExecuterAbstractBase { // Append the category value to the existing values Serializable value = this.nodeService.getProperty(actionedUponNodeRef, categoryProperty); - Collection categories = DefaultTypeConverter.INSTANCE.getCollection(NodeRef.class, value); - if (categories.contains(categoryValue) == false) + Collection categories = null; + if (value == null) { - categories.add(categoryValue); - this.nodeService.setProperty(actionedUponNodeRef, categoryProperty, (Serializable)categories); + categories = DefaultTypeConverter.INSTANCE.getCollection(NodeRef.class, categoryValue); } + else + { + categories = DefaultTypeConverter.INSTANCE.getCollection(NodeRef.class, value); + if (categories.contains(categoryValue) == false) + { + categories.add(categoryValue); + } + } + this.nodeService.setProperty(actionedUponNodeRef, categoryProperty, (Serializable)categories); } } } diff --git a/source/java/org/alfresco/repo/jscript/Node.java b/source/java/org/alfresco/repo/jscript/Node.java index 17acb5cbd7..3b000ff2fc 100644 --- a/source/java/org/alfresco/repo/jscript/Node.java +++ b/source/java/org/alfresco/repo/jscript/Node.java @@ -68,12 +68,10 @@ import org.springframework.util.StringUtils; /** * Node class implementation, specific for use by ScriptService as part of the object model. *

- * 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 property syntax, such as node.children[0].properties.name. + * 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 property 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. + * Various helper methods are provided to access common and useful node variables such as the content url and type information. * * @author Kevin Roast */ @@ -85,64 +83,87 @@ public class Node implements Serializable, Scopeable private static final long serialVersionUID = -3378946227712939600L; private static Log logger = LogFactory.getLog(Node.class); - + private final static String NAMESPACE_BEGIN = "" + QName.NAMESPACE_BEGIN; + private final static String CONTENT_DEFAULT_URL = "/download/direct/{0}/{1}/{2}/{3}"; - private final static String CONTENT_PROP_URL = "/download/direct/{0}/{1}/{2}/{3}?property={4}"; - private final static String FOLDER_BROWSE_URL = "/navigate/browse/{0}/{1}/{2}"; - + + private final static String CONTENT_PROP_URL = "/download/direct/{0}/{1}/{2}/{3}?property={4}"; + + private final static String FOLDER_BROWSE_URL = "/navigate/browse/{0}/{1}/{2}"; + /** Root scope for this object */ protected Scriptable scope; - + /** Node Value Converter */ protected NodeValueConverter converter = null; - + /** Cached values */ protected NodeRef nodeRef; + private String name; + private QName type; protected String id; + /** The aspects applied to this node */ private Set aspects = null; + /** The associations from this node */ private ScriptableQNameMap assocs = null; + /** The children of this node */ private Node[] children = null; + /** The properties of this node */ private ScriptableQNameMap properties = null; + protected ServiceRegistry services = null; + private NodeService nodeService = null; + private Boolean isDocument = null; + private Boolean isContainer = null; + private String displayPath = null; + protected TemplateImageResolver imageResolver = null; protected Node parent = null; + private ChildAssociationRef primaryParentAssoc = null; + // NOTE: see the reset() method when adding new cached members! - - + // ------------------------------------------------------------------------------ // Construction - + /** * Constructor * - * @param nodeRef The NodeRef this Node wrapper represents - * @param services The ServiceRegistry the Node can use to access services - * @param resolver Image resolver to use to retrieve icons + * @param nodeRef + * The NodeRef this Node wrapper represents + * @param services + * The ServiceRegistry the Node can use to access services + * @param resolver + * Image resolver to use to retrieve icons */ public Node(NodeRef nodeRef, ServiceRegistry services, TemplateImageResolver resolver) { this(nodeRef, services, resolver, null); } - + /** * Constructor * - * @param nodeRef The NodeRef this Node wrapper represents - * @param services The ServiceRegistry the Node can use to access services - * @param resolver Image resolver to use to retrieve icons - * @param scope Root scope for this Node + * @param nodeRef + * The NodeRef this Node wrapper represents + * @param services + * The ServiceRegistry the Node can use to access services + * @param resolver + * Image resolver to use to retrieve icons + * @param scope + * Root scope for this Node */ public Node(NodeRef nodeRef, ServiceRegistry services, TemplateImageResolver resolver, Scriptable scope) { @@ -150,12 +171,12 @@ public class Node implements Serializable, Scopeable { throw new IllegalArgumentException("NodeRef must be supplied."); } - + if (services == null) { throw new IllegalArgumentException("The ServiceRegistry must be supplied."); } - + this.nodeRef = nodeRef; this.id = nodeRef.getId(); this.services = services; @@ -163,7 +184,36 @@ public class Node implements Serializable, Scopeable this.imageResolver = resolver; this.scope = scope; } - + + @Override + public int hashCode() + { + final int PRIME = 31; + int result = 1; + result = PRIME * result + ((nodeRef == null) ? 0 : nodeRef.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final Node other = (Node) obj; + if (nodeRef == null) + { + if (other.nodeRef != null) + return false; + } + else if (!nodeRef.equals(other.nodeRef)) + return false; + return true; + } + /** * Factory method */ @@ -179,11 +229,10 @@ public class Node implements Serializable, Scopeable { this.scope = scope; } - - + // ------------------------------------------------------------------------------ - // Node Wrapper API - + // Node Wrapper API + /** * @return The GUID for the node */ @@ -191,12 +240,12 @@ public class Node implements Serializable, Scopeable { return this.id; } - + public String jsGet_id() { return getId(); } - + /** * @return Returns the NodeRef this Node object represents */ @@ -204,12 +253,12 @@ public class Node implements Serializable, Scopeable { return this.nodeRef; } - + public String jsGet_nodeRef() { return getNodeRef().toString(); } - + /** * @return Returns the type. */ @@ -219,15 +268,15 @@ public class Node implements Serializable, Scopeable { this.type = this.nodeService.getType(this.nodeRef); } - + return type; } - + public String jsGet_type() { return getType().toString(); } - + /** * @return Helper to return the 'name' property for the node */ @@ -236,8 +285,8 @@ public class Node implements Serializable, Scopeable if (this.name == null) { // try and get the name from the properties first - this.name = (String)getProperties().get("cm:name"); - + this.name = (String) getProperties().get("cm:name"); + // if we didn't find it as a property get the name from the association name if (this.name == null) { @@ -252,19 +301,20 @@ public class Node implements Serializable, Scopeable } } } - + return this.name; } - + public String jsGet_name() { return getName(); } - + /** * Helper to set the 'name' property for the node. * - * @param name Name to set + * @param name + * Name to set */ public void setName(String name) { @@ -273,12 +323,12 @@ public class Node implements Serializable, Scopeable this.getProperties().put(ContentModel.PROP_NAME.toString(), name.toString()); } } - + public void jsSet_name(String name) { setName(name); } - + /** * @return The children of this Node as Node wrappers */ @@ -288,7 +338,7 @@ public class Node implements Serializable, Scopeable { List childRefs = this.nodeService.getChildAssocs(this.nodeRef); this.children = new Node[childRefs.size()]; - for (int i=0; imynode.childByNamePath("/QA/Testing/Docs"); + * @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 Node childByNamePath(String path) { - // convert the name based path to a valid XPath query + // 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(); /**/) { @@ -319,43 +369,38 @@ public class Node implements Serializable, Scopeable { xpath.append('/'); } - xpath.append("*[@cm:name='") - .append(t.nextToken()) // TODO: use QueryParameterDefinition see FileFolderService.search() - .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 getChildByNamePath(path); - //} - + // public Node jsFunction_childByNamePath(String path) + // { + // return getChildByNamePath(path); + // } + /** - * @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 the Nodes at the specified XPath walking the children of this Node. So a valid call might be mynode.childrenByXPath("*[@cm:name='Testing']/*"); */ 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 childrenByXPath(xpath); - //} - + // public Node[] jsFunction_childrenByXPath(String xpath) + // { + // return childrenByXPath(xpath); + // } + /** - * Return the associations for this Node. As a Map of assoc name to an 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 the associations for this Node. As a Map of assoc name to an 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 associations as a Map of assoc name to an Array of Nodes. */ @@ -365,12 +410,12 @@ public class Node implements Serializable, Scopeable { // this Map implements the Scriptable interface for native JS syntax property access this.assocs = new ScriptableQNameMap(this.services.getNamespaceService()); - + 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); + Node[] nodes = (Node[]) this.assocs.get(qname); if (nodes == null) { // first access for the list for this qname @@ -388,21 +433,18 @@ public class Node implements Serializable, Scopeable this.assocs.put(ref.getTypeQName().toString(), nodes); } } - + return this.assocs; } - + public Map jsGet_assocs() { return getAssocs(); } - + /** - * 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 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. */ @@ -412,26 +454,26 @@ public class Node implements Serializable, Scopeable { // this Map implements the Scriptable interface for native JS syntax property access this.properties = new ScriptableQNameMap(this.services.getNamespaceService()); - + Map props = this.nodeService.getProperties(this.nodeRef); for (QName qname : props.keySet()) { Serializable propValue = props.get(qname); - + // perform the conversion to a script safe value and store - + this.properties.put(qname.toString(), getValueConverter().convertValueForScript(qname, propValue)); } } - + return this.properties; } - + public Map jsGet_properties() { return getProperties(); } - + /** * @return true if this Node is a container (i.e. a folder) */ @@ -440,18 +482,18 @@ public class Node implements Serializable, Scopeable if (isContainer == null) { DictionaryService dd = this.services.getDictionaryService(); - isContainer = Boolean.valueOf( (dd.isSubClass(getType(), ContentModel.TYPE_FOLDER) == true && - dd.isSubClass(getType(), ContentModel.TYPE_SYSTEM_FOLDER) == false) ); + isContainer = Boolean.valueOf((dd.isSubClass(getType(), ContentModel.TYPE_FOLDER) == true && dd.isSubClass( + getType(), ContentModel.TYPE_SYSTEM_FOLDER) == false)); } - + return isContainer.booleanValue(); } - + public boolean jsGet_isContainer() { return getIsContainer(); } - + /** * @return true if this Node is a Document (i.e. with content) */ @@ -462,15 +504,15 @@ public class Node implements Serializable, Scopeable DictionaryService dd = this.services.getDictionaryService(); isDocument = Boolean.valueOf(dd.isSubClass(getType(), ContentModel.TYPE_CONTENT)); } - + return isDocument.booleanValue(); } - + public boolean jsGet_isDocument() { return getIsDocument(); } - + /** * @return true if the Node is a Category */ @@ -479,12 +521,12 @@ public class Node implements Serializable, Scopeable // this valid is overriden by the CategoryNode sub-class return false; } - + public boolean jsGet_isCategory() { return getIsCategory(); } - + /** * @return The list of aspects applied to this node */ @@ -494,10 +536,10 @@ public class Node implements Serializable, Scopeable { this.aspects = this.nodeService.getAspects(this.nodeRef); } - + return this.aspects; } - + public String[] jsGet_aspects() { Set aspects = getAspects(); @@ -505,44 +547,43 @@ public class Node implements Serializable, Scopeable int count = 0; for (QName qname : aspects) { - result[count++] = qname.toString(); + result[count++] = qname.toString(); } return result; } - + /** - * @param aspect The aspect name to test for (full qualified or short-name form) - * + * @param aspect + * The aspect name to test for (full qualified or short-name form) * @return true if the node has the aspect false otherwise */ public boolean hasAspect(String aspect) { return getAspects().contains(createQName(aspect)); } - + /** * Return true if the user has the specified permission on the node. *

- * The default permissions are found in org.alfresco.service.cmr.security.PermissionService. - * Most commonly used are "Write", "Delete" and "AddChildren". - * - * @param permission as found in org.alfresco.service.cmr.security.PermissionService + * The default permissions are found in org.alfresco.service.cmr.security.PermissionService. Most commonly used are "Write", "Delete" and "AddChildren". * + * @param permission + * as found in org.alfresco.service.cmr.security.PermissionService * @return true if the user has the specified permission on the node. */ public boolean hasPermission(String permission) { boolean allowed = false; - + if (permission != null && permission.length() != 0) { AccessStatus status = this.services.getPermissionService().hasPermission(this.nodeRef, permission); allowed = (AccessStatus.ALLOWED == status); } - + return allowed; } - + /** * @return Display path to this node */ @@ -559,15 +600,15 @@ public class Node implements Serializable, Scopeable displayPath = ""; } } - + return displayPath; } - + public String jsGet_displayPath() { return getDisplayPath(); } - + /** * @return the small icon image for this node */ @@ -589,12 +630,12 @@ public class Node implements Serializable, Scopeable return "/images/filetypes/_default.gif"; } } - + public String jsGet_icon16() { return getIcon16(); } - + /** * @return the large icon image for this node */ @@ -608,7 +649,7 @@ public class Node implements Serializable, Scopeable } else { - String icon = (String)getProperties().get("app:icon"); + String icon = (String) getProperties().get("app:icon"); if (icon != null) { return "/images/icons/" + icon + ".gif"; @@ -624,19 +665,19 @@ public class Node implements Serializable, Scopeable return "/images/filetypes32/_default.gif"; } } - + public String jsGet_icon32() { return getIcon32(); } - + /** * @return true if the node is currently locked */ public boolean isLocked() { boolean locked = false; - + if (getAspects().contains(ContentModel.ASPECT_LOCKABLE)) { LockStatus lockStatus = this.services.getLockService().getLockStatus(this.nodeRef); @@ -645,15 +686,15 @@ public class Node implements Serializable, Scopeable locked = true; } } - + return locked; } - + public boolean jsGet_isLocked() { return isLocked(); } - + /** * @return the parent node */ @@ -668,17 +709,16 @@ public class Node implements Serializable, Scopeable parent = newInstance(parentRef, this.services, this.imageResolver, this.scope); } } - + 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. */ public ChildAssociationRef getPrimaryParentAssoc() @@ -689,46 +729,45 @@ public class Node implements Serializable, Scopeable } return primaryParentAssoc; } - + public ChildAssociationRef jsGet_primaryParentAssoc() { return getPrimaryParentAssoc(); } - - + // ------------------------------------------------------------------------------ // Content API - + /** - * @return the content String for this node from the default content property - * (@see ContentModel.PROP_CONTENT) + * @return the content String for this node from the default content property (@see ContentModel.PROP_CONTENT) */ public String getContent() { String content = ""; - - ScriptContentData contentData = (ScriptContentData)getProperties().get(ContentModel.PROP_CONTENT); + + ScriptContentData contentData = (ScriptContentData) getProperties().get(ContentModel.PROP_CONTENT); if (contentData != null) { content = contentData.getContent(); } - + return content; } - + public String jsGet_content() { return getContent(); } - + /** * Set the content for this node * - * @param content Content string to set + * @param content + * Content string to set */ public void setContent(String content) { - ScriptContentData contentData = (ScriptContentData)getProperties().get(ContentModel.PROP_CONTENT); + ScriptContentData contentData = (ScriptContentData) getProperties().get(ContentModel.PROP_CONTENT); if (contentData == null) { // guess a mimetype based on the filename @@ -739,15 +778,14 @@ public class Node implements Serializable, Scopeable } contentData.setContent(content); } - + public void jsSet_content(String content) { setContent(content); } - + /** - * @return For a content document, this method returns the URL to the content stream for - * the default content property (@see ContentModel.PROP_CONTENT) + * @return For a content document, this method returns the URL to the content stream for the default content property (@see ContentModel.PROP_CONTENT) *

* For a container node, this method return the URL to browse to the folder in the web-client */ @@ -755,95 +793,89 @@ public class Node implements Serializable, Scopeable { if (getIsDocument() == true) { - try - { - return MessageFormat.format(CONTENT_DEFAULT_URL, new Object[] { - nodeRef.getStoreRef().getProtocol(), - nodeRef.getStoreRef().getIdentifier(), - nodeRef.getId(), - StringUtils.replace(URLEncoder.encode(getName(), "UTF-8"), "+", "%20") } ); - } - catch (UnsupportedEncodingException err) - { - throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + nodeRef, err); - } + try + { + return MessageFormat.format(CONTENT_DEFAULT_URL, new Object[] { nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), nodeRef.getId(), + StringUtils.replace(URLEncoder.encode(getName(), "UTF-8"), "+", "%20") }); + } + catch (UnsupportedEncodingException err) + { + throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + nodeRef, err); + } } else { - return MessageFormat.format(FOLDER_BROWSE_URL, new Object[] { - nodeRef.getStoreRef().getProtocol(), - nodeRef.getStoreRef().getIdentifier(), - nodeRef.getId() } ); + return MessageFormat.format(FOLDER_BROWSE_URL, new Object[] { nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), nodeRef.getId() }); } } - + 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) + * @return The mimetype encoding for content attached to the node from the default content property (@see ContentModel.PROP_CONTENT) */ public String getMimetype() { String mimetype = null; - ScriptContentData content = (ScriptContentData)this.getProperties().get(ContentModel.PROP_CONTENT); + ScriptContentData content = (ScriptContentData) this.getProperties().get(ContentModel.PROP_CONTENT); if (content != null) { mimetype = content.getMimetype(); } - + return mimetype; } - + public String jsGet_mimetype() { return getMimetype(); } - + /** - * Set the mimetype encoding for the content attached to the node from the default content property - * (@see ContentModel.PROP_CONTENT) + * Set the mimetype encoding for the content attached to the node from the default content property (@see ContentModel.PROP_CONTENT) * - * @param mimetype Mimetype to set + * @param mimetype + * Mimetype to set */ public void setMimetype(String mimetype) { - ScriptContentData content = (ScriptContentData)this.getProperties().get(ContentModel.PROP_CONTENT); + ScriptContentData content = (ScriptContentData) this.getProperties().get(ContentModel.PROP_CONTENT); if (content != null) { content.setMimetype(mimetype); } } - + public void jsSet_mimetype(String mimetype) { setMimetype(mimetype); } - + /** - * @return The size in bytes of the content attached to the node from the default content property - * (@see ContentModel.PROP_CONTENT) + * @return The size in bytes of the content attached to the node from the default content property (@see ContentModel.PROP_CONTENT) */ public long getSize() { long size = 0; - ScriptContentData content = (ScriptContentData)this.getProperties().get(ContentModel.PROP_CONTENT); + ScriptContentData content = (ScriptContentData) this.getProperties().get(ContentModel.PROP_CONTENT); if (content != null) { size = content.getSize(); } - + return size; } - + public long jsGet_size() { return getSize(); } - + /** * @return the image resolver instance used by this node */ @@ -851,83 +883,94 @@ public class Node implements Serializable, Scopeable { return this.imageResolver; } - - + // ------------------------------------------------------------------------------ - // Security API - + // Security API + /** * @return true if the node inherits permissions from the parent node, false otherwise */ public boolean inheritsPermissions() { - return this.services.getPermissionService().getInheritParentPermissions(this.nodeRef); + return this.services.getPermissionService().getInheritParentPermissions(this.nodeRef); } - + /** * Set whether this node should inherit permissions from the parent node. * - * @param inherit True to inherit parent permissions, false otherwise. + * @param inherit + * True to inherit parent permissions, false otherwise. */ public void setInheritsPermissions(boolean inherit) { - this.services.getPermissionService().setInheritParentPermissions(this.nodeRef, inherit); + this.services.getPermissionService().setInheritParentPermissions(this.nodeRef, inherit); } - + /** * Apply a permission for ALL users to the node. * - * @param permission Permission to apply @see org.alfresco.service.cmr.security.PermissionService + * @param permission + * Permission to apply + * @see org.alfresco.service.cmr.security.PermissionService */ public void setPermission(String permission) { - this.services.getPermissionService().setPermission(this.nodeRef, PermissionService.ALL_AUTHORITIES, permission, true); + this.services.getPermissionService().setPermission(this.nodeRef, PermissionService.ALL_AUTHORITIES, permission, + true); } - + /** * Apply a permission for the specified authority (e.g. username or group) to the node. - * - * @param permission Permission to apply @see org.alfresco.service.cmr.security.PermissionService - * @param authority Authority (generally a username or group name) to apply the permission for + * + * @param permission + * Permission to apply + * @see org.alfresco.service.cmr.security.PermissionService + * @param authority + * Authority (generally a username or group name) to apply the permission for */ public void setPermission(String permission, String authority) { - this.services.getPermissionService().setPermission(this.nodeRef, authority, permission, true); + this.services.getPermissionService().setPermission(this.nodeRef, authority, permission, true); } - + /** * Remove a permission for ALL user from the node. * - * @param permission Permission to remove @see org.alfresco.service.cmr.security.PermissionService + * @param permission + * Permission to remove + * @see org.alfresco.service.cmr.security.PermissionService */ public void removePermission(String permission) { - this.services.getPermissionService().deletePermission(this.nodeRef, PermissionService.ALL_AUTHORITIES, permission); + this.services.getPermissionService().deletePermission(this.nodeRef, PermissionService.ALL_AUTHORITIES, + permission); } - + /** * Remove a permission for the specified authority (e.g. username or group) from the node. * - * @param permission Permission to remove @see org.alfresco.service.cmr.security.PermissionService - * @param authority Authority (generally a username or group name) to apply the permission for + * @param permission + * Permission to remove + * @see org.alfresco.service.cmr.security.PermissionService + * @param authority + * Authority (generally a username or group name) to apply the permission for */ public void removePermission(String permission, String authority) { - this.services.getPermissionService().deletePermission(this.nodeRef, authority, permission); + this.services.getPermissionService().deletePermission(this.nodeRef, authority, permission); } - - + // ------------------------------------------------------------------------------ // Ownership API - + /** - * Set the owner of the node + * Set the owner of the node */ public void setOwner(String userId) { this.services.getOwnableService().setOwner(this.nodeRef, userId); } - + /** * Take ownership of the node. */ @@ -935,16 +978,17 @@ public class Node implements Serializable, Scopeable { this.services.getOwnableService().takeOwnership(this.nodeRef); } - + /** * Get the owner of the node. + * * @return */ public String getOwner() { return this.services.getOwnableService().getOwner(this.nodeRef); } - + /** * Make owner available as a property. * @@ -954,10 +998,10 @@ public class Node implements Serializable, Scopeable { return getOwner(); } - + // ------------------------------------------------------------------------------ - // Create and Modify API - + // Create and Modify API + /** * Persist the properties of this Node. */ @@ -967,41 +1011,39 @@ public class Node implements Serializable, Scopeable Map props = new HashMap(getProperties().size()); for (String key : this.properties.keySet()) { - Serializable value = (Serializable)this.properties.get(key); - + Serializable value = (Serializable) this.properties.get(key); + // perform the conversion from script wrapper object to repo serializable values value = getValueConverter().convertValueForRepo(value); - + props.put(createQName(key), value); } this.nodeService.setProperties(this.nodeRef, props); } /** - * Re-sets the type of the node. Can be called in order specialise a node to a sub-type. - * - * This should be used with caution since calling it changes the type of the node and thus - * implies a different set of aspects, properties and associations. It is the responsibility - * of the caller to ensure that the node is in a approriate state after changing the type. - * - * @param type Type to specialize the node + * Re-sets the type of the node. Can be called in order specialise a node to a sub-type. This should be used with caution since calling it changes the type of the node and thus + * implies a different set of aspects, properties and associations. It is the responsibility of the caller to ensure that the node is in a approriate state after changing the + * type. * + * @param type + * Type to specialize the node * @return true if successful, false otherwise */ public boolean specializeType(String type) { QName qnameType = createQName(type); - + // Ensure that we are performing a specialise - if (getType().equals(qnameType) == false && - this.services.getDictionaryService().isSubClass(qnameType, getType()) == true) + if (getType().equals(qnameType) == false + && this.services.getDictionaryService().isSubClass(qnameType, getType()) == true) { // Specialise the type of the node try { this.nodeService.setType(this.nodeRef, qnameType); this.type = qnameType; - + return true; } catch (InvalidNodeRefException err) @@ -1011,26 +1053,26 @@ public class Node implements Serializable, Scopeable } return false; } - + /** * Create a new File (cm:content) node as a child of this node. *

* Once created the file should have content set using the content property. * - * @param name Name of the file to create - * + * @param name + * Name of the file to create * @return Newly created Node or null if failed to create. */ public Node createFile(String name) { Node node = null; - + try { if (name != null && name.length() != 0) { - FileInfo fileInfo = this.services.getFileFolderService().create( - this.nodeRef, name, ContentModel.TYPE_CONTENT); + FileInfo fileInfo = this.services.getFileFolderService().create(this.nodeRef, name, + ContentModel.TYPE_CONTENT); node = newInstance(fileInfo.getNodeRef(), this.services, this.imageResolver, this.scope); } } @@ -1043,27 +1085,27 @@ public class Node implements Serializable, Scopeable { // default of null will be returned } - + return node; } - + /** * Create a new folder (cm:folder) node as a child of this node. * - * @param name Name of the folder to create - * + * @param name + * Name of the folder to create * @return Newly created Node or null if failed to create. */ public Node createFolder(String name) { Node node = null; - + try { if (name != null && name.length() != 0) { - FileInfo fileInfo = this.services.getFileFolderService().create( - this.nodeRef, name, ContentModel.TYPE_FOLDER); + FileInfo fileInfo = this.services.getFileFolderService().create(this.nodeRef, name, + ContentModel.TYPE_FOLDER); node = newInstance(fileInfo.getNodeRef(), this.services, this.imageResolver, this.scope); } } @@ -1076,35 +1118,32 @@ public class Node implements Serializable, Scopeable { // default of null will be returned } - + return node; } - + /** * Create a new Node of the specified type as a child of this node. * - * @param name Name of the node to create - * @param type QName type (can either be fully qualified or short form such as 'cm:content') - * + * @param name + * Name of the node to create + * @param type + * QName type (can either be fully qualified or short form such as 'cm:content') * @return Newly created Node or null if failed to create. */ public Node createNode(String name, String type) { Node node = null; - + try { - if (name != null && name.length() != 0 && - type != null && type.length() != 0) + if (name != null && name.length() != 0 && type != null && type.length() != 0) { Map props = new HashMap(1); props.put(ContentModel.PROP_NAME, name); - ChildAssociationRef childAssocRef = this.nodeService.createNode( - this.nodeRef, - ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.ALFRESCO_URI, QName.createValidLocalName(name)), - createQName(type), - props); + ChildAssociationRef childAssocRef = this.nodeService.createNode(this.nodeRef, + ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.ALFRESCO_URI, QName + .createValidLocalName(name)), createQName(type), props); node = newInstance(childAssocRef.getChildRef(), this.services, this.imageResolver, this.scope); } } @@ -1112,23 +1151,23 @@ public class Node implements Serializable, Scopeable { // default of null will be returned } - + return node; } - + /** * Remove this node. Any references to this Node or its NodeRef should be discarded! */ public boolean remove() { boolean success = false; - + try { this.nodeService.deleteNode(this.nodeRef); - + reset(); - + success = true; } catch (AccessDeniedException accessErr) @@ -1139,45 +1178,41 @@ public class Node implements Serializable, Scopeable { // default of false will be returned } - + return success; } - + /** - * Copy this Node to a new parent destination. Note that children of the source - * Node are not copied. - * - * @param destination Node + * Copy this Node to a new parent destination. Note that children of the source Node are not copied. * + * @param destination + * Node * @return The newly copied Node instance or null if failed to copy. */ public Node copy(Node destination) { return copy(destination, false); } - + /** * Copy this Node and potentially all child nodes to a new parent destination. * - * @param destination Node - * @param deepCopy True for a deep copy, false otherwise. - * + * @param destination + * Node + * @param deepCopy + * True for a deep copy, false otherwise. * @return The newly copied Node instance or null if failed to copy. */ public Node copy(Node destination, boolean deepCopy) { Node copy = null; - + try { if (destination != null) { - NodeRef copyRef = this.services.getCopyService().copyAndRename( - this.nodeRef, - destination.getNodeRef(), - ContentModel.ASSOC_CONTAINS, - getPrimaryParentAssoc().getQName(), - deepCopy); + NodeRef copyRef = this.services.getCopyService().copyAndRename(this.nodeRef, destination.getNodeRef(), + ContentModel.ASSOC_CONTAINS, getPrimaryParentAssoc().getQName(), deepCopy); copy = newInstance(copyRef, this.services, this.imageResolver, this.scope); } } @@ -1189,34 +1224,31 @@ public class Node implements Serializable, Scopeable { // default of null will be returned } - + return copy; } - + /** * Move this Node to a new parent destination. * - * @param destination Node - * + * @param destination + * Node * @return true on successful move, false on failure to move. */ public boolean move(Node destination) { boolean success = false; - + try { if (destination != null) { - this.primaryParentAssoc = this.nodeService.moveNode( - this.nodeRef, - destination.getNodeRef(), - ContentModel.ASSOC_CONTAINS, - getPrimaryParentAssoc().getQName()); - + this.primaryParentAssoc = this.nodeService.moveNode(this.nodeRef, destination.getNodeRef(), + ContentModel.ASSOC_CONTAINS, getPrimaryParentAssoc().getQName()); + // reset cached values reset(); - + success = true; } } @@ -1228,36 +1260,35 @@ public class Node implements Serializable, Scopeable { // default of false will be returned } - + return success; } - + /** - * Add an aspect to the Node. As no properties are provided in this call, it can only be - * used to add aspects that do not require any mandatory properties. + * Add an aspect to the Node. As no properties are provided in this call, it can only be used to add aspects that do not require any mandatory properties. * - * @param type Type name of the aspect to add - * + * @param type + * Type name of the aspect to add * @return true if the aspect was added successfully, false if an error occured. */ public boolean addAspect(String type) { return addAspect(type, null); } - + /** * Add an aspect to the Node. * - * @param type Type name of the aspect to add - * @param props ScriptableObject (generally an assocative array) providing the named properties - * for the aspect - any mandatory properties for the aspect must be provided! - * + * @param type + * Type name of the aspect to add + * @param props + * ScriptableObject (generally an assocative array) providing the named properties for the aspect - any mandatory properties for the aspect must be provided! * @return true if the aspect was added successfully, false if an error occured. */ public boolean addAspect(String type, Object props) { boolean success = false; - + if (type != null && type.length() != 0) { try @@ -1265,33 +1296,33 @@ public class Node implements Serializable, Scopeable Map aspectProps = null; if (props instanceof ScriptableObject) { - ScriptableObject properties = (ScriptableObject)props; - + ScriptableObject properties = (ScriptableObject) props; + // we need to get all the keys to the properties provided // and convert them to a Map of QName to Serializable objects Object[] propIds = properties.getIds(); aspectProps = new HashMap(propIds.length); - for (int i=0; i model = FreeMarkerProcessor.buildDefaultModel(services, - ((Node)((Wrapper)scope.get("person", scope)).unwrap()).getNodeRef(), - ((Node)((Wrapper)scope.get("companyhome", scope)).unwrap()).getNodeRef(), - ((Node)((Wrapper)scope.get("userhome", scope)).unwrap()).getNodeRef(), - templateRef, + Map model = FreeMarkerProcessor.buildDefaultModel(services, ((Node) ((Wrapper) scope.get( + "person", scope)).unwrap()).getNodeRef(), ((Node) ((Wrapper) scope.get("companyhome", scope)).unwrap()) + .getNodeRef(), ((Node) ((Wrapper) scope.get("userhome", scope)).unwrap()).getNodeRef(), templateRef, this.imageResolver); - + // add the current node as either the document/space as appropriate if (this.getIsDocument()) { model.put("document", new TemplateNode(this.nodeRef, this.services, this.imageResolver)); - model.put("space", new TemplateNode(getPrimaryParentAssoc().getParentRef(), this.services, this.imageResolver)); + model.put("space", new TemplateNode(getPrimaryParentAssoc().getParentRef(), this.services, + this.imageResolver)); } else { model.put("space", new TemplateNode(this.nodeRef, this.services, this.imageResolver)); } - + // add the supplied args to the 'args' root object if (args != null) { @@ -1676,37 +1701,35 @@ public class Node implements Serializable, Scopeable // and convert them to a Map of QName to Serializable objects Object[] propIds = args.getIds(); Map templateArgs = new HashMap(propIds.length); - for (int i=0; i nodes = this.services.getSearchService().selectNodes( - this.nodeRef, - xpath, - null, - this.services.getNamespaceService(), - false); - + + List nodes = this.services.getSearchService().selectNodes(this.nodeRef, xpath, null, + this.services.getNamespaceService(), false); + // see if we only want the first result if (firstOnly == true) { @@ -1803,25 +1823,24 @@ public class Node implements Serializable, Scopeable else { result = new Node[nodes.size()]; - for (int i=0; i set = new LinkedHashSet (); // perform the search against the repo ResultSet results = null; @@ -208,13 +218,10 @@ public final class Search implements Scopeable if (results.length() != 0) { - nodes = new Node[results.length()]; - int count = 0; for (ResultSetRow row: results) { NodeRef nodeRef = row.getNodeRef(); - nodes[count] = new Node(nodeRef, services, this.imageResolver, this.scope); - count++; + set.add(new Node(nodeRef, services, this.imageResolver, this.scope)); } } } @@ -229,7 +236,7 @@ public final class Search implements Scopeable results.close(); } } - - return nodes != null ? nodes : new Node[0]; + + return set.toArray(new Node[(set.size())]); } } diff --git a/source/java/org/alfresco/repo/template/BaseSearchResultsMap.java b/source/java/org/alfresco/repo/template/BaseSearchResultsMap.java index bd07d6bb05..06442f47b7 100644 --- a/source/java/org/alfresco/repo/template/BaseSearchResultsMap.java +++ b/source/java/org/alfresco/repo/template/BaseSearchResultsMap.java @@ -18,6 +18,7 @@ package org.alfresco.repo.template; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; @@ -29,8 +30,7 @@ import org.alfresco.service.cmr.search.ResultSetRow; import org.alfresco.service.cmr.search.SearchService; /** - * Class providing the base Search Query services to execute a search returning a list of - * TemplateNode objects from a Lucene search string. + * Class providing the base Search Query services to execute a search returning a list of TemplateNode objects from a Lucene search string. * * @author Kevin Roast */ @@ -39,8 +39,10 @@ public abstract class BaseSearchResultsMap extends BaseTemplateMap /** * Constructor * - * @param parent The parent TemplateNode to execute searches from - * @param services The ServiceRegistry to use + * @param parent + * The parent TemplateNode to execute searches from + * @param services + * The ServiceRegistry to use */ public BaseSearchResultsMap(TemplateNode parent, ServiceRegistry services) { @@ -48,12 +50,13 @@ public abstract class BaseSearchResultsMap extends BaseTemplateMap } /** - * Perform a SearchService query with the given Lucene search string + * Perform a SearchService query with the given Lucene search string */ protected List query(String search) { List nodes = null; - + HashSet nodeRefs = new HashSet(); + // check if a full Lucene search string has been supplied or extracted from XML if (search != null && search.length() != 0) { @@ -61,18 +64,20 @@ public abstract class BaseSearchResultsMap extends BaseTemplateMap ResultSet results = null; try { - results = this.services.getSearchService().query( - this.parent.getNodeRef().getStoreRef(), - SearchService.LANGUAGE_LUCENE, - search); - + results = this.services.getSearchService().query(this.parent.getNodeRef().getStoreRef(), + SearchService.LANGUAGE_LUCENE, search); + if (results.length() != 0) { nodes = new ArrayList(results.length()); - for (ResultSetRow row: results) + for (ResultSetRow row : results) { NodeRef nodeRef = row.getNodeRef(); - nodes.add(new TemplateNode(nodeRef, services, this.parent.getImageResolver())); + if (!nodeRefs.contains(nodeRef)) + { + nodes.add(new TemplateNode(nodeRef, services, this.parent.getImageResolver())); + nodeRefs.add(nodeRef); + } } } } @@ -88,7 +93,7 @@ public abstract class BaseSearchResultsMap extends BaseTemplateMap } } } - - return nodes != null ? nodes : (List)Collections.emptyList(); + + return nodes != null ? nodes : (List) Collections.emptyList(); } }