/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see .
*/
package org.alfresco.repo.jscript;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ApplicationModel;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.executer.TransformActionExecuter;
import org.alfresco.repo.content.transform.magick.ImageTransformationOptions;
import org.alfresco.repo.search.QueryParameterDefImpl;
import org.alfresco.repo.tagging.script.TagScope;
import org.alfresco.repo.thumbnail.CreateThumbnailActionExecuter;
import org.alfresco.repo.thumbnail.ThumbnailDefinition;
import org.alfresco.repo.thumbnail.ThumbnailRegistry;
import org.alfresco.repo.thumbnail.script.ScriptThumbnail;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.version.VersionModel;
import org.alfresco.repo.workflow.jscript.JscriptWorkflowInstance;
import org.alfresco.scripts.ScriptException;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.lock.LockStatus;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.FileNotFoundException;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.TemplateImageResolver;
import org.alfresco.service.cmr.search.QueryParameterDefinition;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.thumbnail.ThumbnailService;
import org.alfresco.service.cmr.version.Version;
import org.alfresco.service.cmr.version.VersionHistory;
import org.alfresco.service.cmr.version.VersionType;
import org.alfresco.service.cmr.workflow.WorkflowInstance;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.namespace.NamespacePrefixResolver;
import org.alfresco.service.namespace.NamespacePrefixResolverProvider;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.util.GUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.UniqueTag;
import org.mozilla.javascript.Wrapper;
import org.springframework.extensions.surf.util.Content;
import org.springframework.extensions.surf.util.ParameterCheck;
import org.springframework.extensions.surf.util.URLEncoder;
/**
* Script Node class implementation, specific for use by ScriptService as part of the object model.
*
* The class exposes Node properties, children and assocs as dynamically populated maps and lists. The various collection classes are mirrored as JavaScript properties. So can be
* accessed using standard JavaScript property syntax, such as node.children[0].properties.name.
*
* Various helper methods are provided to access common and useful node variables such as the content url and type information.
*
* @author Kevin Roast
*/
public class ScriptNode implements Serializable, Scopeable, NamespacePrefixResolverProvider
{
private static final long serialVersionUID = -3378946227712939601L;
private static Log logger = LogFactory.getLog(ScriptNode.class);
private final static String NAMESPACE_BEGIN = "" + QName.NAMESPACE_BEGIN;
private final static String CONTENT_DEFAULT_URL = "/d/d/{0}/{1}/{2}/{3}";
private final static String CONTENT_DOWNLOAD_URL = "/d/a/{0}/{1}/{2}/{3}";
private final static String CONTENT_PROP_URL = "/d/d/{0}/{1}/{2}/{3}?property={4}";
private final static String CONTENT_DOWNLOAD_PROP_URL = "/d/a/{0}/{1}/{2}/{3}?property={4}";
private final static String FOLDER_BROWSE_URL = "/n/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;
protected String siteName;
protected boolean siteNameResolved = false;
/** The aspects applied to this node */
protected Set aspects = null;
/** The target associations from this node */
private ScriptableQNameMap targetAssocs = null;
/** The source associations to this node */
private ScriptableQNameMap sourceAssocs = null;
/** The child associations for this node */
private ScriptableQNameMap childAssocs = null;
/** The children of this node */
private Scriptable children = null;
/** The properties of this node */
private ScriptableQNameMap properties = null;
/** The versions of this node */
private Scriptable versions = null;
/** The active workflows acting on this node */
private Scriptable activeWorkflows = null;
protected ServiceRegistry services = null;
private NodeService nodeService = null;
private Boolean isDocument = null;
private Boolean isContainer = null;
private Boolean isLinkToDocument = null;
private Boolean isLinkToContainer = null;
private String displayPath = null;
protected TemplateImageResolver imageResolver = null;
protected ScriptNode parent = null;
private ChildAssociationRef primaryParentAssoc = null;
private ScriptableQNameMap parentAssocs = 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
*/
public ScriptNode(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 scope Root scope for this Node
*/
public ScriptNode(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;
if (!nodeRef.equals(((ScriptNode)obj).nodeRef)) return false;
return true;
}
/**
* Factory method
*/
public ScriptNode newInstance(NodeRef nodeRef, ServiceRegistry services, Scriptable scope)
{
return new ScriptNode(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;
}
/**
* @return the store type for the node
*/
public String getStoreType()
{
return this.nodeRef.getStoreRef().getProtocol();
}
/**
* @return the store id for the node
*/
public String getStoreId()
{
return this.nodeRef.getStoreRef().getIdentifier();
}
/**
* @return Returns the NodeRef this Node object represents
*/
public NodeRef getNodeRef()
{
return this.nodeRef;
}
/**
* @return Returns the QName type.
*/
public QName getQNameType()
{
if (this.type == null)
{
this.type = this.nodeService.getType(this.nodeRef);
}
return type;
}
/**
* @return Returns the type.
*/
public String getType()
{
return getQNameType().toString();
}
/**
* @return Returns the type in short format.
*/
public String getTypeShort()
{
return this.getShortQName(getQNameType());
}
/**
* @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)
{
this.name = parentRef.getQName().getLocalName();
}
else
{
this.name = "";
}
}
}
return this.name;
}
/**
* Helper to set the 'name' property for the node.
*
* @param name Name to set
*/
public void setName(String name)
{
if (name != null)
{
QName typeQName = getQNameType();
if ((services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_FOLDER) &&
!services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_SYSTEM_FOLDER)) ||
services.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_CONTENT))
{
try
{
this.services.getFileFolderService().rename(this.nodeRef, name);
}
catch (FileNotFoundException e)
{
throw new AlfrescoRuntimeException("Failed to rename node " + nodeRef + " to " + name, e);
}
}
this.getProperties().put(ContentModel.PROP_NAME.toString(), name.toString());
}
}
/**
* @return The children of this Node as JavaScript array of Node object wrappers
*/
public Scriptable getChildren()
{
if (this.children == null)
{
List childRefs = this.nodeService.getChildAssocs(this.nodeRef);
Object[] children = new Object[childRefs.size()];
for (int i = 0; i < childRefs.size(); i++)
{
// create our Node representation from the NodeRef
children[i] = newInstance(childRefs.get(i).getChildRef(), this.services, this.scope);
}
this.children = Context.getCurrentContext().newArray(this.scope, children);
}
return this.children;
}
/**
* childByNamePath 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");
*
* is a leading / required? No, but it can be specified.
* are wild-cards supported? Does not seem to be used anywhere
*
* @return The ScriptNode or null if the node is not found.
*/
public ScriptNode childByNamePath(String path)
{
ScriptNode child = null;
if (this.services.getDictionaryService().isSubClass(getQNameType(), ContentModel.TYPE_FOLDER))
{
/**
* The current node is a folder.
* optimized code path for cm:folder and sub-types supporting getChildrenByName() method
*/
NodeRef result = null;
StringTokenizer t = new StringTokenizer(path, "/");
if (t.hasMoreTokens())
{
result = this.nodeRef;
while (t.hasMoreTokens() && result != null)
{
String name = t.nextToken();
List results = this.nodeService.getChildrenByName(
result, ContentModel.ASSOC_CONTAINS, Collections.singletonList(name));
result = (results.size() > 0 ? results.get(0).getChildRef() : null);
}
}
child = (result != null ? newInstance(result, this.services, this.scope) : null);
}
else
{
/**
* The current node is not a folder. Perhaps it is Company Home ?
*/
// convert the name based path to a valid XPath query
StringBuilder xpath = new StringBuilder(path.length() << 1);
StringTokenizer t = new StringTokenizer(path, "/");
int count = 0;
QueryParameterDefinition[] params = new QueryParameterDefinition[t.countTokens()];
DataTypeDefinition ddText =
this.services.getDictionaryService().getDataType(DataTypeDefinition.TEXT);
NamespaceService ns = this.services.getNamespaceService();
while (t.hasMoreTokens())
{
if (xpath.length() != 0)
{
xpath.append('/');
}
String strCount = Integer.toString(count);
xpath.append("*[@cm:name=$cm:name")
.append(strCount)
.append(']');
params[count++] = new QueryParameterDefImpl(
QName.createQName(NamespaceService.CONTENT_MODEL_PREFIX, "name" + strCount, ns),
ddText,
true,
t.nextToken());
}
Object[] nodes = getChildrenByXPath(xpath.toString(), params, true);
child = (nodes.length != 0) ? (ScriptNode)nodes[0] : null;
}
return child;
}
/**
* @return Returns a JavaScript array of Nodes at the specified XPath starting at this Node.
* So a valid call might be mynode.childrenByXPath("*[@cm:name='Testing']/*");
*/
public Scriptable childrenByXPath(String xpath)
{
return Context.getCurrentContext().newArray(this.scope, getChildrenByXPath(xpath, null, false));
}
/**
* @return Returns a JavaScript array of child file/folder nodes for this nodes.
* Automatically retrieves all sub-types of cm:content and cm:folder, also removes
* system folder types from the results.
* This is equivalent to @see FileFolderService.list()
*/
public Scriptable childFileFolders()
{
return childFileFolders(true, true, null);
}
/**
* @param files Return files extending from cm:content
* @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder
* @return Returns a JavaScript array of child file/folder nodes for this nodes.
* Automatically retrieves all sub-types of cm:content and cm:folder, also removes
* system folder types from the results.
* This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders()
*/
public Scriptable childFileFolders(boolean files, boolean folders)
{
return childFileFolders(files, folders, null);
}
/**
* @param files Return files extending from cm:content
* @param folders Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder
* @return Returns a JavaScript array of child file/folder nodes for this nodes.
* Automatically retrieves all sub-types of cm:content and cm:folder, also removes
* system folder types from the results.
* Also optionally removes additional type qnames. The additional type can be
* specified in short or long qname string form as a single string or an Array e.g. "fm:forum".
* This is equivalent to @see FileFolderService.listFiles() and @see FileFolderService.listFolders()
*/
public Scriptable childFileFolders(boolean files, boolean folders, Object ignoreTypes)
{
Object[] results;
// Build a list of file and folder types
DictionaryService dd = services.getDictionaryService();
Set searchTypeQNames = new HashSet(16, 1.0f);
if (folders)
{
Collection qnames = dd.getSubTypes(ContentModel.TYPE_FOLDER, true);
searchTypeQNames.addAll(qnames);
searchTypeQNames.add(ContentModel.TYPE_FOLDER);
}
if (files)
{
Collection qnames = dd.getSubTypes(ContentModel.TYPE_CONTENT, true);
searchTypeQNames.addAll(qnames);
searchTypeQNames.add(ContentModel.TYPE_CONTENT);
qnames = dd.getSubTypes(ContentModel.TYPE_LINK, true);
searchTypeQNames.addAll(qnames);
searchTypeQNames.add(ContentModel.TYPE_LINK);
}
// Remove 'system' folder types
Collection qnames = dd.getSubTypes(ContentModel.TYPE_SYSTEM_FOLDER, true);
searchTypeQNames.removeAll(qnames);
searchTypeQNames.remove(ContentModel.TYPE_SYSTEM_FOLDER);
// Add user defined types to ignore from the search
if (ignoreTypes instanceof ScriptableObject)
{
Serializable types = getValueConverter().convertValueForRepo((ScriptableObject)ignoreTypes);
if (types instanceof List)
{
for (Serializable typeObj : (List)types)
{
searchTypeQNames.remove(createQName(typeObj.toString()));
}
}
else if (types instanceof String)
{
searchTypeQNames.remove(createQName(types.toString()));
}
}
else if (ignoreTypes instanceof String)
{
searchTypeQNames.remove(createQName(ignoreTypes.toString()));
}
// Perform the query and collect the results
if (searchTypeQNames.size() != 0)
{
List childAssocRefs = this.nodeService.getChildAssocs(this.nodeRef, searchTypeQNames);
results = new Object[childAssocRefs.size()];
for (int i=0; inode.assocs["translations"][0]
*
* @return target associations as a Map of assoc name to a JavaScript array of Nodes.
*/
@SuppressWarnings("unchecked")
public Map getAssocs()
{
if (this.targetAssocs == null)
{
// this Map implements the Scriptable interface for native JS syntax property access
this.targetAssocs = new ScriptableQNameMap(this);
// get the list of target nodes for each association type
List refs = this.nodeService.getTargetAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL);
for (AssociationRef ref : refs)
{
String qname = ref.getTypeQName().toString();
List nodes = (List)this.targetAssocs.get(qname);
if (nodes == null)
{
// first access of the list for this qname
nodes = new ArrayList(4);
}
this.targetAssocs.put(ref.getTypeQName().toString(), nodes);
nodes.add(newInstance(ref.getTargetRef(), this.services, this.scope));
}
// convert each Node list into a JavaScript array object
for (String qname : this.targetAssocs.keySet())
{
List nodes = (List)this.targetAssocs.get(qname);
Object[] objs = nodes.toArray(new Object[nodes.size()]);
this.targetAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs));
}
}
return this.targetAssocs;
}
public Map getAssociations()
{
return getAssocs();
}
/**
* Return the source associations to this Node. As a Map of assoc name to a JavaScript array of Nodes.
* The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript
* associative array access. This means source associations to this node can be access thus:
* node.sourceAssocs["translations"][0]
*
* @return source associations as a Map of assoc name to a JavaScript array of Nodes.
*/
@SuppressWarnings("unchecked")
public Map getSourceAssocs()
{
if (this.sourceAssocs == null)
{
// this Map implements the Scriptable interface for native JS syntax property access
this.sourceAssocs = new ScriptableQNameMap(this);
// get the list of source nodes for each association type
List refs = this.nodeService.getSourceAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL);
for (AssociationRef ref : refs)
{
String qname = ref.getTypeQName().toString();
List nodes = (List)this.sourceAssocs.get(qname);
if (nodes == null)
{
// first access of the list for this qname
nodes = new ArrayList(4);
this.sourceAssocs.put(ref.getTypeQName().toString(), nodes);
}
nodes.add(newInstance(ref.getSourceRef(), this.services, this.scope));
}
// convert each Node list into a JavaScript array object
for (String qname : this.sourceAssocs.keySet())
{
List nodes = (List)this.sourceAssocs.get(qname);
Object[] objs = nodes.toArray(new Object[nodes.size()]);
this.sourceAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs));
}
}
return this.sourceAssocs;
}
public Map getSourceAssociations()
{
return getSourceAssocs();
}
/**
* Return the child associations from this Node. As a Map of assoc name to a JavaScript array of Nodes.
* The Map returned implements the Scriptable interface to allow access to the assoc arrays via JavaScript
* associative array access. This means associations of this node can be access thus:
* node.childAssocs["contains"][0]
*
* @return child associations as a Map of assoc name to a JavaScript array of Nodes.
*/
@SuppressWarnings("unchecked")
public Map getChildAssocs()
{
if (this.childAssocs == null)
{
// this Map implements the Scriptable interface for native JS syntax property access
this.childAssocs = new ScriptableQNameMap(this);
// get the list of child assoc nodes for each association type
List refs = this.nodeService.getChildAssocs(nodeRef);
for (ChildAssociationRef ref : refs)
{
String qname = ref.getTypeQName().toString();
List nodes = (List)this.childAssocs.get(qname);
if (nodes == null)
{
// first access of the list for this qname
nodes = new ArrayList(4);
this.childAssocs.put(ref.getTypeQName().toString(), nodes);
}
nodes.add(newInstance(ref.getChildRef(), this.services, this.scope));
}
// convert each Node list into a JavaScript array object
for (String qname : this.childAssocs.keySet())
{
List nodes = (List)this.childAssocs.get(qname);
Object[] objs = nodes.toArray(new Object[nodes.size()]);
this.childAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs));
}
}
return this.childAssocs;
}
public Map getChildAssociations()
{
return getChildAssocs();
}
/**
* Return an Array of the associations from this Node that match a specific object type.
* node.getChildAssocsByType("cm:folder")[0]
*
* @return Array of child associations from this Node that match a specific object type.
*/
@SuppressWarnings("unchecked")
public Scriptable getChildAssocsByType(String type)
{
// get the list of child assoc nodes for each association type
Set types = new HashSet(1, 1.0f);
types.add(createQName(type));
List refs = this.nodeService.getChildAssocs(this.nodeRef, types);
Object[] nodes = new Object[refs.size()];
for (int i=0; inode.parentAssocs["contains"][0]
*
* @return parent associations as a Map of assoc name to a JavaScript array of Nodes.
*/
@SuppressWarnings("unchecked")
public Map getParentAssocs()
{
if (this.parentAssocs == null)
{
// this Map implements the Scriptable interface for native JS syntax property access
this.parentAssocs = new ScriptableQNameMap(this);
// get the list of child assoc nodes for each association type
List refs = this.nodeService.getParentAssocs(nodeRef);
for (ChildAssociationRef ref : refs)
{
String qname = ref.getTypeQName().toString();
List nodes = (List)this.parentAssocs.get(qname);
if (nodes == null)
{
// first access of the list for this qname
nodes = new ArrayList(4);
this.parentAssocs.put(ref.getTypeQName().toString(), nodes);
}
nodes.add(newInstance(ref.getParentRef(), this.services, this.scope));
}
// convert each Node list into a JavaScript array object
for (String qname : this.parentAssocs.keySet())
{
List nodes = (List)this.parentAssocs.get(qname);
Object[] objs = nodes.toArray(new Object[nodes.size()]);
this.parentAssocs.put(qname, Context.getCurrentContext().newArray(this.scope, objs));
}
}
return this.parentAssocs;
}
public Map getParentAssociations()
{
return getParentAssocs();
}
/**
* 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 impl of the QNameMap is capable of creating ScriptContentData on demand for 'cm:content'
// properties that have not been initialised - see AR-1673.
this.properties = new ContentAwareScriptableQNameMap(this, this.services);
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;
}
/**
* Return all the property names defined for this node's type as an array of short QNames.
*
* @return Array of property names for this node's type.
*/
public Scriptable getTypePropertyNames()
{
return getTypePropertyNames(true);
}
/**
* Return all the property names defined for this node's type as an array.
*
* @param useShortQNames if true short-form qnames will be returned, else long-form.
* @return Array of property names for this node's type.
*/
public Scriptable getTypePropertyNames(boolean useShortQNames)
{
Set props = this.services.getDictionaryService().getClass(this.getQNameType()).getProperties().keySet();
Object[] result = new Object[props.size()];
int count = 0;
for (QName qname : props)
{
result[count++] = useShortQNames ? getShortQName(qname).toString() : qname.toString();
}
return Context.getCurrentContext().newArray(this.scope, result);
}
/**
* Return all the property names defined for this node as an array.
*
* @param useShortQNames if true short-form qnames will be returned, else long-form.
* @return Array of property names for this node type and optionally parent properties.
*/
public Scriptable getPropertyNames(boolean useShortQNames)
{
Set props = this.nodeService.getProperties(this.nodeRef).keySet();
Object[] result = new Object[props.size()];
int count = 0;
for (QName qname : props)
{
result[count++] = useShortQNames ? getShortQName(qname).toString() : qname.toString();
}
return Context.getCurrentContext().newArray(this.scope, result);
}
/**
* @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(getQNameType(), ContentModel.TYPE_FOLDER) == true &&
dd.isSubClass(getQNameType(), ContentModel.TYPE_SYSTEM_FOLDER) == false));
}
return isContainer.booleanValue();
}
/**
* @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(getQNameType(), ContentModel.TYPE_CONTENT));
}
return isDocument.booleanValue();
}
/**
* @return true if this Node is a Link to a Container (i.e. a folderlink)
*/
public boolean getIsLinkToContainer()
{
if (isLinkToContainer == null)
{
DictionaryService dd = this.services.getDictionaryService();
isLinkToContainer = Boolean.valueOf(dd.isSubClass(getQNameType(), ApplicationModel.TYPE_FOLDERLINK));
}
return isLinkToContainer.booleanValue();
}
/**
* @return true if this Node is a Link to a Document (i.e. a filelink)
*/
public boolean getIsLinkToDocument()
{
if (isLinkToDocument == null)
{
DictionaryService dd = this.services.getDictionaryService();
isLinkToDocument = Boolean.valueOf(dd.isSubClass(getQNameType(), ApplicationModel.TYPE_FILELINK));
}
return isLinkToDocument.booleanValue();
}
/**
* @return true if the Node is a Category
*/
public boolean getIsCategory()
{
// this valid is overriden by the CategoryNode sub-class
return false;
}
/**
* @return The list of aspects applied to this node
*/
public Set getAspectsSet()
{
if (this.aspects == null)
{
this.aspects = this.nodeService.getAspects(this.nodeRef);
}
return this.aspects;
}
/**
* @return The array of aspects applied to this node
*/
public Scriptable getAspects()
{
Set aspects = getAspectsSet();
Object[] result = new Object[aspects.size()];
int count = 0;
for (QName qname : aspects)
{
result[count++] = qname.toString();
}
return Context.getCurrentContext().newArray(this.scope, result);
}
/**
* @param aspect The aspect name to test for (fully qualified or short-name form)
* @return true if the node has the aspect false otherwise
*/
public boolean hasAspect(String aspect)
{
return getAspectsSet().contains(createQName(aspect));
}
/**
* @param type The qname type to test this object against (fully qualified or short-name form)
* @return true if this Node is a sub-type of the specified class (or itself of that class)
*/
public boolean isSubType(String type)
{
ParameterCheck.mandatoryString("Type", type);
QName qnameType = createQName(type);
return this.services.getDictionaryService().isSubClass(getQNameType(), qnameType);
}
/**
* @return QName path to this node. This can be used for Lucene PATH: style queries
*/
public String getQnamePath()
{
return this.services.getNodeService().getPath(getNodeRef()).toPrefixString(this.services.getNamespaceService());
}
/**
* @return Display path to this node
*/
public String getDisplayPath()
{
if (displayPath == null)
{
displayPath = this.nodeService.getPath(this.nodeRef).toDisplayPath(
this.nodeService, this.services.getPermissionService());
}
return displayPath;
}
/**
* @return the small icon image for this node
*/
public String getIcon16()
{
return "/images/filetypes/_default.gif";
}
/**
* @return the large icon image for this node
*/
public String getIcon32()
{
return "/images/filetypes32/_default.gif";
}
/**
* @return true if the node is currently locked
*/
public boolean getIsLocked()
{
boolean locked = false;
if (getAspectsSet().contains(ContentModel.ASPECT_LOCKABLE))
{
LockStatus lockStatus = this.services.getLockService().getLockStatus(this.nodeRef);
if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER)
{
locked = true;
}
}
return locked;
}
/**
* @return the primary parent node
*/
public ScriptNode 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;
}
/**
* @return all parent nodes
*/
public Scriptable getParents()
{
List parentRefs = this.nodeService.getParentAssocs(this.nodeRef);
Object[] parents = new Object[parentRefs.size()];
for (int i = 0; i < parentRefs.size(); i++)
{
NodeRef ref = parentRefs.get(i).getParentRef();
parents[i] = newInstance(ref, this.services, this.scope);
}
return Context.getCurrentContext().newArray(this.scope, parents);
}
/**
* @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;
}
// ------------------------------------------------------------------------------
// 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;
}
/**
* 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)
{
contentData.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)
{
return MessageFormat.format(CONTENT_DEFAULT_URL, new Object[] { nodeRef.getStoreRef().getProtocol(),
nodeRef.getStoreRef().getIdentifier(), nodeRef.getId(),
URLEncoder.encode(getName())});
}
else
{
return MessageFormat.format(FOLDER_BROWSE_URL, new Object[] { nodeRef.getStoreRef().getProtocol(),
nodeRef.getStoreRef().getIdentifier(), nodeRef.getId() });
}
}
/**
* @return For a content document, this method returns the download URL to the content for
* the default content property (@see ContentModel.PROP_CONTENT)
*
* For a container node, this method returns an empty string
*/
public String getDownloadUrl()
{
if (getIsDocument() == true)
{
return MessageFormat.format(CONTENT_DOWNLOAD_URL, new Object[] {
nodeRef.getStoreRef().getProtocol(),
nodeRef.getStoreRef().getIdentifier(),
nodeRef.getId(),
URLEncoder.encode(getName()) });
}
else
{
return "";
}
}
public String jsGet_downloadUrl()
{
return getDownloadUrl();
}
/**
* @return The WebDav cm:name based path to the content for the default content property
* (@see ContentModel.PROP_CONTENT)
*/
public String getWebdavUrl()
{
try
{
if (getIsContainer() || getIsDocument())
{
List paths = this.services.getFileFolderService().getNamePath(null, getNodeRef());
// build up the webdav url
StringBuilder path = new StringBuilder(128);
path.append("/webdav");
// build up the path skipping the first path as it is the root folder
for (int i=1; i
* 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 Array of permissions applied to this Node, including inherited.
* Strings returned are of the format [ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION for example
* ALLOWED;kevinr;Consumer so can be easily tokenized on the ';' character.
*/
public Scriptable getPermissions()
{
return Context.getCurrentContext().newArray(this.scope, retrieveAllSetPermissions(false, false));
}
/**
* @return Array of permissions applied directly to this Node (does not include inherited).
* Strings returned are of the format [ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION for example
* ALLOWED;kevinr;Consumer so can be easily tokenized on the ';' character.
*/
public Scriptable getDirectPermissions()
{
return Context.getCurrentContext().newArray(this.scope, retrieveAllSetPermissions(true, false));
}
/**
* @return Array of all permissions applied to this Node, including inherited.
* Strings returned are of the format [ALLOWED|DENIED];[USERNAME|GROUPNAME];PERMISSION;[INHERITED|DIRECT]
* for example: ALLOWED;kevinr;Consumer;DIRECT so can be easily tokenized on the ';' character.
*/
public Scriptable getFullPermissions()
{
return Context.getCurrentContext().newArray(this.scope, retrieveAllSetPermissions(false, true));
}
/**
* Helper to construct the response object for the various getPermissions() calls.
*
* @param direct True to only retrieve direct permissions, false to get inherited also
* @param full True to retrieve full data string with [INHERITED|DIRECT] element
* This exists to maintain backward compatibility with existing permission APIs.
*
* @return Object[] of packed permission strings.
*/
private Object[] retrieveAllSetPermissions(boolean direct, boolean full)
{
Set acls = this.services.getPermissionService().getAllSetPermissions(getNodeRef());
List