diff --git a/config/alfresco/script-services-context.xml b/config/alfresco/script-services-context.xml index 97978a58ca..6466d0ef6d 100644 --- a/config/alfresco/script-services-context.xml +++ b/config/alfresco/script-services-context.xml @@ -90,7 +90,7 @@ - + avm @@ -99,4 +99,13 @@ + + + crossRepoCopy + + + + + + diff --git a/source/java/org/alfresco/repo/copy/CrossRepositoryCopyServiceImpl.java b/source/java/org/alfresco/repo/copy/CrossRepositoryCopyServiceImpl.java index 6eb783a325..481105d55b 100644 --- a/source/java/org/alfresco/repo/copy/CrossRepositoryCopyServiceImpl.java +++ b/source/java/org/alfresco/repo/copy/CrossRepositoryCopyServiceImpl.java @@ -20,6 +20,7 @@ import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.CopyService; import org.alfresco.service.cmr.repository.CrossRepositoryCopyService; @@ -117,24 +118,24 @@ public class CrossRepositoryCopyServiceImpl implements { StoreRef srcStoreRef = src.getStoreRef(); StoreRef dstStoreRef = dst.getStoreRef(); - if (srcStoreRef.getProtocol().equals("avm")) + if (srcStoreRef.getProtocol().equals(StoreRef.PROTOCOL_AVM)) { - if (dstStoreRef.getProtocol().equals("avm")) + if (dstStoreRef.getProtocol().equals(StoreRef.PROTOCOL_AVM)) { copyAVMToAVM(src, dst, name); } - else + else if (dstStoreRef.getProtocol().equals(StoreRef.PROTOCOL_WORKSPACE)) { copyAVMToRepo(src, dst, name); } } - else + else if (srcStoreRef.getProtocol().equals(StoreRef.PROTOCOL_WORKSPACE)) { - if (dstStoreRef.getProtocol().equals("avm")) + if (dstStoreRef.getProtocol().equals(StoreRef.PROTOCOL_AVM)) { copyRepoToAVM(src, dst, name); } - else + else if (dstStoreRef.getProtocol().equals(StoreRef.PROTOCOL_WORKSPACE)) { copyRepoToRepo(src, dst, name); } @@ -277,6 +278,8 @@ public class CrossRepositoryCopyServiceImpl implements */ private void copyRepoToRepo(NodeRef src, NodeRef dst, String name) { + ChildAssociationRef assocRef = fNodeService.getPrimaryParent(src); + fCopyService.copyAndRename(src, dst, ContentModel.ASSOC_CONTAINS, assocRef.getQName(), true); } private void copyData(InputStream in, OutputStream out) diff --git a/source/java/org/alfresco/repo/jscript/AVMNode.java b/source/java/org/alfresco/repo/jscript/AVMNode.java index 441aa1d1ab..1822299d07 100644 --- a/source/java/org/alfresco/repo/jscript/AVMNode.java +++ b/source/java/org/alfresco/repo/jscript/AVMNode.java @@ -19,7 +19,8 @@ package org.alfresco.repo.jscript; import org.alfresco.repo.avm.AVMNodeConverter; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.util.Pair; +import org.alfresco.util.ParameterCheck; import org.mozilla.javascript.Scriptable; /** @@ -31,6 +32,7 @@ import org.mozilla.javascript.Scriptable; public class AVMNode extends Node { private String path; + private int version; /** * Constructor @@ -41,8 +43,7 @@ public class AVMNode extends Node */ public AVMNode(NodeRef nodeRef, ServiceRegistry services) { - super(nodeRef, services); - this.path = AVMNodeConverter.ToAVMVersionPath(nodeRef).getSecond(); + this(nodeRef, services, null); } /** @@ -56,11 +57,13 @@ public class AVMNode extends Node public AVMNode(NodeRef nodeRef, ServiceRegistry services, Scriptable scope) { super(nodeRef, services, scope); - this.path = AVMNodeConverter.ToAVMVersionPath(nodeRef).getSecond(); + Pair versionPath = AVMNodeConverter.ToAVMVersionPath(nodeRef); + this.path = versionPath.getSecond(); + this.version = versionPath.getFirst(); } /** - * Factory method + * Factory methods */ @Override public Node newInstance(NodeRef nodeRef, ServiceRegistry services, Scriptable scope) @@ -68,6 +71,11 @@ public class AVMNode extends Node return new AVMNode(nodeRef, services, scope); } + public Node newInstance(String path, int version, ServiceRegistry services, Scriptable scope) + { + return new AVMNode(AVMNodeConverter.ToNodeRef(version, path), services, scope); + } + // TODO: changing the 'name' property (either directly using .name or with .properties.name) // invalidates the path and the base noderef instance! // AVMService has a specific rename method - use this and block name property changes? @@ -85,6 +93,16 @@ public class AVMNode extends Node return getPath(); } + public int getVersion() + { + return this.version; + } + + public int jsGet_version() + { + return getVersion(); + } + /** * Copy this Node into a new parent destination. * @@ -95,14 +113,9 @@ public class AVMNode extends Node @Override public Node copy(Node destination) { - Node copy = null; + ParameterCheck.mandatory("Destination Node", destination); - if (destination instanceof AVMNode) - { - copy = copy(((AVMNode)destination).getPath()); - } - - return copy; + return getCrossRepositoryCopyHelper().copy(this, destination, getName()); } /** @@ -114,17 +127,12 @@ public class AVMNode extends Node */ public Node copy(String destination) { - Node copy = null; + ParameterCheck.mandatoryString("Destination Path", destination); - if (destination != null && destination.length() != 0) - { - this.services.getAVMService().copy(-1, getPath(), destination, getName()); - copy = newInstance( - AVMNodeConverter.ToNodeRef(-1, destination + '/' + getName()), - this.services, this.scope); - } - - return copy; + this.services.getAVMService().copy(this.version, this.path, destination, getName()); + return newInstance( + AVMNodeConverter.ToNodeRef(-1, AVMNodeConverter.ExtendAVMPath(destination, getName())), + this.services, this.scope); } /** @@ -137,6 +145,8 @@ public class AVMNode extends Node @Override public boolean move(Node destination) { + ParameterCheck.mandatory("Destination Node", destination); + boolean success = false; if (destination instanceof AVMNode) @@ -156,6 +166,8 @@ public class AVMNode extends Node */ public boolean move(String destination) { + ParameterCheck.mandatoryString("Destination Path", destination); + boolean success = false; if (destination != null && destination.length() != 0) @@ -164,7 +176,7 @@ public class AVMNode extends Node this.services.getAVMService().rename( parent.getPath(), getName(), destination, getName()); - reset(destination + '/' + getName()); + reset(AVMNodeConverter.ExtendAVMPath(destination, getName())); success = true; } @@ -181,6 +193,8 @@ public class AVMNode extends Node */ public boolean rename(String name) { + ParameterCheck.mandatoryString("Destination name", name); + boolean success = false; if (name != null && name.length() != 0) @@ -189,7 +203,7 @@ public class AVMNode extends Node this.services.getAVMService().rename( parentPath, getName(), parentPath, name); - reset(parentPath + '/' + name); + reset(AVMNodeConverter.ExtendAVMPath(parentPath, name)); success = true; } @@ -204,14 +218,14 @@ public class AVMNode extends Node { super.reset(); this.path = path; - this.nodeRef = AVMNodeConverter.ToNodeRef(-1, path); + this.nodeRef = AVMNodeConverter.ToNodeRef(version, path); this.id = nodeRef.getId(); } @Override public String toString() { - if (this.services.getAVMService().lookup(-1, this.path) != null) + if (this.services.getAVMService().lookup(version, this.path) != null) { return "AVM Path: " + getPath() + "\nNode Type: " + getType() + diff --git a/source/java/org/alfresco/repo/jscript/CrossRepositoryCopy.java b/source/java/org/alfresco/repo/jscript/CrossRepositoryCopy.java new file mode 100644 index 0000000000..1d227a192d --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/CrossRepositoryCopy.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.jscript; + +import org.alfresco.repo.avm.AVMNodeConverter; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.avm.AVMNodeDescriptor; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.util.Pair; +import org.alfresco.util.ParameterCheck; +import org.mozilla.javascript.Scriptable; + +/** + * Helper bean to access Cross Repository copy services from a script context. + * + * @author Kevin Roast + */ +public final class CrossRepositoryCopy extends BaseScriptImplementation implements Scopeable +{ + public final static String BEAN_NAME = "crossCopyScript"; + + /** Service registry */ + private ServiceRegistry services; + + /** Root scope for this object */ + private Scriptable scope; + + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; + } + + /** + * Set the service registry + * + * @param services the service registry + */ + public void setServiceRegistry(ServiceRegistry services) + { + this.services = services; + } + + /** + * Perform a copy of a source node to the specified parent destination node. The name will + * be applied to the destination node copy. + *

+ * Inter-store copy operations between Workspace and AVM and visa-versa are supported. + * + * @param src Source node instance + * @param dest Destination parent node instance + * @param name Name of the node copy + * + * @return node representing the copy if successful, null on unsupported store type. + * + * @throws org.alfresco.error.AlfrescoRuntimeException on copy error + */ + public Node copy(Node src, Node dest, String name) + { + ParameterCheck.mandatory("Node source", src); + ParameterCheck.mandatory("Node destination", dest); + ParameterCheck.mandatory("Node destination name", name); + + Node result = null; + + // perform the copy operation using the repository service + this.services.getCrossRepositoryCopyService().copy(src.getNodeRef(), dest.getNodeRef(), name); + + // if we get here then copy succeeded - retrieve the new node reference + if (dest.getNodeRef().getStoreRef().getProtocol().equals(StoreRef.PROTOCOL_AVM)) + { + Pair versionPath = AVMNodeConverter.ToAVMVersionPath(dest.getNodeRef()); + String destPath = AVMNodeConverter.ExtendAVMPath(versionPath.getSecond(), name); + AVMNodeDescriptor node = this.services.getAVMService().lookup(-1, destPath); + if (node != null) + { + result = ((AVMNode)dest).newInstance(destPath, -1, this.services, this.scope); + } + } + else if (dest.getNodeRef().getStoreRef().getProtocol().equals(StoreRef.PROTOCOL_WORKSPACE)) + { + result = dest.childByNamePath(name); + } + + return result; + } +} diff --git a/source/java/org/alfresco/repo/jscript/Node.java b/source/java/org/alfresco/repo/jscript/Node.java index c10ccdf6dd..54347d6b81 100644 --- a/source/java/org/alfresco/repo/jscript/Node.java +++ b/source/java/org/alfresco/repo/jscript/Node.java @@ -49,6 +49,7 @@ import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.NoTransformerException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.TemplateImageResolver; import org.alfresco.service.cmr.repository.TemplateNode; import org.alfresco.service.cmr.security.AccessStatus; @@ -58,6 +59,8 @@ import org.alfresco.service.cmr.version.VersionType; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.mozilla.javascript.Scriptable; @@ -77,1932 +80,1966 @@ import org.springframework.util.StringUtils; */ 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}"; - - /** 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 - */ - public Node(NodeRef nodeRef, ServiceRegistry services) - { - this(nodeRef, services, 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 - */ - public Node(NodeRef nodeRef, ServiceRegistry services, Scriptable scope) - { - if (nodeRef == null) - { - 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; - this.nodeService = services.getNodeService(); - 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 - */ - public Node newInstance(NodeRef nodeRef, ServiceRegistry services, Scriptable scope) - { - return new Node(nodeRef, services, scope); - } - - /** - * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) - */ - public void setScope(Scriptable scope) - { - this.scope = scope; - } - - - // ------------------------------------------------------------------------------ - // Node Wrapper API - - /** - * @return The GUID for the node - */ - public String getId() - { - return this.id; - } - - public String jsGet_id() - { - return getId(); - } - - /** - * @return Returns the NodeRef this Node object represents - */ - public NodeRef getNodeRef() - { - return this.nodeRef; - } - - public String jsGet_nodeRef() - { - return getNodeRef().toString(); - } - - /** - * @return Returns the type. - */ - public QName getType() - { - if (this.type == null) - { - 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 - */ - public String getName() - { - if (this.name == null) - { - // try and get the name from the properties first - 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) - { - ChildAssociationRef parentRef = this.nodeService.getPrimaryParent(this.nodeRef); - if (parentRef != null && parentRef.getQName() != null) + 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}"; + + /** 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 + */ + public Node(NodeRef nodeRef, ServiceRegistry services) + { + this(nodeRef, services, 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 + */ + public Node(NodeRef nodeRef, ServiceRegistry services, Scriptable scope) + { + if (nodeRef == null) + { + 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; + this.nodeService = services.getNodeService(); + 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 + */ + public Node newInstance(NodeRef nodeRef, ServiceRegistry services, Scriptable scope) + { + return new Node(nodeRef, services, scope); + } + + /** + * @see org.alfresco.repo.jscript.Scopeable#setScope(org.mozilla.javascript.Scriptable) + */ + public void setScope(Scriptable scope) + { + this.scope = scope; + } + + + // ------------------------------------------------------------------------------ + // Node Wrapper API + + /** + * @return The GUID for the node + */ + public String getId() + { + return this.id; + } + + public String jsGet_id() + { + return getId(); + } + + /** + * @return Returns the NodeRef this Node object represents + */ + public NodeRef getNodeRef() + { + return this.nodeRef; + } + + public String jsGet_nodeRef() + { + return getNodeRef().toString(); + } + + /** + * @return Returns the type. + */ + public QName getType() + { + if (this.type == null) + { + 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 + */ + public String getName() + { + if (this.name == null) + { + // try and get the name from the properties first + 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) { - this.name = parentRef.getQName().getLocalName(); + ChildAssociationRef parentRef = this.nodeService.getPrimaryParent(this.nodeRef); + if (parentRef != null && parentRef.getQName() != null) + { + this.name = parentRef.getQName().getLocalName(); + } + else + { + this.name = ""; + } } - else + } + + return this.name; + } + + public String jsGet_name() + { + return getName(); + } + + /** + * Helper to set the 'name' property for the node. + * + * @param name Name to set + */ + public void setName(String name) + { + if (name != null) + { + 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 + */ + public Node[] getChildren() + { + if (this.children == null) + { + List childRefs = this.nodeService.getChildAssocs(this.nodeRef); + this.children = new Node[childRefs.size()]; + for (int i = 0; i < childRefs.size(); i++) { - this.name = ""; + // create our Node representation from the NodeRef + Node child = newInstance(childRefs.get(i).getChildRef(), this.services, this.scope); + this.children[i] = child; } - } - } - - return this.name; - } - - public String jsGet_name() - { - return getName(); - } - - /** - * Helper to set the 'name' property for the node. - * - * @param name Name to set - */ - public void setName(String name) - { - if (name != null) - { - 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 - */ - public Node[] getChildren() - { - if (this.children == null) - { - List childRefs = this.nodeService.getChildAssocs(this.nodeRef); - this.children = new Node[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; - } - } - - return this.children; - } - - public Node[] jsGet_children() - { - return getChildren(); - } - - /** - * @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 - 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 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']/*"); - */ - public Node[] childrenByXPath(String xpath) - { - return getChildrenByXPath(xpath, false); - } - - /** - * 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. - */ - @SuppressWarnings("unchecked") - 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()); - - 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); - if (nodes == null) + } + + return this.children; + } + + public Node[] jsGet_children() + { + return getChildren(); + } + + /** + * @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 + StringBuilder xpath = new StringBuilder(path.length() << 1); + for (StringTokenizer t = new StringTokenizer(path, "/"); t.hasMoreTokens(); /**/) + { + if (xpath.length() != 0) { - // first access for the list for this qname - nodes = new Node[1]; + xpath.append('/'); } - else + 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); + // } + + /** + * @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); + } + + /** + * 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. + */ + @SuppressWarnings("unchecked") + 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()); + + List refs = this.nodeService.getTargetAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL); + for (AssociationRef ref : refs) { - Node[] newNodes = new Node[nodes.length + 1]; - System.arraycopy(nodes, 0, newNodes, 0, nodes.length); - nodes = newNodes; + String qname = ref.getTypeQName().toString(); + Node[] nodes = (Node[]) this.assocs.get(qname); + if (nodes == null) + { + // first access for the list for this qname + nodes = new Node[1]; + } + 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[nodes.length - 1] = newInstance(ref.getTargetRef(), this.services, this.scope); - - 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 Map of properties for this Node. - */ - @SuppressWarnings("unchecked") - public Map getProperties() - { - if (this.properties == null) - { - // 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) - */ - public boolean getIsContainer() - { - 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)); - } - - return isContainer.booleanValue(); - } - - public boolean jsGet_isContainer() - { - return getIsContainer(); - } - - /** - * @return true if this Node is a Document (i.e. with content) - */ - public boolean getIsDocument() - { - if (isDocument == null) - { - 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 - */ - public boolean getIsCategory() - { - // 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 - */ - public Set getAspects() - { - if (this.aspects == null) - { - this.aspects = this.nodeService.getAspects(this.nodeRef); - } - - return this.aspects; - } - - public String[] jsGet_aspects() - { - Set aspects = getAspects(); - String[] result = new String[aspects.size()]; - int count = 0; - for (QName qname : aspects) - { - result[count++] = qname.toString(); - } - return result; - } - - /** - * @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 - * @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 - */ - public String getDisplayPath() - { - if (displayPath == null) - { - try - { - displayPath = this.nodeService.getPath(this.nodeRef).toDisplayPath(this.nodeService); - } - catch (AccessDeniedException err) - { - displayPath = ""; - } - } - - return displayPath; - } - - public String jsGet_displayPath() - { - return getDisplayPath(); - } - - /** - * @return the small icon image for this node - */ - public String getIcon16() - { - return "/images/filetypes/_default.gif"; - } - - public String jsGet_icon16() - { - return getIcon16(); - } - - /** - * @return the large icon image for this node - */ - public String getIcon32() - { - 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); - if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER) - { - locked = true; - } - } - - return locked; - } - - public boolean jsGet_isLocked() - { - return isLocked(); - } - - /** - * @return the parent node - */ - public Node getParent() - { - if (parent == null) - { - NodeRef parentRef = getPrimaryParentAssoc().getParentRef(); - // handle root node (no parent!) - if (parentRef != null) - { - parent = newInstance(parentRef, this.services, 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() - { - if (primaryParentAssoc == null) - { - primaryParentAssoc = this.nodeService.getPrimaryParent(nodeRef); - } - 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) - */ - public String getContent() - { - String 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 - */ - public void setContent(String content) - { - ScriptContentData contentData = (ScriptContentData) getProperties().get(ContentModel.PROP_CONTENT); - if (contentData == null) - { - // guess a mimetype based on the filename - String mimetype = this.services.getMimetypeService().guessMimetype(getName()); - ContentData cdata = new ContentData(null, mimetype, 0L, "UTF-8"); - contentData = new ScriptContentData(cdata, ContentModel.PROP_CONTENT); - getProperties().put(ContentModel.PROP_CONTENT.toString(), contentData); - } - 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) - *

- * For a container node, this method return the URL to browse to the folder in the web-client - */ - public String getUrl() - { - 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); - } - } - else - { - 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) - */ - public String getMimetype() - { - String mimetype = null; - 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) - * - * @param mimetype Mimetype to set - */ - public void setMimetype(String mimetype) - { - 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) - */ - public long getSize() - { - long size = 0; - ScriptContentData content = (ScriptContentData) this.getProperties().get(ContentModel.PROP_CONTENT); - if (content != null) - { - size = content.getSize(); - } - - return size; - } - - public long jsGet_size() - { - return getSize(); - } - - - // ------------------------------------------------------------------------------ - // 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); - } - - /** - * Set whether this node should inherit permissions from the parent node. - * - * @param inherit True to inherit parent permissions, false otherwise. - */ - public void setInheritsPermissions(boolean 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 - */ - public void setPermission(String permission) - { - 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 - */ - public void setPermission(String permission, String authority) - { - 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 - */ - public void removePermission(String permission) - { - this.services.getPermissionService() + } + + 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 Map of properties for this Node. + */ + @SuppressWarnings("unchecked") + public Map getProperties() + { + if (this.properties == null) + { + // 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) + */ + public boolean getIsContainer() + { + 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)); + } + + return isContainer.booleanValue(); + } + + public boolean jsGet_isContainer() + { + return getIsContainer(); + } + + /** + * @return true if this Node is a Document (i.e. with content) + */ + public boolean getIsDocument() + { + if (isDocument == null) + { + 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 + */ + public boolean getIsCategory() + { + // 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 + */ + public Set getAspects() + { + if (this.aspects == null) + { + this.aspects = this.nodeService.getAspects(this.nodeRef); + } + + return this.aspects; + } + + public String[] jsGet_aspects() + { + Set aspects = getAspects(); + String[] result = new String[aspects.size()]; + int count = 0; + for (QName qname : aspects) + { + result[count++] = qname.toString(); + } + return result; + } + + /** + * @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 + * @return true if the user has the specified permission on the node. + */ + public boolean hasPermission(String permission) + { + ParameterCheck.mandatory("Permission Name", 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 + */ + public String getDisplayPath() + { + if (displayPath == null) + { + try + { + displayPath = this.nodeService.getPath(this.nodeRef).toDisplayPath(this.nodeService); + } + catch (AccessDeniedException err) + { + displayPath = ""; + } + } + + return displayPath; + } + + public String jsGet_displayPath() + { + return getDisplayPath(); + } + + /** + * @return the small icon image for this node + */ + public String getIcon16() + { + return "/images/filetypes/_default.gif"; + } + + public String jsGet_icon16() + { + return getIcon16(); + } + + /** + * @return the large icon image for this node + */ + public String getIcon32() + { + 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); + if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER) + { + locked = true; + } + } + + return locked; + } + + public boolean jsGet_isLocked() + { + return isLocked(); + } + + /** + * @return the parent node + */ + public Node getParent() + { + if (parent == null) + { + NodeRef parentRef = getPrimaryParentAssoc().getParentRef(); + // handle root node (no parent!) + if (parentRef != null) + { + parent = newInstance(parentRef, this.services, 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() + { + if (primaryParentAssoc == null) + { + primaryParentAssoc = this.nodeService.getPrimaryParent(nodeRef); + } + 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) + */ + public String getContent() + { + String 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 + */ + public void setContent(String content) + { + ScriptContentData contentData = (ScriptContentData) getProperties().get(ContentModel.PROP_CONTENT); + if (contentData == null) + { + // guess a mimetype based on the filename + String mimetype = this.services.getMimetypeService().guessMimetype(getName()); + ContentData cdata = new ContentData(null, mimetype, 0L, "UTF-8"); + contentData = new ScriptContentData(cdata, ContentModel.PROP_CONTENT); + getProperties().put(ContentModel.PROP_CONTENT.toString(), contentData); + } + 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) + *

+ * For a container node, this method return the URL to browse to the folder in the web-client + */ + public String getUrl() + { + 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); + } + } + else + { + 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) + */ + public String getMimetype() + { + String mimetype = null; + 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) + * + * @param mimetype Mimetype to set + */ + public void setMimetype(String mimetype) + { + 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) + */ + public long getSize() + { + long size = 0; + ScriptContentData content = (ScriptContentData) this.getProperties().get(ContentModel.PROP_CONTENT); + if (content != null) + { + size = content.getSize(); + } + + return size; + } + + public long jsGet_size() + { + return getSize(); + } + + + // ------------------------------------------------------------------------------ + // 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); + } + + /** + * Set whether this node should inherit permissions from the parent node. + * + * @param inherit True to inherit parent permissions, false otherwise. + */ + public void setInheritsPermissions(boolean 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 + */ + public void setPermission(String permission) + { + ParameterCheck.mandatoryString("Permission Name", permission); + 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 + */ + public void setPermission(String permission, String authority) + { + ParameterCheck.mandatoryString("Permission Name", permission); + ParameterCheck.mandatoryString("Authority", authority); + 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 + */ + public void removePermission(String permission) + { + ParameterCheck.mandatoryString("Permission Name", 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 - */ - public void removePermission(String permission, String authority) - { - this.services.getPermissionService().deletePermission(this.nodeRef, authority, permission); - } - - - // ------------------------------------------------------------------------------ - // Ownership API - - /** - * Set the owner of the node - */ - public void setOwner(String userId) - { - this.services.getOwnableService().setOwner(this.nodeRef, userId); - } - - /** - * Take ownership of the node. - */ - public void takeOwnership() - { - 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. - * - * @return - */ - public String jsGet_owner() - { - return getOwner(); - } - - - // ------------------------------------------------------------------------------ - // Create and Modify API - - /** - * Persist the properties of this Node. - */ - public void save() - { - // persist properties back to the node in the DB - Map props = new HashMap(getProperties().size()); - for (String key : this.properties.keySet()) - { - 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 - * - * @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) - { - // Specialise the type of the node - try - { - this.nodeService.setType(this.nodeRef, qnameType); - this.type = qnameType; - - return true; - } - catch (InvalidNodeRefException err) - { - // fall through to return fase - } - } - 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 - * - * @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); - node = newInstance(fileInfo.getNodeRef(), this.services, this.scope); - } - } - catch (FileExistsException fileErr) - { - // default of null will be returned - // TODO: how to report this kind of exception to the script writer? - } - catch (AccessDeniedException accessErr) - { - // 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 - * - * @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); - node = newInstance(fileInfo.getNodeRef(), this.services, this.scope); - } - } - catch (FileExistsException fileErr) - { - // default of null will be returned - // TODO: how to report this kind of exception to the script writer? - } - catch (AccessDeniedException accessErr) - { - // 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') - * - * @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) - { - 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); - node = newInstance(childAssocRef.getChildRef(), this.services, this.scope); - } - } - catch (AccessDeniedException accessErr) - { - // 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) - { - // default of false will be returned - } - catch (InvalidNodeRefException refErr) - { - // 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 - * - * @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. - * - * @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); - copy = newInstance(copyRef, this.services, this.scope); - } - } - catch (AccessDeniedException accessErr) - { - // default of null will be returned - } - catch (InvalidNodeRefException nodeErr) - { - // default of null will be returned - } - - return copy; - } - - /** - * Move this Node to a new parent destination. - * - * @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) - { + } + + /** + * 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 + */ + public void removePermission(String permission, String authority) + { + ParameterCheck.mandatoryString("Permission Name", permission); + ParameterCheck.mandatoryString("Authority", authority); + this.services.getPermissionService().deletePermission(this.nodeRef, authority, permission); + } + + + // ------------------------------------------------------------------------------ + // Ownership API + + /** + * Set the owner of the node + */ + public void setOwner(String userId) + { + this.services.getOwnableService().setOwner(this.nodeRef, userId); + } + + /** + * Take ownership of the node. + */ + public void takeOwnership() + { + 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. + * + * @return + */ + public String jsGet_owner() + { + return getOwner(); + } + + + // ------------------------------------------------------------------------------ + // Create and Modify API + + /** + * Persist the properties of this Node. + */ + public void save() + { + // persist properties back to the node in the DB + Map props = new HashMap(getProperties().size()); + for (String key : this.properties.keySet()) + { + 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 + * + * @return true if successful, false otherwise + */ + public boolean specializeType(String type) + { + ParameterCheck.mandatoryString("Type", type); + + QName qnameType = createQName(type); + + // Ensure that we are performing a specialise + 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) + { + // fall through to return fase + } + } + 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 + * + * @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); + node = newInstance(fileInfo.getNodeRef(), this.services, this.scope); + } + } + catch (FileExistsException fileErr) + { + // default of null will be returned + // TODO: how to report this kind of exception to the script writer? + } + catch (AccessDeniedException accessErr) + { + // 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 + * + * @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); + node = newInstance(fileInfo.getNodeRef(), this.services, this.scope); + } + } + catch (FileExistsException fileErr) + { + // default of null will be returned + // TODO: how to report this kind of exception to the script writer? + } + catch (AccessDeniedException accessErr) + { + // 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') + * + * @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) + { + 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); + node = newInstance(childAssocRef.getChildRef(), this.services, this.scope); + } + } + catch (AccessDeniedException accessErr) + { + // 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) + { + // default of false will be returned + } + catch (InvalidNodeRefException refErr) + { + // 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 + * + * @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. + * + * @return The newly copied Node instance or null if failed to copy. + */ + public Node copy(Node destination, boolean deepCopy) + { + ParameterCheck.mandatory("Destination Node", destination); + + Node copy = null; + + try + { + if (destination.getNodeRef().getStoreRef().getProtocol().equals(StoreRef.PROTOCOL_WORKSPACE)) + { + NodeRef copyRef = this.services.getCopyService().copyAndRename(this.nodeRef, destination.getNodeRef(), + ContentModel.ASSOC_CONTAINS, getPrimaryParentAssoc().getQName(), deepCopy); + copy = newInstance(copyRef, this.services, this.scope); + } + else + { + // NOTE: the deepCopy flag is not respected for this copy mechanism + copy = getCrossRepositoryCopyHelper().copy(this, destination, getName()); + } + } + catch (AccessDeniedException accessErr) + { + // default of null will be returned + } + catch (InvalidNodeRefException nodeErr) + { + // default of null will be returned + } + + return copy; + } + + /** + * Move this Node to a new parent destination. + * + * @param destination Node + * + * @return true on successful move, false on failure to move. + */ + public boolean move(Node destination) + { + ParameterCheck.mandatory("Destination Node", destination); + + boolean success = false; + + try + { this.primaryParentAssoc = this.nodeService.moveNode(this.nodeRef, destination.getNodeRef(), - ContentModel.ASSOC_CONTAINS, getPrimaryParentAssoc().getQName()); - + ContentModel.ASSOC_CONTAINS, getPrimaryParentAssoc().getQName()); + // reset cached values reset(); - + success = true; - } - } - catch (AccessDeniedException accessErr) - { - // default of false will be returned - } - catch (InvalidNodeRefException refErr) - { - // 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. - * - * @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! - * - * @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 - { + } + catch (AccessDeniedException accessErr) + { + // default of false will be returned + } + catch (InvalidNodeRefException refErr) + { + // 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. + * + * @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! + * + * @return true if the aspect was added successfully, false if an error occured. + */ + public boolean addAspect(String type, Object props) + { + ParameterCheck.mandatoryString("Aspect Type", type); + + boolean success = false; + + try + { Map aspectProps = null; if (props instanceof ScriptableObject) { - 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 < propIds.length; i++) - { - // work on each key in turn - Object propId = propIds[i]; - - // we are only interested in keys that are formed of Strings i.e. QName.toString() - if (propId instanceof String) - { - // get the value out for the specified key - make sure it is Serializable - Object value = properties.get((String) propId, properties); - value = getValueConverter().convertValueForRepo((Serializable) value); - aspectProps.put(createQName((String) propId), (Serializable) value); - } - } + 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 < propIds.length; i++) + { + // work on each key in turn + Object propId = propIds[i]; + + // we are only interested in keys that are formed of Strings i.e. QName.toString() + if (propId instanceof String) + { + // get the value out for the specified key - make sure it is Serializable + Object value = properties.get((String) propId, properties); + value = getValueConverter().convertValueForRepo((Serializable) value); + aspectProps.put(createQName((String) propId), (Serializable) value); + } + } } QName aspectQName = createQName(type); this.nodeService.addAspect(this.nodeRef, aspectQName, aspectProps); - + // reset the relevant cached node members reset(); - + success = true; - } - catch (InvalidAspectException aspectErr) - { + } + catch (InvalidAspectException aspectErr) + { // default of failed will be returned - } - } - - return success; - } - - /** - * Remove aspect from the node. - * - * @param type the aspect type - * - * @return true if successful, false otherwise - */ - public boolean removeAspect(String type) - { - boolean success = false; - - if (type != null && type.length() != 0) - { - QName aspectQName = createQName(type); - this.nodeService.removeAspect(this.nodeRef, aspectQName); - - // reset the relevant cached node members - reset(); - - success = true; - } - - return success; - } - - - // ------------------------------------------------------------------------------ - // Checkout/Checkin Services - - /** - * Perform a check-out of this document into the current parent space. - * - * @return the working copy Node for the checked out document - */ - public Node checkout() - { - NodeRef workingCopyRef = this.services.getCheckOutCheckInService().checkout(this.nodeRef); - Node workingCopy = newInstance(workingCopyRef, this.services, this.scope); - - // reset the aspect and properties as checking out a document causes changes - this.properties = null; - this.aspects = null; - - return workingCopy; - } - - /** - * Perform a check-out of this document into the specified destination space. - * - * @param destination - * Destination for the checked out document working copy Node. - * @return the working copy Node for the checked out document - */ - public Node checkout(Node destination) - { - ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(destination.getNodeRef()); - NodeRef workingCopyRef = this.services.getCheckOutCheckInService().checkout(this.nodeRef, - destination.getNodeRef(), ContentModel.ASSOC_CONTAINS, childAssocRef.getQName()); - Node workingCopy = newInstance(workingCopyRef, this.services, this.scope); - - // reset the aspect and properties as checking out a document causes changes - this.properties = null; - this.aspects = null; - - return workingCopy; - } - - /** - * Check-in a working copy document. The current state of the working copy is copied to the original node, - * this will include any content updated in the working node. Note that this method can only be called on a - * working copy Node. - * - * @return the original Node that was checked out. - */ - public Node checkin() - { - return checkin("", false); - } - - /** - * Check-in a working copy document. The current state of the working copy is copied to the original node, - * this will include any content updated in the working node. Note that this method can only be called on a - * working copy Node. - * - * @param history Version history note - * - * @return the original Node that was checked out. - */ - public Node checkin(String history) - { - return checkin(history, false); - } - - /** - * Check-in a working copy document. The current state of the working copy is copied to the original node, - * this will include any content updated in the working node. Note that this method can only be called on a - * working copy Node. - * - * @param history Version history note - * @param majorVersion True to save as a major version increment, false for minor version. - * - * @return the original Node that was checked out. - */ - public Node checkin(String history, boolean majorVersion) - { - Map props = new HashMap(2, 1.0f); - props.put(Version.PROP_DESCRIPTION, history); - props.put(VersionModel.PROP_VERSION_TYPE, majorVersion ? VersionType.MAJOR : VersionType.MINOR); - NodeRef original = this.services.getCheckOutCheckInService().checkin(this.nodeRef, props); - return newInstance(original, this.services, this.scope); - } - - /** - * Cancel the check-out of a working copy document. The working copy will be deleted and any changes made to it - * are lost. Note that this method can only be called on a working copy Node. The reference to this working copy - * Node should be discarded. - * - * @return the original Node that was checked out. - */ - public Node cancelCheckout() - { - NodeRef original = this.services.getCheckOutCheckInService().cancelCheckout(this.nodeRef); - return newInstance(original, this.services, this.scope); - } - - // ------------------------------------------------------------------------------ - // Transformation and Rendering API - - /** - * Transform a document to a new document mimetype format. A copy of the document is made and the extension - * changed to match the new mimetype, then the transformation isapplied. - * - * @param mimetype Mimetype destination for the transformation - * - * @return Node representing the newly transformed document. - */ - public Node transformDocument(String mimetype) - { - return transformDocument(mimetype, getPrimaryParentAssoc().getParentRef()); - } - - /** - * Transform a document to a new document mimetype format. A copy of the document is made in the specified - * destination folder and the extension changed to match the new mimetype, then then transformation is applied. - * - * @param mimetype Mimetype destination for the transformation - * @param destination Destination folder location - * - * @return Node representing the newly transformed document. - */ - public Node transformDocument(String mimetype, Node destination) - { - return transformDocument(mimetype, destination.getNodeRef()); - } - - private Node transformDocument(String mimetype, NodeRef destination) - { - // the delegate definition for transforming a document - Transformer transformer = new Transformer() - { - public Node transform(ContentService contentService, NodeRef nodeRef, ContentReader reader, - ContentWriter writer) - { - Node transformedNode = null; - if (contentService.isTransformable(reader, writer)) + } + + return success; + } + + /** + * Remove aspect from the node. + * + * @param type the aspect type + * + * @return true if successful, false otherwise + */ + public boolean removeAspect(String type) + { + ParameterCheck.mandatoryString("Aspect Type", type); + + QName aspectQName = createQName(type); + this.nodeService.removeAspect(this.nodeRef, aspectQName); + + // reset the relevant cached node members + reset(); + + return true; + } + + + // ------------------------------------------------------------------------------ + // Checkout/Checkin Services + + /** + * Perform a check-out of this document into the current parent space. + * + * @return the working copy Node for the checked out document + */ + public Node checkout() + { + NodeRef workingCopyRef = this.services.getCheckOutCheckInService().checkout(this.nodeRef); + Node workingCopy = newInstance(workingCopyRef, this.services, this.scope); + + // reset the aspect and properties as checking out a document causes changes + this.properties = null; + this.aspects = null; + + return workingCopy; + } + + /** + * Perform a check-out of this document into the specified destination space. + * + * @param destination + * Destination for the checked out document working copy Node. + * @return the working copy Node for the checked out document + */ + public Node checkout(Node destination) + { + ParameterCheck.mandatory("Destination Node", destination); + + ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(destination.getNodeRef()); + NodeRef workingCopyRef = this.services.getCheckOutCheckInService().checkout(this.nodeRef, + destination.getNodeRef(), ContentModel.ASSOC_CONTAINS, childAssocRef.getQName()); + Node workingCopy = newInstance(workingCopyRef, this.services, this.scope); + + // reset the aspect and properties as checking out a document causes changes + this.properties = null; + this.aspects = null; + + return workingCopy; + } + + /** + * Check-in a working copy document. The current state of the working copy is copied to the original node, + * this will include any content updated in the working node. Note that this method can only be called on a + * working copy Node. + * + * @return the original Node that was checked out. + */ + public Node checkin() + { + return checkin("", false); + } + + /** + * Check-in a working copy document. The current state of the working copy is copied to the original node, + * this will include any content updated in the working node. Note that this method can only be called on a + * working copy Node. + * + * @param history Version history note + * + * @return the original Node that was checked out. + */ + public Node checkin(String history) + { + return checkin(history, false); + } + + /** + * Check-in a working copy document. The current state of the working copy is copied to the original node, + * this will include any content updated in the working node. Note that this method can only be called on a + * working copy Node. + * + * @param history Version history note + * @param majorVersion True to save as a major version increment, false for minor version. + * + * @return the original Node that was checked out. + */ + public Node checkin(String history, boolean majorVersion) + { + Map props = new HashMap(2, 1.0f); + props.put(Version.PROP_DESCRIPTION, history); + props.put(VersionModel.PROP_VERSION_TYPE, majorVersion ? VersionType.MAJOR : VersionType.MINOR); + NodeRef original = this.services.getCheckOutCheckInService().checkin(this.nodeRef, props); + return newInstance(original, this.services, this.scope); + } + + /** + * Cancel the check-out of a working copy document. The working copy will be deleted and any changes made to it + * are lost. Note that this method can only be called on a working copy Node. The reference to this working copy + * Node should be discarded. + * + * @return the original Node that was checked out. + */ + public Node cancelCheckout() + { + NodeRef original = this.services.getCheckOutCheckInService().cancelCheckout(this.nodeRef); + return newInstance(original, this.services, this.scope); + } + + + // ------------------------------------------------------------------------------ + // Transformation and Rendering API + + /** + * Transform a document to a new document mimetype format. A copy of the document is made and the extension + * changed to match the new mimetype, then the transformation isapplied. + * + * @param mimetype Mimetype destination for the transformation + * + * @return Node representing the newly transformed document. + */ + public Node transformDocument(String mimetype) + { + return transformDocument(mimetype, getPrimaryParentAssoc().getParentRef()); + } + + /** + * Transform a document to a new document mimetype format. A copy of the document is made in the specified + * destination folder and the extension changed to match the new mimetype, then then transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * @param destination Destination folder location + * + * @return Node representing the newly transformed document. + */ + public Node transformDocument(String mimetype, Node destination) + { + return transformDocument(mimetype, destination.getNodeRef()); + } + + private Node transformDocument(String mimetype, NodeRef destination) + { + ParameterCheck.mandatoryString("Mimetype", mimetype); + ParameterCheck.mandatory("Destination Node", destination); + + // the delegate definition for transforming a document + Transformer transformer = new Transformer() + { + public Node transform(ContentService contentService, NodeRef nodeRef, ContentReader reader, + ContentWriter writer) { - try - { - contentService.transform(reader, writer); - transformedNode = newInstance(nodeRef, services, scope); - } - catch (NoTransformerException err) - { - // failed to find a useful transformer - do not return a node instance - } + Node transformedNode = null; + if (contentService.isTransformable(reader, writer)) + { + try + { + contentService.transform(reader, writer); + transformedNode = newInstance(nodeRef, services, scope); + } + catch (NoTransformerException err) + { + // failed to find a useful transformer - do not return a node instance + } + } + return transformedNode; } - return transformedNode; - } - }; - - return transformNode(transformer, mimetype, destination); - } - - /** - * Generic method to transform Node content from one mimetype to another. - * - * @param transformer The Transformer delegate supplying the transformation logic - * @param mimetype Mimetype of the destination content - * @param destination Destination folder location for the resulting document - * - * @return Node representing the transformed content - or null if the transform failed - */ - private Node transformNode(Transformer transformer, String mimetype, NodeRef destination) - { - Node transformedNode = null; - - // get the content reader - ContentService contentService = this.services.getContentService(); - ContentReader reader = contentService.getReader(this.nodeRef, ContentModel.PROP_CONTENT); - - // only perform the transformation if some content is available - if (reader != null) - { - // Copy the content node to a new node - String copyName = TransformActionExecuter.transformName(this.services.getMimetypeService(), getName(), - mimetype); - NodeRef copyNodeRef = this.services.getCopyService().copy(this.nodeRef, destination, - ContentModel.ASSOC_CONTAINS, - QName.createQName(ContentModel.PROP_CONTENT.getNamespaceURI(), QName.createValidLocalName(copyName)), - false); - - // modify the name of the copy to reflect the new mimetype - this.nodeService.setProperty(copyNodeRef, ContentModel.PROP_NAME, copyName); - - // get the writer and set it up - ContentWriter writer = contentService.getWriter(copyNodeRef, ContentModel.PROP_CONTENT, true); - writer.setMimetype(mimetype); // new mimetype - writer.setEncoding(reader.getEncoding()); // original encoding - - // Try and transform the content using the supplied delegate - transformedNode = transformer.transform(contentService, copyNodeRef, reader, writer); - } - - return transformedNode; - } - - /** - * Transform an image to a new image format. A copy of the image document is made and the extension changed to - * match the new mimetype, then the transformation is applied. - * - * @param mimetype Mimetype destination for the transformation - * - * @return Node representing the newly transformed image. - */ - public Node transformImage(String mimetype) - { - return transformImage(mimetype, null, getPrimaryParentAssoc().getParentRef()); - } - - /** - * Transform an image to a new image format. A copy of the image document is made and the extension changed to - * match the new mimetype, then the transformation is applied. - * - * @param mimetype Mimetype destination for the transformation - * @param options Image convert command options - * - * @return Node representing the newly transformed image. - */ - public Node transformImage(String mimetype, String options) - { - return transformImage(mimetype, options, getPrimaryParentAssoc().getParentRef()); - } - - /** - * Transform an image to a new image mimetype format. A copy of the image document is made in the specified - * destination folder and the extension changed to match the newmimetype, then then transformation is applied. - * - * @param mimetype Mimetype destination for the transformation - * @param destination Destination folder location - * - * @return Node representing the newly transformed image. - */ - public Node transformImage(String mimetype, Node destination) - { - return transformImage(mimetype, null, destination.getNodeRef()); - } - - /** - * Transform an image to a new image mimetype format. A copy of the image document is made in the specified - * destination folder and the extension changed to match the new - * mimetype, then then transformation is applied. - * - * @param mimetype Mimetype destination for the transformation - * @param options Image convert command options - * @param destination Destination folder location - * - * @return Node representing the newly transformed image. - */ - public Node transformImage(String mimetype, String options, Node destination) - { - return transformImage(mimetype, options, destination.getNodeRef()); - } - - private Node transformImage(String mimetype, final String options, NodeRef destination) - { - // the delegate definition for transforming an image - Transformer transformer = new Transformer() - { - public Node transform(ContentService contentService, NodeRef nodeRef, ContentReader reader, - ContentWriter writer) - { - Node transformedNode = null; + }; + + return transformNode(transformer, mimetype, destination); + } + + /** + * Generic method to transform Node content from one mimetype to another. + * + * @param transformer The Transformer delegate supplying the transformation logic + * @param mimetype Mimetype of the destination content + * @param destination Destination folder location for the resulting document + * + * @return Node representing the transformed content - or null if the transform failed + */ + private Node transformNode(Transformer transformer, String mimetype, NodeRef destination) + { + Node transformedNode = null; + + // get the content reader + ContentService contentService = this.services.getContentService(); + ContentReader reader = contentService.getReader(this.nodeRef, ContentModel.PROP_CONTENT); + + // only perform the transformation if some content is available + if (reader != null) + { + // Copy the content node to a new node + String copyName = TransformActionExecuter.transformName(this.services.getMimetypeService(), getName(), + mimetype); + NodeRef copyNodeRef = this.services.getCopyService().copy(this.nodeRef, destination, + ContentModel.ASSOC_CONTAINS, + QName.createQName(ContentModel.PROP_CONTENT.getNamespaceURI(), QName.createValidLocalName(copyName)), + false); + + // modify the name of the copy to reflect the new mimetype + this.nodeService.setProperty(copyNodeRef, ContentModel.PROP_NAME, copyName); + + // get the writer and set it up + ContentWriter writer = contentService.getWriter(copyNodeRef, ContentModel.PROP_CONTENT, true); + writer.setMimetype(mimetype); // new mimetype + writer.setEncoding(reader.getEncoding()); // original encoding + + // Try and transform the content using the supplied delegate + transformedNode = transformer.transform(contentService, copyNodeRef, reader, writer); + } + + return transformedNode; + } + + /** + * Transform an image to a new image format. A copy of the image document is made and the extension changed to + * match the new mimetype, then the transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * + * @return Node representing the newly transformed image. + */ + public Node transformImage(String mimetype) + { + return transformImage(mimetype, null, getPrimaryParentAssoc().getParentRef()); + } + + /** + * Transform an image to a new image format. A copy of the image document is made and the extension changed to + * match the new mimetype, then the transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * @param options Image convert command options + * + * @return Node representing the newly transformed image. + */ + public Node transformImage(String mimetype, String options) + { + return transformImage(mimetype, options, getPrimaryParentAssoc().getParentRef()); + } + + /** + * Transform an image to a new image mimetype format. A copy of the image document is made in the specified + * destination folder and the extension changed to match the newmimetype, then then transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * @param destination Destination folder location + * + * @return Node representing the newly transformed image. + */ + public Node transformImage(String mimetype, Node destination) + { + ParameterCheck.mandatory("Destination Node", destination); + return transformImage(mimetype, null, destination.getNodeRef()); + } + + /** + * Transform an image to a new image mimetype format. A copy of the image document is made in the specified + * destination folder and the extension changed to match the new + * mimetype, then then transformation is applied. + * + * @param mimetype Mimetype destination for the transformation + * @param options Image convert command options + * @param destination Destination folder location + * + * @return Node representing the newly transformed image. + */ + public Node transformImage(String mimetype, String options, Node destination) + { + ParameterCheck.mandatory("Destination Node", destination); + return transformImage(mimetype, options, destination.getNodeRef()); + } + + private Node transformImage(String mimetype, final String options, NodeRef destination) + { + ParameterCheck.mandatoryString("Mimetype", mimetype); + + // the delegate definition for transforming an image + Transformer transformer = new Transformer() + { + public Node transform(ContentService contentService, NodeRef nodeRef, ContentReader reader, + ContentWriter writer) + { + Node transformedNode = null; + try + { + Map opts = new HashMap(1); + opts.put(ImageMagickContentTransformer.KEY_OPTIONS, options != null ? options : ""); + contentService.getImageTransformer().transform(reader, writer, opts); + transformedNode = newInstance(nodeRef, services, scope); + } + catch (NoTransformerException err) + { + // failed to find a useful transformer - do not return a node instance + } + return transformedNode; + } + }; + + return transformNode(transformer, mimetype, destination); + } + + /** + * Process a FreeMarker Template against the current node. + * + * @param template Node of the template to execute + * + * @return output of the template execution + */ + public String processTemplate(Node template) + { + ParameterCheck.mandatory("Template Node", template); + return processTemplate(template.getContent(), null, null); + } + + /** + * Process a FreeMarker Template against the current node. + * + * @param template Node of the template to execute + * @param args Scriptable object (generally an associative array) containing the name/value pairs of + * arguments to be passed to the template + * + * @return output of the template execution + */ + public String processTemplate(Node template, Object args) + { + ParameterCheck.mandatory("Template Node", template); + return processTemplate(template.getContent(), null, (ScriptableObject) args); + } + + /** + * Process a FreeMarker Template against the current node. + * + * @param template The template to execute + * + * @return output of the template execution + */ + public String processTemplate(String template) + { + ParameterCheck.mandatoryString("Template", template); + return processTemplate(template, null, null); + } + + /** + * Process a FreeMarker Template against the current node. + * + * @param template The template to execute + * @param args Scriptable object (generally an associative array) containing the name/value pairs of + * arguments to be passed to the template + * + * @return output of the template execution + */ + public String processTemplate(String template, Object args) + { + ParameterCheck.mandatoryString("Template", template); + return processTemplate(template, null, (ScriptableObject) args); + } + + private String processTemplate(String template, NodeRef templateRef, ScriptableObject args) + { + // build default model for the template processing + 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, null); + + // add the current node as either the document/space as appropriate + if (this.getIsDocument()) + { + model.put("document", new TemplateNode(this.nodeRef, this.services, null)); + model.put("space", new TemplateNode(getPrimaryParentAssoc().getParentRef(), this.services, null)); + } + else + { + model.put("space", new TemplateNode(this.nodeRef, this.services, null)); + } + + // add the supplied args to the 'args' root object + if (args != null) + { + // we need to get all the keys to the properties provided + // 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 < propIds.length; i++) + { + // work on each key in turn + Object propId = propIds[i]; + + // we are only interested in keys that are formed of Strings i.e. QName.toString() + if (propId instanceof String) + { + // get the value out for the specified key - make sure it is Serializable + Object value = args.get((String) propId, args); + value = getValueConverter().convertValueForRepo((Serializable) value); + if (value != null) + { + templateArgs.put((String) propId, value.toString()); + } + } + } + // add the args to the model as the 'args' root object + model.put("args", templateArgs); + } + + // execute template! + // TODO: check that script modified nodes are reflected... + return this.services.getTemplateService().processTemplateString(null, template, model); + } + + + // ------------------------------------------------------------------------------ + // Helper methods + + /** + * Override Object.toString() to provide useful debug output + */ + public String toString() + { + if (this.nodeService.exists(nodeRef)) + { + // TODO: DC: Allow debug output of property values - for now it's disabled as this could potentially + // follow a large network of nodes. Unfortunately, JBPM issues unprotected debug statements + // where node.toString is used - will request this is fixed in next release of JBPM. + return "Node Type: " + getType() + "\nNode Properties: " + this.getProperties().size() + "\nNode Aspects: " + + this.getAspects().toString(); + } + else + { + return "Node no longer exists: " + nodeRef; + } + } + + /** + * Helper to create a QName from either a fully qualified or short-name QName string + * + * @param s Fully qualified or short-name QName string + * + * @return QName + */ + private QName createQName(String s) + { + QName qname; + if (s.indexOf(NAMESPACE_BEGIN) != -1) + { + qname = QName.createQName(s); + } + else + { + qname = QName.createQName(s, this.services.getNamespaceService()); + } + return qname; + } + + /** + * Reset the Node cached state + */ + public void reset() + { + this.name = null; + this.type = null; + this.properties = null; + this.aspects = null; + this.assocs = null; + this.children = null; + this.displayPath = null; + this.isDocument = null; + this.isContainer = null; + this.parent = null; + this.primaryParentAssoc = null; + } + + /** + * @return helper object to perform cross repository copy of JavaScript Node objects + */ + protected CrossRepositoryCopy getCrossRepositoryCopyHelper() + { + return (CrossRepositoryCopy)this.services.getService( + QName.createQName("", CrossRepositoryCopy.BEAN_NAME)); + } + + /** + * 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); + + 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) + { + if (nodes.size() != 0) + { + result = new Node[1]; + result[0] = newInstance(nodes.get(0), this.services, this.scope); + } + } + // or all the results + else + { + result = new Node[nodes.size()]; + for (int i = 0; i < nodes.size(); i++) + { + NodeRef ref = nodes.get(i); + result[i] = newInstance(ref, this.services, this.scope); + } + } + } + + return result != null ? result : new Node[0]; + } + + + // ------------------------------------------------------------------------------ + // Value Conversion + + /** + * Gets the node value converter + * + * @return the node value converter + */ + protected NodeValueConverter getValueConverter() + { + if (converter == null) + { + converter = createValueConverter(); + } + return converter; + } + + /** + * Constructs the node value converter + * + * @return the node value converter + */ + protected NodeValueConverter createValueConverter() + { + return new NodeValueConverter(); + } + + // Set support + + /** + * Value converter with knowledge of Node specific value types + */ + public class NodeValueConverter extends ValueConverter + { + /** + * Convert an object from any repository serialized value to a valid script object. This includes converting + * Collection multi-value properties into JavaScript Array objects. + * + * @param qname QName of the property value for conversion + * @param value Property value + * + * @return Value safe for scripting usage + */ + public Serializable convertValueForScript(QName qname, Serializable value) + { + return convertValueForScript(services, scope, qname, value); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.jscript.ValueConverter#convertValueForScript(org.alfresco.service.ServiceRegistry, + * org.mozilla.javascript.Scriptable, org.alfresco.service.namespace.QName, java.io.Serializable) + */ + @Override + public Serializable convertValueForScript(ServiceRegistry services, Scriptable scope, QName qname, + Serializable value) + { + if (value instanceof ContentData) + { + // ContentData object properties are converted to ScriptContentData objects + // so the content and other properties of those objects can be accessed + value = new ScriptContentData((ContentData) value, qname); + } + else + { + value = super.convertValueForScript(services, scope, qname, value); + } + return value; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.jscript.ValueConverter#convertValueForRepo(java.io.Serializable) + */ + @Override + public Serializable convertValueForRepo(Serializable value) + { + if (value instanceof ScriptContentData) + { + // convert back to ContentData + value = ((ScriptContentData) value).contentData; + } + else + { + value = super.convertValueForRepo(value); + } + return value; + } + } + + + // ------------------------------------------------------------------------------ + // Inner Classes + + /** + * Inner class wrapping and providing access to a ContentData property + */ + public class ScriptContentData implements Serializable + { + private static final long serialVersionUID = -7819328543933312278L; + + /** + * Constructor + * + * @param contentData The ContentData object this object wraps + * @param property The property the ContentData is attached too + */ + public ScriptContentData(ContentData contentData, QName property) + { + this.contentData = contentData; + this.property = property; + } + + /** + * @return the content stream + */ + public String getContent() + { + ContentService contentService = services.getContentService(); + ContentReader reader = contentService.getReader(nodeRef, property); + + return (reader != null && reader.exists()) ? reader.getContentString() : ""; + } + + public String jsGet_content() + { + return getContent(); + } + + /** + * Set the content stream + * + * @param content Content string to set + */ + public void setContent(String content) + { + ContentService contentService = services.getContentService(); + ContentWriter writer = contentService.getWriter(nodeRef, this.property, true); + writer.setMimetype(getMimetype()); // use existing mimetype value + writer.putContent(content); + + // update cached variables after putContent() + this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property); + } + + public void jsSet_content(String content) + { + setContent(content); + } + + /** + * @return download URL to the content + */ + public String getUrl() + { try { - Map opts = new HashMap(1); - opts.put(ImageMagickContentTransformer.KEY_OPTIONS, options != null ? options : ""); - contentService.getImageTransformer().transform(reader, writer, opts); - transformedNode = newInstance(nodeRef, services, scope); + return MessageFormat.format(CONTENT_PROP_URL, new Object[] { nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), nodeRef.getId(), + StringUtils.replace(URLEncoder.encode(getName(), "UTF-8"), "+", "%20"), + StringUtils.replace(URLEncoder.encode(property.toString(), "UTF-8"), "+", "%20") }); } - catch (NoTransformerException err) + catch (UnsupportedEncodingException err) { - // failed to find a useful transformer - do not return a node instance + throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + nodeRef, err); } - return transformedNode; - } - }; - - return transformNode(transformer, mimetype, destination); - } - - /** - * Process a FreeMarker Template against the current node. - * - * @param template Node of the template to execute - * - * @return output of the template execution - */ - public String processTemplate(Node template) - { - return processTemplate(template.getContent(), null, null); - } - - /** - * Process a FreeMarker Template against the current node. - * - * @param template Node of the template to execute - * @param args Scriptable object (generally an associative array) containing the name/value pairs of - * arguments to be passed to the template - * - * @return output of the template execution - */ - public String processTemplate(Node template, Object args) - { - return processTemplate(template.getContent(), null, (ScriptableObject) args); - } - - /** - * Process a FreeMarker Template against the current node. - * - * @param template The template to execute - * - * @return output of the template execution - */ - public String processTemplate(String template) - { - return processTemplate(template, null, null); - } - - /** - * Process a FreeMarker Template against the current node. - * - * @param template The template to execute - * @param args Scriptable object (generally an associative array) containing the name/value pairs of - * arguments to be passed to the template - * - * @return output of the template execution - */ - public String processTemplate(String template, Object args) - { - return processTemplate(template, null, (ScriptableObject) args); - } - - private String processTemplate(String template, NodeRef templateRef, ScriptableObject args) - { - // build default model for the template processing - 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, null); - - // add the current node as either the document/space as appropriate - if (this.getIsDocument()) - { - model.put("document", new TemplateNode(this.nodeRef, this.services, null)); - model.put("space", new TemplateNode(getPrimaryParentAssoc().getParentRef(), this.services, null)); - } - else - { - model.put("space", new TemplateNode(this.nodeRef, this.services, null)); - } - - // add the supplied args to the 'args' root object - if (args != null) - { - // we need to get all the keys to the properties provided - // 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 < propIds.length; i++) - { - // work on each key in turn - Object propId = propIds[i]; - - // we are only interested in keys that are formed of Strings i.e. QName.toString() - if (propId instanceof String) - { - // get the value out for the specified key - make sure it is Serializable - Object value = args.get((String) propId, args); - value = getValueConverter().convertValueForRepo((Serializable) value); - if (value != null) - { - templateArgs.put((String) propId, value.toString()); - } - } - } - // add the args to the model as the 'args' root object - model.put("args", templateArgs); - } - - // execute template! - // TODO: check that script modified nodes are reflected... - return this.services.getTemplateService().processTemplateString(null, template, model); - } - - - // ------------------------------------------------------------------------------ - // Helper methods - - /** - * Override Object.toString() to provide useful debug output - */ - public String toString() - { - if (this.nodeService.exists(nodeRef)) - { - // TODO: DC: Allow debug output of property values - for now it's disabled as this could potentially - // follow a large network of nodes. Unfortunately, JBPM issues unprotected debug statements - // where node.toString is used - will request this is fixed in next release of JBPM. - return "Node Type: " + getType() + "\nNode Properties: " + this.getProperties().size() + "\nNode Aspects: " - + this.getAspects().toString(); - } - else - { - return "Node no longer exists: " + nodeRef; - } - } - - /** - * Helper to create a QName from either a fully qualified or short-name QName string - * - * @param s Fully qualified or short-name QName string - * - * @return QName - */ - private QName createQName(String s) - { - QName qname; - if (s.indexOf(NAMESPACE_BEGIN) != -1) - { - qname = QName.createQName(s); - } - else - { - qname = QName.createQName(s, this.services.getNamespaceService()); - } - return qname; - } - - /** - * Reset the Node cached state - */ - public void reset() - { - this.name = null; - this.type = null; - this.properties = null; - this.aspects = null; - this.assocs = null; - this.children = null; - this.displayPath = null; - this.isDocument = null; - this.isContainer = null; - this.parent = null; - this.primaryParentAssoc = null; - } - - /** - * 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); - - 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) - { - if (nodes.size() != 0) - { - result = new Node[1]; - result[0] = newInstance(nodes.get(0), this.services, this.scope); - } - } - // or all the results - else - { - result = new Node[nodes.size()]; - for (int i = 0; i < nodes.size(); i++) - { - NodeRef ref = nodes.get(i); - result[i] = newInstance(ref, this.services, this.scope); - } - } - } - - return result != null ? result : new Node[0]; - } - - - // ------------------------------------------------------------------------------ - // Value Conversion - - /** - * Gets the node value converter - * - * @return the node value converter - */ - protected NodeValueConverter getValueConverter() - { - if (converter == null) - { - converter = createValueConverter(); - } - return converter; - } - - /** - * Constructs the node value converter - * - * @return the node value converter - */ - protected NodeValueConverter createValueConverter() - { - return new NodeValueConverter(); - } - - // Set support - - /** - * Value converter with knowledge of Node specific value types - */ - public class NodeValueConverter extends ValueConverter - { - /** - * Convert an object from any repository serialized value to a valid script object. This includes converting - * Collection multi-value properties into JavaScript Array objects. - * - * @param qname QName of the property value for conversion - * @param value Property value - * - * @return Value safe for scripting usage - */ - public Serializable convertValueForScript(QName qname, Serializable value) - { - return convertValueForScript(services, scope, qname, value); - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.repo.jscript.ValueConverter#convertValueForScript(org.alfresco.service.ServiceRegistry, - * org.mozilla.javascript.Scriptable, org.alfresco.service.namespace.QName, java.io.Serializable) - */ - @Override - public Serializable convertValueForScript(ServiceRegistry services, Scriptable scope, QName qname, - Serializable value) - { - if (value instanceof ContentData) - { - // ContentData object properties are converted to ScriptContentData objects - // so the content and other properties of those objects can be accessed - value = new ScriptContentData((ContentData) value, qname); - } - else - { - value = super.convertValueForScript(services, scope, qname, value); - } - return value; - } - - /* - * (non-Javadoc) - * - * @see org.alfresco.repo.jscript.ValueConverter#convertValueForRepo(java.io.Serializable) - */ - @Override - public Serializable convertValueForRepo(Serializable value) - { - if (value instanceof ScriptContentData) - { - // convert back to ContentData - value = ((ScriptContentData) value).contentData; - } - else - { - value = super.convertValueForRepo(value); - } - return value; - } - } - - - // ------------------------------------------------------------------------------ - // Inner Classes - - /** - * Inner class wrapping and providing access to a ContentData property - */ - public class ScriptContentData implements Serializable - { - private static final long serialVersionUID = -7819328543933312278L; - - /** - * Constructor - * - * @param contentData The ContentData object this object wraps - * @param property The property the ContentData is attached too - */ - public ScriptContentData(ContentData contentData, QName property) - { - this.contentData = contentData; - this.property = property; - } - - /** - * @return the content stream - */ - public String getContent() - { - ContentService contentService = services.getContentService(); - ContentReader reader = contentService.getReader(nodeRef, property); - - return (reader != null && reader.exists()) ? reader.getContentString() : ""; - } - - public String jsGet_content() - { - return getContent(); - } - - /** - * Set the content stream - * - * @param content Content string to set - */ - public void setContent(String content) - { - ContentService contentService = services.getContentService(); - ContentWriter writer = contentService.getWriter(nodeRef, this.property, true); - writer.setMimetype(getMimetype()); // use existing mimetype value - writer.putContent(content); - - // update cached variables after putContent() - this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property); - } - - public void jsSet_content(String content) - { - setContent(content); - } - - /** - * @return download URL to the content - */ - public String getUrl() - { - try - { - return MessageFormat.format(CONTENT_PROP_URL, new Object[] { nodeRef.getStoreRef().getProtocol(), - nodeRef.getStoreRef().getIdentifier(), nodeRef.getId(), - StringUtils.replace(URLEncoder.encode(getName(), "UTF-8"), "+", "%20"), - StringUtils.replace(URLEncoder.encode(property.toString(), "UTF-8"), "+", "%20") }); - } - catch (UnsupportedEncodingException err) - { - throw new AlfrescoRuntimeException("Failed to encode content URL for node: " + nodeRef, err); - } - } - - public String jsGet_url() - { - return getUrl(); - } - - public long getSize() - { - return contentData.getSize(); - } - - public long jsGet_size() - { - return getSize(); - } - - public String getMimetype() - { - return contentData.getMimetype(); - } - - public String jsGet_mimetype() - { - return getMimetype(); - } - - public void setMimetype(String mimetype) - { - this.contentData = ContentData.setMimetype(this.contentData, mimetype); - services.getNodeService().setProperty(nodeRef, this.property, this.contentData); - - // update cached variables after putContent() - this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property); - } - - public void jsSet_mimetype(String mimetype) - { - setMimetype(mimetype); - } - - private ContentData contentData; - - private QName property; - } - - /** - * Interface contract for simple anonymous classes that implement document transformations - */ - private interface Transformer - { - /** - * Transform the reader to the specified writer - * - * @param contentService ContentService - * @param noderef NodeRef of the destination for the transform - * @param reader Source reader - * @param writer Destination writer - * - * @return Node representing the transformed entity - */ - Node transform(ContentService contentService, NodeRef noderef, ContentReader reader, ContentWriter writer); - } + } + + public String jsGet_url() + { + return getUrl(); + } + + public long getSize() + { + return contentData.getSize(); + } + + public long jsGet_size() + { + return getSize(); + } + + public String getMimetype() + { + return contentData.getMimetype(); + } + + public String jsGet_mimetype() + { + return getMimetype(); + } + + public void setMimetype(String mimetype) + { + this.contentData = ContentData.setMimetype(this.contentData, mimetype); + services.getNodeService().setProperty(nodeRef, this.property, this.contentData); + + // update cached variables after putContent() + this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property); + } + + public void jsSet_mimetype(String mimetype) + { + setMimetype(mimetype); + } + + private ContentData contentData; + + private QName property; + } + + /** + * Interface contract for simple anonymous classes that implement document transformations + */ + private interface Transformer + { + /** + * Transform the reader to the specified writer + * + * @param contentService ContentService + * @param noderef NodeRef of the destination for the transform + * @param reader Source reader + * @param writer Destination writer + * + * @return Node representing the transformed entity + */ + Node transform(ContentService contentService, NodeRef noderef, ContentReader reader, ContentWriter writer); + } } \ No newline at end of file