mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	128168 adavis: MNT-16381 Transformers must validate the content stream mimetype
      - Addition of AbstractContentTransformer2.strictMimeTypeCheck(...) to check that the
        declared mimetype is the same as the Tika derived mimetype. Controlled by a new alfresco
        global property content.transformer.strict.mimetype.check (set to true by default).
      - Added the UnsupportedTransformationException to a number of transformation callers as
        they should have been handling it already.
      - Modified DifferrentMimeTypeTest to test the new strictMimeTypeCheck.
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@129272 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
		
	
		
			
				
	
	
		
			4139 lines
		
	
	
		
			158 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			4139 lines
		
	
	
		
			158 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| /*
 | |
|  * #%L
 | |
|  * Alfresco Repository
 | |
|  * %%
 | |
|  * Copyright (C) 2005 - 2016 Alfresco Software Limited
 | |
|  * %%
 | |
|  * This file is part of the Alfresco software. 
 | |
|  * If the software was purchased under a paid Alfresco license, the terms of 
 | |
|  * the paid license agreement will prevail.  Otherwise, the software is 
 | |
|  * provided under the following open source license terms:
 | |
|  * 
 | |
|  * 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 <http://www.gnu.org/licenses/>.
 | |
|  * #L%
 | |
|  */
 | |
| package org.alfresco.repo.jscript;
 | |
| 
 | |
| import java.io.BufferedInputStream;
 | |
| 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.Collator;
 | |
| import java.text.MessageFormat;
 | |
| import java.util.ArrayList;
 | |
| import java.util.Arrays;
 | |
| import java.util.Collection;
 | |
| import java.util.Collections;
 | |
| import java.util.Comparator;
 | |
| import java.util.Date;
 | |
| 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.opencmis.CMISConnector;
 | |
| import org.alfresco.query.PagingRequest;
 | |
| import org.alfresco.query.PagingResults;
 | |
| import org.alfresco.repo.action.executer.TransformActionExecuter;
 | |
| import org.alfresco.repo.content.MimetypeMap;
 | |
| import org.alfresco.repo.content.transform.UnimportantTransformException;
 | |
| import org.alfresco.repo.content.transform.UnsupportedTransformationException;
 | |
| import org.alfresco.repo.content.transform.magick.ImageTransformationOptions;
 | |
| import org.alfresco.repo.model.filefolder.FileFolderServiceImpl.InvalidTypeException;
 | |
| import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery;
 | |
| import org.alfresco.repo.search.QueryParameterDefImpl;
 | |
| import org.alfresco.repo.security.authentication.AuthenticationUtil;
 | |
| import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
 | |
| import org.alfresco.repo.tagging.script.TagScope;
 | |
| import org.alfresco.repo.thumbnail.ThumbnailDefinition;
 | |
| import org.alfresco.repo.thumbnail.ThumbnailHelper;
 | |
| import org.alfresco.repo.thumbnail.ThumbnailRegistry;
 | |
| import org.alfresco.repo.thumbnail.script.ScriptThumbnail;
 | |
| import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
 | |
| import org.alfresco.repo.transaction.RetryingTransactionHelper;
 | |
| import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
 | |
| 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.dictionary.PropertyDefinition;
 | |
| import org.alfresco.service.cmr.lock.LockStatus;
 | |
| import org.alfresco.service.cmr.model.FileExistsException;
 | |
| import org.alfresco.service.cmr.model.FileFolderService;
 | |
| 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.NoTransformerException;
 | |
| 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.TemplateImageResolver;
 | |
| import org.alfresco.service.cmr.repository.TransformationOptions;
 | |
| import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
 | |
| 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.NamespaceException;
 | |
| 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.FileFilterMode;
 | |
| import org.alfresco.util.FileFilterMode.Client;
 | |
| import org.alfresco.util.GUID;
 | |
| import org.alfresco.util.ISO8601DateFormat;
 | |
| import org.alfresco.util.ISO9075;
 | |
| import org.alfresco.util.Pair;
 | |
| 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.I18NUtil;
 | |
| 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.
 | |
|  * <p>
 | |
|  * 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 <code>node.children[0].properties.name</code>.
 | |
|  * <p>
 | |
|  * 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 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 FileInfo nodeInfo;
 | |
|     
 | |
|     private String name;
 | |
|     private QName type;
 | |
|     protected String id;
 | |
|     protected String siteName;
 | |
|     protected boolean siteNameResolved = false;
 | |
|     
 | |
|     /** The aspects applied to this node */
 | |
|     protected Set<QName> aspects = null;
 | |
|     
 | |
|     /** The target associations from this node */
 | |
|     private ScriptableQNameMap<String, Object> targetAssocs = null;
 | |
|     
 | |
|     /** The source associations to this node */
 | |
|     private ScriptableQNameMap<String, Object> sourceAssocs = null;
 | |
|     
 | |
|     /** The child associations for this node */
 | |
|     private ScriptableQNameMap<String, Object> childAssocs = null;
 | |
|     
 | |
|     /** The children of this node */
 | |
|     private Scriptable children = null;
 | |
|     
 | |
|     /** The properties of this node */
 | |
|     private ScriptableQNameMap<String, Serializable> 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 FileFolderService fileFolderService = null;
 | |
|     private RetryingTransactionHelper retryingTransactionHelper = null;
 | |
|     private Boolean isDocument = null;
 | |
|     private Boolean isContainer = null;
 | |
|     private Boolean isLinkToDocument = null;
 | |
|     private Boolean isLinkToContainer = null;
 | |
|     private Boolean hasChildren = null;
 | |
|     private String displayPath = null;
 | |
|     private String qnamePath = null;
 | |
|     protected TemplateImageResolver imageResolver = null;
 | |
|     protected ScriptNode parent = null;
 | |
|     private ChildAssociationRef primaryParentAssoc = null;
 | |
|     private ScriptableQNameMap<String, Object> 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 nodeInfo  The FileInfo this Node wrapper represents
 | |
|      * @param services  The ServiceRegistry the Node can use to access services
 | |
|      * @param scope     Root scope for this Node
 | |
|      */
 | |
|     public ScriptNode(FileInfo nodeInfo, ServiceRegistry services, Scriptable scope)
 | |
|     {
 | |
|         this(nodeInfo.getNodeRef(), services, scope);
 | |
|         
 | |
|         this.nodeInfo = nodeInfo;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * 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.fileFolderService = services.getFileFolderService();
 | |
|         this.retryingTransactionHelper = services.getTransactionService().getRetryingTransactionHelper();
 | |
|         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);
 | |
|     }
 | |
|     
 | |
|     public ScriptNode newInstance(FileInfo nodeInfo, ServiceRegistry services, Scriptable scope)
 | |
|     {
 | |
|         return new ScriptNode(nodeInfo, 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<ChildAssociationRef> 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);
 | |
|             }
 | |
| 
 | |
|             // Do a locale-sensitive sort by name
 | |
|             sort(children);
 | |
| 
 | |
|             this.children = Context.getCurrentContext().newArray(this.scope, children);
 | |
|             this.hasChildren = (children.length != 0);
 | |
|         }
 | |
|         
 | |
|         return this.children;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Performs a locale-sensitive sort by name of a node array
 | |
|      * @param nodes the node array
 | |
|      */
 | |
|     private static void sort(Object[] nodes)
 | |
|     {
 | |
|         final Collator col = Collator.getInstance(I18NUtil.getLocale());
 | |
|         Arrays.sort(nodes, new Comparator<Object>(){
 | |
|             @Override
 | |
|             public int compare(Object o1, Object o2)
 | |
|             {
 | |
|                 return col.compare(((ScriptNode)o1).getName(), ((ScriptNode)o2).getName());
 | |
|             }});        
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @return true if the Node has children
 | |
|      */
 | |
|     public boolean getHasChildren()
 | |
|     {
 | |
|         if (this.hasChildren == null)
 | |
|         {
 | |
|             this.hasChildren = !this.services.getNodeService().getChildAssocs(
 | |
|                   getNodeRef(), RegexQNamePattern.MATCH_ALL, RegexQNamePattern.MATCH_ALL, false).isEmpty();
 | |
|         }
 | |
|         return hasChildren;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * childByNamePath returns the Node at the specified 'cm:name' based Path walking the children of this Node.
 | |
|      *         So a valid call might be:
 | |
|      *         <code>mynode.childByNamePath("/QA/Testing/Docs");</code>
 | |
|      *         
 | |
|      * @param path the relative path of the descendant node to find e.g. {@code "/QA/Testing/Docs"}
 | |
|      * @return The ScriptNode or {@code null} if the node is not found.
 | |
|      *         {@code null} if the specified path is {@code ""}.
 | |
|      * @throws NullPointerException if the provided path is {@code null}.
 | |
|      */
 | |
|     public ScriptNode childByNamePath(String path)
 | |
|     {
 | |
|         // Ensure that paths that do not represent descendants are not needlessly tokenised. See ALF-20896.
 | |
|         if (path == null)        { throw new NullPointerException("Illegal null path"); }
 | |
|         else if (path.isEmpty()) { return null; }
 | |
|         
 | |
|         // We have a path worth looking at...
 | |
|         ScriptNode child = null;
 | |
|         
 | |
|         if (this.services.getDictionaryService().isSubClass(getQNameType(), ContentModel.TYPE_FOLDER))
 | |
|         {
 | |
|             // The current node is a folder e.g. Company Home and standard child folders.
 | |
|             // optimized code path for cm:folder and sub-types supporting getChildrenByName() method
 | |
|             final StringTokenizer t = new StringTokenizer(path, "/");
 | |
|             // allow traversal of a cm:name based path even if user cannot retrieve the node directly
 | |
|             NodeRef result = AuthenticationUtil.runAs(new RunAsWork<NodeRef>()
 | |
|             {
 | |
|                 @Override
 | |
|                 public NodeRef doWork() throws Exception
 | |
|                 {
 | |
|                     NodeRef child = ScriptNode.this.nodeRef;
 | |
|                     while (t.hasMoreTokens() && child != null)
 | |
|                     {
 | |
|                         String name = t.nextToken();
 | |
|                         child = nodeService.getChildByName(child, ContentModel.ASSOC_CONTAINS, name);
 | |
|                     }
 | |
|                     return child;
 | |
|                 }
 | |
|             }, AuthenticationUtil.getSystemUserName());
 | |
| 
 | |
|             // final node must be accessible to the user via the usual ACL permission checks
 | |
|             if (result != null
 | |
|                     && services.getPublicServiceAccessService().hasAccess("NodeService", "getProperties", result) != AccessStatus.ALLOWED)
 | |
|             {
 | |
|                 result = null;
 | |
|             }
 | |
|             
 | |
|             child = (result != null ? newInstance(result, this.services, this.scope) : null);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // The current node is not a folder. It does not support the cm:contains association.
 | |
|             // 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 <code>mynode.childrenByXPath("*[@cm:name='Testing']/*");</code>
 | |
|      */
 | |
|     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
 | |
|      * @param ignoreTypes   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".
 | |
|      * 
 | |
|      * @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, Object ignoreTypes)
 | |
|     {
 | |
|         return childFileFolders(files, folders, ignoreTypes, -1, -1, 0, null, null, null).getPage();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @param files         Return files extending from cm:content
 | |
|      * @param folders       Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder
 | |
|      * @param ignoreTypes   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".
 | |
|      * @param maxItems      Max number of items
 | |
|      *                      
 | |
|      * @return Returns ScriptPagingNodes which includes 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()
 | |
|      *         
 | |
|      * @deprecated API for review (subject to change prior to release)
 | |
|      *
 | |
|      * <br>author janv
 | |
|      * @since 4.0
 | |
|      */
 | |
|     public ScriptPagingNodes childFileFolders(boolean files, boolean folders, Object ignoreTypes, int maxItems)
 | |
|     {
 | |
|         return childFileFolders(files, folders, ignoreTypes, 0, maxItems, 0, null, null, null);
 | |
|     }
 | |
|     
 | |
|     @SuppressWarnings("unchecked")
 | |
|     /**
 | |
|      * @param files                Return files extending from cm:content
 | |
|      * @param folders              Return folders extending from cm:folder - ignoring sub-types of cm:systemfolder
 | |
|      * @param ignoreTypes          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".
 | |
|      * @param skipOffset           Items to skip (e.g. 0 or (num pages to skip * size of page)
 | |
|      * @param maxItems             Max number of items (eg. size of page)
 | |
|      * @param requestTotalCountMax Request total count (upto a given max total count)
 | |
|      *                             Note: if 0 then total count is not requested and the query may be able to optimise/cutoff for max items)
 | |
|      * @param sortProp             Optional sort property as a prefix qname string (e.g. "cm:name"). Also supports special 
 | |
|      *                             content case (i.e. "cm:content.size" and "cm:content.mimetype")
 | |
|      * @param sortAsc              Given a sort property, true => ascending, false => descending
 | |
|      * @param queryExecutionId     If paging then can pass back the previous query execution (as a hint for possible query optimisation)
 | |
|      *                             
 | |
|      * @return Returns ScriptPagingNodes which includes 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()
 | |
|      *         
 | |
|      * <br/><br/>author janv
 | |
|      * @since 4.0
 | |
|      */
 | |
|     public ScriptPagingNodes childFileFolders(boolean files, boolean folders, Object ignoreTypes, int skipOffset, int maxItems, int requestTotalCountMax, String sortProp, Boolean sortAsc, String queryExecutionId)
 | |
|     {
 | |
|         Object[] results;
 | |
|         
 | |
|         Set<QName> ignoreTypeQNames = new HashSet<QName>(5);
 | |
|         
 | |
|         // Add user defined types to ignore
 | |
|         if (ignoreTypes instanceof ScriptableObject)
 | |
|         {
 | |
|             Serializable types = getValueConverter().convertValueForRepo((ScriptableObject)ignoreTypes);
 | |
|             if (types instanceof List)
 | |
|             {
 | |
|                 for (Serializable typeObj : (List<Serializable>)types)
 | |
|                 {
 | |
|                     ignoreTypeQNames.add(createQName(typeObj.toString()));
 | |
|                 }
 | |
|             }
 | |
|             else if (types instanceof String)
 | |
|             {
 | |
|                 ignoreTypeQNames.add(createQName(types.toString()));
 | |
|             }
 | |
|         }
 | |
|         else if (ignoreTypes instanceof String)
 | |
|         {
 | |
|             ignoreTypeQNames.add(createQName(ignoreTypes.toString()));
 | |
|         }
 | |
|         
 | |
|         // ALF-13968 - sort folders before files (for Share) - TODO should be optional sort param
 | |
|         List<Pair<QName, Boolean>> sortProps = new ArrayList<Pair<QName, Boolean>>(2);
 | |
|         if ((sortProp == null) || (! sortProp.equals(GetChildrenCannedQuery.SORT_QNAME_NODE_TYPE.getLocalName())))
 | |
|         {
 | |
|             sortProps.add(new Pair<QName, Boolean>(GetChildrenCannedQuery.SORT_QNAME_NODE_IS_FOLDER, false));
 | |
|         }
 | |
|         if (sortProp != null)
 | |
|         {
 | |
|             sortProps.add(new Pair<QName, Boolean>(createQName(sortProp), sortAsc));
 | |
|         }
 | |
|         
 | |
|         PagingRequest pageRequest = new PagingRequest(skipOffset, maxItems, queryExecutionId);
 | |
|         pageRequest.setRequestTotalCountMax(requestTotalCountMax);
 | |
|         
 | |
|         PagingResults<FileInfo> pageOfNodeInfos = null;
 | |
|         FileFilterMode.setClient(Client.script);
 | |
|         try
 | |
|         {
 | |
|             pageOfNodeInfos = this.fileFolderService.list(this.nodeRef, files, folders, null, ignoreTypeQNames, sortProps, pageRequest);
 | |
|         }
 | |
|         finally
 | |
|         {
 | |
|             FileFilterMode.clearClient();
 | |
|         }
 | |
| 
 | |
|         List<FileInfo> nodeInfos = pageOfNodeInfos.getPage();
 | |
|         int size = nodeInfos.size();
 | |
|         results = new Object[size];
 | |
|         for (int i=0; i<size; i++)
 | |
|         {
 | |
|             FileInfo nodeInfo = nodeInfos.get(i);
 | |
|             results[i] = newInstance(nodeInfo, this.services, this.scope);
 | |
|         }
 | |
|         
 | |
|         int totalResultCountLower = -1;
 | |
|         int totalResultCountUpper = -1;
 | |
|         
 | |
|         Pair<Integer, Integer> totalResultCount = pageOfNodeInfos.getTotalResultCount();
 | |
|         if (totalResultCount != null)
 | |
|         {
 | |
|             totalResultCountLower = (totalResultCount.getFirst() != null ? totalResultCount.getFirst() : -1);
 | |
|             totalResultCountUpper = (totalResultCount.getSecond() != null ? totalResultCount.getSecond() : -1);
 | |
|         }
 | |
|         
 | |
|         return new ScriptPagingNodes(Context.getCurrentContext().newArray(this.scope, results), pageOfNodeInfos.hasMoreItems(), totalResultCountLower, totalResultCountUpper);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Return the target associations from this Node. As a Map of assoc type 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:
 | |
|      * <code>node.assocs["translations"][0]</code>
 | |
|      * 
 | |
|      * @return target associations as a Map of assoc name to a JavaScript array of Nodes.
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public Map<String, Object> getAssocs()
 | |
|     {
 | |
|         if (this.targetAssocs == null)
 | |
|         {
 | |
|             // this Map implements the Scriptable interface for native JS syntax property access
 | |
|             this.targetAssocs = new ScriptableQNameMap<String, Object>(this);
 | |
| 
 | |
|             // get the list of target nodes for each association type
 | |
|             List<AssociationRef> refs = this.nodeService.getTargetAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL);
 | |
|             for (AssociationRef ref : refs)
 | |
|             {
 | |
|                 String qname = ref.getTypeQName().toString();
 | |
|                 List<ScriptNode> nodes = (List<ScriptNode>)this.targetAssocs.get(qname);
 | |
|                 if (nodes == null)
 | |
|                 {
 | |
|                     // first access of the list for this qname
 | |
|                     nodes = new ArrayList<ScriptNode>(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<ScriptNode> nodes = (List<ScriptNode>)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<String, Object> 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:
 | |
|      * <code>node.sourceAssocs["translations"][0]</code>
 | |
|      * 
 | |
|      * @return source associations as a Map of assoc name to a JavaScript array of Nodes.
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public Map<String, Object> getSourceAssocs()
 | |
|     {
 | |
|         if (this.sourceAssocs == null)
 | |
|         {
 | |
|             // this Map implements the Scriptable interface for native JS syntax property access
 | |
|             this.sourceAssocs = new ScriptableQNameMap<String, Object>(this);
 | |
| 
 | |
|             // get the list of source nodes for each association type
 | |
|             List<AssociationRef> refs = this.nodeService.getSourceAssocs(this.nodeRef, RegexQNamePattern.MATCH_ALL);
 | |
|             for (AssociationRef ref : refs)
 | |
|             {
 | |
|                 String qname = ref.getTypeQName().toString();
 | |
|                 List<ScriptNode> nodes = (List<ScriptNode>)this.sourceAssocs.get(qname);
 | |
|                 if (nodes == null)
 | |
|                 {
 | |
|                     // first access of the list for this qname
 | |
|                     nodes = new ArrayList<ScriptNode>(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<ScriptNode> nodes = (List<ScriptNode>)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<String, Object> 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:
 | |
|      * <code>node.childAssocs["contains"][0]</code>
 | |
|      * 
 | |
|      * @return child associations as a Map of assoc name to a JavaScript array of Nodes.
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public Map<String, Object> getChildAssocs()
 | |
|     {
 | |
|         if (this.childAssocs == null)
 | |
|         {
 | |
|             // this Map implements the Scriptable interface for native JS syntax property access
 | |
|             this.childAssocs = new ScriptableQNameMap<String, Object>(this);
 | |
|             
 | |
|             // get the list of child assoc nodes for each association type
 | |
|             List<ChildAssociationRef> refs = this.nodeService.getChildAssocs(nodeRef);
 | |
|             for (ChildAssociationRef ref : refs)
 | |
|             {
 | |
|                 String qname = ref.getTypeQName().toString();
 | |
|                 List<ScriptNode> nodes = (List<ScriptNode>)this.childAssocs.get(qname);
 | |
|                 if (nodes == null)
 | |
|                 {
 | |
|                     // first access of the list for this qname
 | |
|                     nodes = new ArrayList<ScriptNode>(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<ScriptNode> nodes = (List<ScriptNode>)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<String, Object> getChildAssociations()
 | |
|     {
 | |
|         return getChildAssocs();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Return an Array of the associations from this Node that match a specific object type.
 | |
|      * <code>node.getChildAssocsByType("cm:folder")[0]</code>
 | |
|      * 
 | |
|      * @return Array of child associations from this Node that match a specific object type.
 | |
|      */
 | |
|     public Scriptable getChildAssocsByType(String type)
 | |
|     {
 | |
|         // get the list of child assoc nodes for each association type
 | |
|         Set<QName> types = new HashSet<QName>(1, 1.0f);
 | |
|         types.add(createQName(type));
 | |
|         List<ChildAssociationRef> refs = this.nodeService.getChildAssocs(this.nodeRef, types);
 | |
|         Object[] nodes = new Object[refs.size()];
 | |
|         for (int i=0; i<nodes.length; i++)
 | |
|         {
 | |
|             ChildAssociationRef ref = refs.get(i);
 | |
|             nodes[i] = newInstance(ref.getChildRef(), this.services, this.scope);
 | |
|         }
 | |
|         return Context.getCurrentContext().newArray(this.scope, nodes);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Return the parent 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 associations of this node can be access thus:
 | |
|      * <code>node.parentAssocs["contains"][0]</code>
 | |
|      * 
 | |
|      * @return parent associations as a Map of assoc name to a JavaScript array of Nodes.
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public Map<String, Object> getParentAssocs()
 | |
|     {
 | |
|         if (this.parentAssocs == null)
 | |
|         {
 | |
|             // this Map implements the Scriptable interface for native JS syntax property access
 | |
|             this.parentAssocs = new ScriptableQNameMap<String, Object>(this);
 | |
|             
 | |
|             // get the list of child assoc nodes for each association type
 | |
|             List<ChildAssociationRef> refs = this.nodeService.getParentAssocs(nodeRef);
 | |
|             for (ChildAssociationRef ref : refs)
 | |
|             {
 | |
|                 String qname = ref.getTypeQName().toString();
 | |
|                 List<ScriptNode> nodes = (List<ScriptNode>)this.parentAssocs.get(qname);
 | |
|                 if (nodes == null)
 | |
|                 {
 | |
|                     // first access of the list for this qname
 | |
|                     nodes = new ArrayList<ScriptNode>(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<ScriptNode> nodes = (List<ScriptNode>)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<String, Object> getParentAssociations()
 | |
|     {
 | |
|         return getParentAssocs();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Checks whether the {@link ScriptNode} exists in the repository.
 | |
|      * @return boolean
 | |
|      */
 | |
|     public boolean exists()
 | |
|     {
 | |
|         return nodeService.exists(nodeRef);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * 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: <code>node.properties["name"]</code>
 | |
|      * 
 | |
|      * @return Map of properties for this Node.
 | |
|      */
 | |
|     @SuppressWarnings("unchecked")
 | |
|     public Map<String, Object> 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<String, Serializable>(this, this.services);
 | |
|             
 | |
|             Map<QName, Serializable> props = null;
 | |
|             if (this.nodeInfo != null)
 | |
|             {
 | |
|                 props = this.nodeInfo.getProperties();
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 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<QName> 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<QName> 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<QName> getAspectsSet()
 | |
|     {
 | |
|         if (this.aspects == null)
 | |
|         {
 | |
|             this.aspects = this.nodeService.getAspects(this.nodeRef);
 | |
|         }
 | |
|         
 | |
|         return this.aspects;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @return The array of aspects applied to this node as fully qualified qname strings
 | |
|      */
 | |
|     public Scriptable getAspects()
 | |
|     {
 | |
|         Set<QName> 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);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @return The array of aspects applied to this node as short prefix qname strings
 | |
|      */
 | |
|     public Scriptable getAspectsShort()
 | |
|     {
 | |
|         final NamespaceService ns = this.services.getNamespaceService();
 | |
|         final Map<String, String> cache = new HashMap<String, String>();
 | |
|         final Set<QName> aspects = getAspectsSet();
 | |
|         final Object[] result = new Object[aspects.size()];
 | |
|         int count = 0;
 | |
|         for (final QName qname : aspects)
 | |
|         {
 | |
|             String prefix = cache.get(qname.getNamespaceURI());
 | |
|             if (prefix == null)
 | |
|             {
 | |
|                 // first request for this namespace prefix, get and cache result
 | |
|                 Collection<String> prefixes = ns.getPrefixes(qname.getNamespaceURI());
 | |
|                 prefix = prefixes.size() != 0 ? prefixes.iterator().next() : "";
 | |
|                 cache.put(qname.getNamespaceURI(), prefix);
 | |
|             }
 | |
|             result[count++] = prefix + QName.NAMESPACE_PREFIX + qname.getLocalName();
 | |
|         }
 | |
|         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()
 | |
|     {
 | |
|         if (this.qnamePath == null)
 | |
|         {
 | |
|             final NamespaceService ns = this.services.getNamespaceService();
 | |
|             final Map<String, String> cache = new HashMap<String, String>();
 | |
|             final StringBuilder buf = new StringBuilder(128);
 | |
|             final Path path = this.services.getNodeService().getPath(getNodeRef());
 | |
|             for (final Path.Element e : path)
 | |
|             {
 | |
|                 if (e instanceof Path.ChildAssocElement)
 | |
|                 {
 | |
|                     final QName qname = ((Path.ChildAssocElement)e).getRef().getQName();
 | |
|                     if (qname != null)
 | |
|                     {
 | |
|                         String prefix = cache.get(qname.getNamespaceURI());
 | |
|                         if (prefix == null)
 | |
|                         {
 | |
|                             // first request for this namespace prefix, get and cache result
 | |
|                             Collection<String> prefixes = ns.getPrefixes(qname.getNamespaceURI());
 | |
|                             prefix = prefixes.size() != 0 ? prefixes.iterator().next() : "";
 | |
|                             cache.put(qname.getNamespaceURI(), prefix);
 | |
|                         }
 | |
|                         buf.append('/').append(prefix).append(':').append(ISO9075.encode(qname.getLocalName()));
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     buf.append('/').append(e.toString());
 | |
|                 }
 | |
|             }
 | |
|             this.qnamePath = buf.toString();
 | |
|         }
 | |
|         
 | |
|         return this.qnamePath;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @return Display path to this node
 | |
|      */
 | |
|     public String getDisplayPath()
 | |
|     {
 | |
|         if (this.displayPath == null)
 | |
|         {
 | |
|             this.displayPath = this.nodeService.getPath(this.nodeRef).toDisplayPath(
 | |
|                     this.nodeService, this.services.getPermissionService());
 | |
|         }
 | |
|         
 | |
|         return this.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 status = this.services.getLockService().getLockStatus(this.nodeRef);
 | |
|             if (status == LockStatus.LOCKED || status == 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<ChildAssociationRef> 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)
 | |
|      *         <p>
 | |
|      *         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)
 | |
|      *         <p>
 | |
|      *         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()
 | |
|     {
 | |
|         String url = "";
 | |
|         try
 | |
|         {
 | |
|             if (getIsContainer() || getIsDocument())
 | |
|             {
 | |
|                 List<String> paths = this.services.getFileFolderService().getNameOnlyPath(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<paths.size(); i++)
 | |
|                 {
 | |
|                     path.append("/")
 | |
|                         .append(URLEncoder.encode(paths.get(i)));
 | |
|                 }
 | |
|                 url = path.toString();
 | |
|             }
 | |
|         }
 | |
|         catch (InvalidTypeException typeErr)
 | |
|         {
 | |
|             // cannot build path if file is a type such as a rendition
 | |
|         }
 | |
|         catch (FileNotFoundException nodeErr)
 | |
|         {
 | |
|             // cannot build path if file no longer exists
 | |
|         }
 | |
|         return url;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * @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;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * 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);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @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;
 | |
|     }
 | |
|     
 | |
|     
 | |
|     // ------------------------------------------------------------------------------
 | |
|     // Security API
 | |
|     
 | |
|     /**
 | |
|      * Return true if the user has the specified permission on the node.
 | |
|      * <p>
 | |
|      * The default permissions are found in <code>org.alfresco.service.cmr.security.PermissionService</code>.
 | |
|      * Most commonly used are "Write", "Delete" and "AddChildren".
 | |
|      * 
 | |
|      * @param permission as found in <code>org.alfresco.service.cmr.security.PermissionService</code>
 | |
|      * @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));
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @return Sorted list of <code>AccessPermission</code> based on <code>CMISConnector.AccessPermissionComparator</code>
 | |
|      *         and <code>AccessStatus</code> of the permission for an authority.
 | |
|      */
 | |
|     public static List<AccessPermission> getSortedACLs(Set<AccessPermission> acls)
 | |
|     {
 | |
|         ArrayList<AccessPermission> ordered = new ArrayList<AccessPermission>(acls);
 | |
|         Map<String, AccessPermission> deDuplicatedPermissions = new HashMap<String, AccessPermission>(acls.size());
 | |
|         Collections.sort(ordered, new CMISConnector.AccessPermissionComparator());
 | |
|         for (AccessPermission current : ordered)
 | |
|         {
 | |
|             String composedKey = current.getAuthority() + current.getPermission();
 | |
|             if (current.getAccessStatus() == AccessStatus.ALLOWED)
 | |
|             {
 | |
|                 deDuplicatedPermissions.put(composedKey, current);
 | |
|             }
 | |
|             else if (current.getAccessStatus() == AccessStatus.DENIED)
 | |
|             {
 | |
|                 deDuplicatedPermissions.remove(composedKey);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return new ArrayList<AccessPermission>(deDuplicatedPermissions.values());
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * 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.
 | |
|      */
 | |
|     protected Object[] retrieveAllSetPermissions(boolean direct, boolean full)
 | |
|     {
 | |
|         Set<AccessPermission> acls = this.services.getPermissionService().getAllSetPermissions(getNodeRef());
 | |
|         List<Object> permissions = new ArrayList<Object>(acls.size());
 | |
|         List<AccessPermission> ordered = getSortedACLs(acls);
 | |
|         for (AccessPermission permission : ordered)
 | |
|         {
 | |
|             if (!direct || permission.isSetDirectly())
 | |
|             {
 | |
|                 StringBuilder buf = new StringBuilder(64);
 | |
|                 buf.append(permission.getAccessStatus())
 | |
|                     .append(';')
 | |
|                     .append(permission.getAuthority())
 | |
|                     .append(';')
 | |
|                     .append(permission.getPermission());
 | |
|                 if (full)
 | |
|                 {
 | |
|                     buf.append(';').append(permission.isSetDirectly() ? "DIRECT" : "INHERITED");
 | |
|                 }
 | |
|                 permissions.add(buf.toString());
 | |
|             }
 | |
|         }
 | |
|         return (Object[])permissions.toArray(new Object[permissions.size()]);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @return Array of settable permissions for this Node
 | |
|      */
 | |
|     public Scriptable getSettablePermissions()
 | |
|     {
 | |
|         Set<String> permissions = this.services.getPermissionService().getSettablePermissions(getNodeRef());
 | |
|         Object[] result = permissions.toArray(new Object[0]);
 | |
|         return Context.getCurrentContext().newArray(this.scope, result);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * @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);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Set whether this node should inherit permissions from the parent node. If the operation takes 
 | |
|      * too long and asyncCall parameter set accordingly, fixed ACLs method will be asynchronously called.
 | |
|      * 
 | |
|      * @param inherit True to inherit parent permissions, false otherwise.
 | |
|      * @param asyncCall True if fixed ACLs should be asynchronously set when operation execution takes too long, false otherwise.
 | |
|      */
 | |
|     public void setInheritsPermissions(boolean inherit, boolean asyncCall)
 | |
|     {
 | |
|         this.services.getPermissionService().setInheritParentPermissions(this.nodeRef, inherit, asyncCall);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * 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)
 | |
|     {
 | |
|         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 String
 | |
|      */
 | |
|     public String getOwner()
 | |
|     {
 | |
|         return this.services.getOwnableService().getOwner(this.nodeRef);
 | |
|     }
 | |
|     
 | |
|     
 | |
|     // ------------------------------------------------------------------------------
 | |
|     // Create and Modify API
 | |
|     
 | |
|     /**
 | |
|      * Persist the modified properties of this Node.
 | |
|      */
 | |
|     public void save()
 | |
|     {
 | |
|         // persist properties back to the node in the DB
 | |
|         Map<QName, Serializable> props = new HashMap<QName, Serializable>(getProperties().size());
 | |
|         for (String key : this.properties.keySet())
 | |
|         {
 | |
|             Serializable value = (Serializable) this.properties.get(key);
 | |
|             
 | |
|             QName qname = createQName(key);
 | |
|             
 | |
|             // MNT-15798
 | |
|             if (ContentModel.PROP_CONTENT.equals(qname) && isScriptContent(value))
 | |
|             {
 | |
|                 ScriptContentData contentData = (ScriptContentData) value;
 | |
|                 // Do not persist the contentData if it was not touched
 | |
|                 if (!contentData.isDirty())
 | |
|                 {
 | |
|                     continue;
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             // perform the conversion from script wrapper object to repo serializable values
 | |
|             value = getValueConverter().convertValueForRepo(value);
 | |
|             
 | |
|             props.put(qname, 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 (getQNameType().equals(qnameType) == false &&
 | |
|                 this.services.getDictionaryService().isSubClass(qnameType, getQNameType()) == true)
 | |
|         {
 | |
|             // Specialise the type of the node
 | |
|             this.nodeService.setType(this.nodeRef, qnameType);
 | |
|             this.type = qnameType;
 | |
|             
 | |
|             return true;
 | |
|         }
 | |
|         return false;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Create a new File (cm:content) node as a child of this node.
 | |
|      * <p>
 | |
|      * Once created the file should have content set using the <code>content</code> property.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *    
 | |
|      * @param name Name of the file to create
 | |
|      * 
 | |
|      * @return Newly created Node or null if failed to create.
 | |
|      */
 | |
|     public ScriptNode createFile(String name)
 | |
|     {
 | |
|         return createFile(name, null);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Create a new File (cm:content) node as a child of this node.
 | |
|      * <p>
 | |
|      * Once created the file should have content set using the <code>content</code> property.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      * 
 | |
|      * @param name Name of the file to create
 | |
|      * @param type Type of the file to create (if null, defaults to ContentModel.TYPE_CONTENT) 
 | |
|      * 
 | |
|      * @return Newly created Node or null if failed to create.
 | |
|      */
 | |
|     public ScriptNode createFile(String name, String type)
 | |
|     {
 | |
|         ParameterCheck.mandatoryString("Node Name", name);
 | |
|         
 | |
|         FileInfo fileInfo = this.services.getFileFolderService().create(
 | |
|                 this.nodeRef, name, type == null ? ContentModel.TYPE_CONTENT : createQName(type));
 | |
|         
 | |
|         reset();
 | |
|         
 | |
|         ScriptNode file = newInstance(fileInfo.getNodeRef(), this.services, this.scope);
 | |
|         file.setMimetype(this.services.getMimetypeService().guessMimetype(name));
 | |
|         
 | |
|         return file;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Create a new folder (cm:folder) node as a child of this node.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *    
 | |
|      * @param name Name of the folder to create
 | |
|      * 
 | |
|      * @return Newly created Node or null if failed to create.
 | |
|      */
 | |
|     public ScriptNode createFolder(String name)
 | |
|     {
 | |
|         return createFolder(name, null);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create a new folder (cm:folder) node as a child of this node.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *    
 | |
|      * @param name Name of the folder to create
 | |
|      * @param type Type of the folder to create (if null, defaults to ContentModel.TYPE_FOLDER)
 | |
|      * 
 | |
|      * @return Newly created Node or null if failed to create.
 | |
|      */
 | |
|     public ScriptNode createFolder(String name, String type)
 | |
|     {
 | |
|         ParameterCheck.mandatoryString("Node Name", name);
 | |
|         
 | |
|         FileInfo fileInfo = this.services.getFileFolderService().create(
 | |
|                 this.nodeRef, name, type == null ? ContentModel.TYPE_FOLDER : createQName(type));
 | |
|         
 | |
|         reset();
 | |
|         
 | |
|         return newInstance(fileInfo.getNodeRef(), this.services, this.scope);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create a new Node of the specified type as a child of this node.
 | |
|      * 
 | |
|      * @param name Name of the node to create (can be null for a node without a 'cm:name' property)
 | |
|      * @param type QName type (fully qualified or short form such as 'cm:content')
 | |
|      * 
 | |
|      * @return Newly created Node or null if failed to create.
 | |
|      */
 | |
|     public ScriptNode createNode(String name, String type)
 | |
|     {
 | |
|         return createNode(name, type, null, ContentModel.ASSOC_CONTAINS.toString());
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Create a new Node of the specified type as a child of this node.
 | |
|      * 
 | |
|      * @param name Name of the node to create (can be null for a node without a 'cm:name' property)
 | |
|      * @param type QName type (fully qualified or short form such as 'cm:content')
 | |
|      * @param assocType QName of the child association type (fully qualified or short form e.g. 'cm:contains')
 | |
|      * 
 | |
|      * @return Newly created Node or null if failed to create.
 | |
|      */
 | |
|     public ScriptNode createNode(String name, String type, String assocType)
 | |
|     {
 | |
|         return createNode(name, type, null, assocType);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Create a new Node of the specified type as a child of this node.
 | |
|      * 
 | |
|      * @param name Name of the node to create (can be null for a node without a 'cm:name' property)
 | |
|      * @param type QName type (fully qualified or short form such as 'cm:content')
 | |
|      * @param properties Associative array of the default properties for the node.
 | |
|      * 
 | |
|      * @return Newly created Node or null if failed to create.
 | |
|      */
 | |
|     public ScriptNode createNode(String name, String type, Object properties)
 | |
|     {
 | |
|         return createNode(name, type, properties, ContentModel.ASSOC_CONTAINS.toString());
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Create a new Node of the specified type as a child of this node.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *    
 | |
|      * @param name Name of the node to create (can be null for a node without a 'cm:name' property)
 | |
|      * @param type QName type (fully qualified or short form such as 'cm:content')
 | |
|      * @param properties Associative array of the default properties for the node.
 | |
|      * @param assocType QName of the child association type (fully qualified or short form e.g. 'cm:contains')
 | |
|      * 
 | |
|      * @return Newly created Node or null if failed to create.
 | |
|      */
 | |
|     public ScriptNode createNode(String name, String type, Object properties, String assocType)
 | |
|     {
 | |
|         return createNode(name, type, properties, assocType, null);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Create a new Node of the specified type as a child of this node.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *    
 | |
|      * @param name Name of the node to create (can be null for a node without a 'cm:name' property)
 | |
|      * @param type QName type (fully qualified or short form such as 'cm:content')
 | |
|      * @param properties Associative array of the default properties for the node.
 | |
|      * @param assocType QName of the child association type (fully qualified or short form e.g. 'cm:contains')
 | |
|      * @param assocName QName of the child association name (fully qualified or short form e.g. 'fm:discussion')
 | |
|      * 
 | |
|      * @return Newly created Node or null if failed to create.
 | |
|      */
 | |
|     public ScriptNode createNode(String name, String type, Object properties, String assocType, String assocName)
 | |
|     {
 | |
|         ParameterCheck.mandatoryString("Node Type", type);
 | |
|         ParameterCheck.mandatoryString("Association Type", assocType);
 | |
|         
 | |
|         Map<QName, Serializable> props = null;
 | |
|         
 | |
|         if (properties instanceof ScriptableObject)
 | |
|         {
 | |
|             props = new HashMap<QName, Serializable>(4, 1.0f);
 | |
|             extractScriptableProperties((ScriptableObject)properties, props);
 | |
|         }
 | |
|         
 | |
|         if (name != null)
 | |
|         {
 | |
|             if (props == null) props = new HashMap<QName, Serializable>(1, 1.0f);
 | |
|             props.put(ContentModel.PROP_NAME, name);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             // set name for the assoc local name
 | |
|             name = GUID.generate();
 | |
|         }
 | |
|         
 | |
|         ChildAssociationRef childAssocRef = this.nodeService.createNode(
 | |
|                 this.nodeRef,
 | |
|                 createQName(assocType),
 | |
|                 assocName == null ?
 | |
|                      QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name)) :
 | |
|                      createQName(assocName),
 | |
|                 createQName(type),
 | |
|                 props);
 | |
|         
 | |
|         reset();
 | |
|         
 | |
|         return newInstance(childAssocRef.getChildRef(), this.services, this.scope);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Creates a new secondary association between the current node and the specified child node.   
 | |
|      * The association is given the same name as the child node's primary association.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *     
 | |
|      * @param node  node to add as a child of this node
 | |
|      */
 | |
|     public void addNode(ScriptNode node)
 | |
|     {
 | |
|         ParameterCheck.mandatory("node", node);
 | |
|         ChildAssociationRef childAssocRef = this.nodeService.getPrimaryParent(node.nodeRef);
 | |
|         nodeService.addChild(this.nodeRef, node.nodeRef, ContentModel.ASSOC_CONTAINS, childAssocRef.getQName());
 | |
|         reset();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Remove an existing child node of this node.
 | |
|      *
 | |
|      * Severs all parent-child relationships between two nodes.
 | |
|      * <p>
 | |
|      * The child node will be cascade deleted if one of the associations was the
 | |
|      * primary association, i.e. the one with which the child node was created.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *    
 | |
|      * @param node  child node to remove
 | |
|      */
 | |
|     public void removeNode(ScriptNode node)
 | |
|     {
 | |
|         ParameterCheck.mandatory("node", node);
 | |
|         nodeService.removeChild(this.nodeRef, node.nodeRef);
 | |
|         reset();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Create an association between this node and the specified target node.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *     
 | |
|      * @param target        Destination node for the association
 | |
|      * @param assocType     Association type qname (short form or fully qualified)
 | |
|      */
 | |
|     public Association createAssociation(ScriptNode target, String assocType)
 | |
|     {
 | |
|         ParameterCheck.mandatory("Target", target);
 | |
|         ParameterCheck.mandatoryString("Association Type Name", assocType);
 | |
|         
 | |
|         AssociationRef assocRef = this.nodeService.createAssociation(this.nodeRef, target.nodeRef, createQName(assocType));
 | |
|         reset();
 | |
|         return new Association(this.services, assocRef);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Remove an association between this node and the specified target node.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *    
 | |
|      * @param target        Destination node on the end of the association
 | |
|      * @param assocType     Association type qname (short form or fully qualified)
 | |
|      */
 | |
|     public void removeAssociation(ScriptNode target, String assocType)
 | |
|     {
 | |
|         ParameterCheck.mandatory("Target", target);
 | |
|         ParameterCheck.mandatoryString("Association Type Name", assocType);
 | |
|         
 | |
|         this.nodeService.removeAssociation(this.nodeRef, target.nodeRef, createQName(assocType));
 | |
|         reset();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Remove this node. Any references to this Node or its NodeRef should be
 | |
|      * discarded!
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called. To
 | |
|      * preserve property changes call {@link save()} first.
 | |
|      * 
 | |
|      */
 | |
|     public boolean remove()
 | |
|     {
 | |
|         return remove(false);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Remove this node in a new transaction or not as specified.
 | |
|      * Any references to this Node or its NodeRef should be discarded!
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called. To
 | |
|      * preserve property changes call {@link save()} first.
 | |
|      * 
 | |
|      */
 | |
|     public boolean remove(boolean newTransaction)
 | |
|     {
 | |
|         boolean success = false;
 | |
|         
 | |
|         if (nodeService.exists(this.nodeRef))
 | |
|         {
 | |
|             retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Void>()
 | |
|             {
 | |
|                 @Override
 | |
|                 public Void execute() throws Throwable
 | |
|                 {
 | |
|                     nodeService.deleteNode(nodeRef);
 | |
|                     return null;
 | |
|                 }
 | |
|             }, false, newTransaction);
 | |
|             success = true;
 | |
|         }
 | |
|         
 | |
|         reset();
 | |
|         
 | |
|         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 ScriptNode copy(ScriptNode destination)
 | |
|     {
 | |
|         ScriptNode copy = copy(destination, false);
 | |
| 
 | |
|         // ALF-9517 fix
 | |
|         if (copy != null && copy.hasAspect(ContentModel.ASPECT_VERSIONABLE.toString()))
 | |
|         {
 | |
|             copy.ensureVersioningEnabled(true, true);
 | |
|         }
 | |
|         return copy;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * 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 ScriptNode copy(ScriptNode destination, boolean deepCopy)
 | |
|     {
 | |
|         ParameterCheck.mandatory("Destination Node", destination);
 | |
|         
 | |
|         NodeRef copyRef = this.services.getCopyService().copyAndRename(this.nodeRef, destination.getNodeRef(),
 | |
|                 ContentModel.ASSOC_CONTAINS, null, deepCopy);
 | |
|         ScriptNode copy = newInstance(copyRef, this.services, this.scope);
 | |
|         
 | |
|         return copy;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Revert this Node to the specified version. Note this is not a deep revert of
 | |
|      * associations.
 | |
|      * This node must have the cm:versionable aspect. It will be checked out if required
 | |
|      * but will be checked in after the call.
 | |
|      * 
 | |
|      * @param versionLabel to revert from
 | |
|      * 
 | |
|      * @return the original Node that was checked out if reverted, {@code null} otherwise
 | |
|      *         (if the version does not exist).
 | |
|      */
 | |
|     public ScriptNode revert(String history, boolean majorVersion, String versionLabel)
 | |
|     {
 | |
|         return revert(history, majorVersion, versionLabel, false);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Revert this Node to the specified version and potentially all child nodes.
 | |
|      * This node must have the cm:versionable aspect. It will be checked out if required
 | |
|      * but will be checked in after the call.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *    
 | |
|      * @param history       Version history note
 | |
|      * @param majorVersion  True to save as a major version increment, false for minor version.
 | |
|      * @param versionLabel to revert from
 | |
|      * @param deep          {@code true} for a deep revert, {@code false} otherwise.
 | |
|      * 
 | |
|      * @return the original Node that was checked out if reverted, {@code null} otherwise
 | |
|      *         (if the version does not exist).
 | |
|      */
 | |
|     public ScriptNode revert(String history, boolean majorVersion, String versionLabel, boolean deep)
 | |
|     {
 | |
|         if (!getIsVersioned())
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
|         
 | |
|         // Get the Version - needed to do the revert
 | |
|         Version version = services.getVersionService().getVersionHistory(nodeRef).getVersion(versionLabel);
 | |
|         if (version == null)
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         ScriptNode originalNode = this;
 | |
|         //cancel editing if we want to revert 
 | |
|         if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY))
 | |
|         {
 | |
|             originalNode = cancelCheckout();
 | |
|         }
 | |
|         
 | |
|         // Revert the new (current) version of the node
 | |
|         services.getVersionService().revert(originalNode.getNodeRef(), version, deep);
 | |
|         
 | |
|         // Checkout/Checkin the node - to store the new version in version history
 | |
|         ScriptNode workingCopy = originalNode.checkout();
 | |
|         originalNode = workingCopy.checkin(history, majorVersion);
 | |
|         
 | |
|         
 | |
|         return originalNode;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Move this Node to a new parent destination.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *    
 | |
|      * @param destination   Node
 | |
|      * 
 | |
|      * @return true on successful move, false on failure to move.
 | |
|      */
 | |
|     public boolean move(ScriptNode destination)
 | |
|     {
 | |
|         ParameterCheck.mandatory("Destination Node", destination);
 | |
|         
 | |
|         this.primaryParentAssoc = this.nodeService.moveNode(this.nodeRef, destination.getNodeRef(),
 | |
|                 ContentModel.ASSOC_CONTAINS, getPrimaryParentAssoc().getQName());
 | |
|         
 | |
|         // reset cached values
 | |
|         reset();
 | |
|         
 | |
|         return true;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Move this Node from specified parent to a new parent destination.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *    
 | |
|      * @param source Node
 | |
|      * @param destination Node
 | |
|      * @return true on successful move, false on failure to move.
 | |
|      */
 | |
|     public boolean move(ScriptNode source, ScriptNode destination)
 | |
|     {
 | |
|         ParameterCheck.mandatory("Destination Node", destination);
 | |
| 
 | |
|         if (source == null)
 | |
|         {
 | |
|             return move(destination);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             try
 | |
|             {
 | |
|                 this.services.getFileFolderService().moveFrom(this.nodeRef, source.getNodeRef(), destination.getNodeRef(), null);
 | |
|             }
 | |
|             //MNT-7514 Uninformational error message on move when file name conflicts
 | |
|             catch (FileExistsException ex)
 | |
|             {
 | |
|                 throw ex;
 | |
|             }
 | |
|             catch (Exception e)
 | |
|             {
 | |
|                 throw new ScriptException("Can't move node", e);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // reset cached values
 | |
|         reset();
 | |
| 
 | |
|         return true;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * 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.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      * @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.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *    
 | |
|      * @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);
 | |
|         
 | |
|         Map<QName, Serializable> aspectProps = null;
 | |
|         if (props instanceof ScriptableObject)
 | |
|         {
 | |
|             aspectProps = new HashMap<QName, Serializable>(4, 1.0f);
 | |
|             extractScriptableProperties((ScriptableObject)props, aspectProps);
 | |
|         }
 | |
|         QName aspectQName = createQName(type);
 | |
|         if (aspectQName.equals(ContentModel.ASPECT_VERSIONABLE))
 | |
|         {
 | |
|             // ALF-13719 need to taking into account script properties for versionable aspect
 | |
|             if (aspectProps != null)
 | |
|             {
 | |
|                 Serializable autoVersionObj, autoVersionPropsObj;
 | |
|                 autoVersionObj = aspectProps.get(ContentModel.PROP_AUTO_VERSION);
 | |
|                 autoVersionPropsObj = aspectProps.get(ContentModel.PROP_AUTO_VERSION_PROPS);
 | |
|                 ensureVersioningEnabled(autoVersionObj instanceof Boolean ? ((Boolean) autoVersionObj) : true,
 | |
|                         autoVersionPropsObj instanceof Boolean ? ((Boolean) autoVersionPropsObj) : true);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 // MNT-9369, read props from contentModel.xml, sets to false, false if there is no defaults.
 | |
|                 Map<QName, PropertyDefinition> versionableProps = services.getDictionaryService().getAspect(ContentModel.ASPECT_VERSIONABLE).getProperties();
 | |
|                 boolean autoVersion = Boolean.parseBoolean(versionableProps.get(ContentModel.PROP_AUTO_VERSION).getDefaultValue());
 | |
|                 boolean autoVersionProps = Boolean.parseBoolean(versionableProps.get(ContentModel.PROP_AUTO_VERSION_PROPS).getDefaultValue());
 | |
|                 ensureVersioningEnabled(autoVersion, autoVersionProps);
 | |
|             }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             this.nodeService.addAspect(this.nodeRef, aspectQName, aspectProps);
 | |
|         }
 | |
|         
 | |
|         // reset the relevant cached node members
 | |
|         reset();
 | |
|         
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Extract a map of properties from a scriptable object (generally an associative array)
 | |
|      * 
 | |
|      * @param scriptable    The scriptable object to extract name/value pairs from.
 | |
|      * @param map           The map to add the converted name/value pairs to.
 | |
|      */
 | |
|     private void extractScriptableProperties(ScriptableObject scriptable, Map<QName, Serializable> map)
 | |
|     {
 | |
|         // we need to get all the keys to the properties provided
 | |
|         // and convert them to a Map of QName to Serializable objects
 | |
|         Object[] propIds = scriptable.getIds();
 | |
|         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 - it must be Serializable
 | |
|                 String key = (String)propId;
 | |
|                 Object value = scriptable.get(key, scriptable);
 | |
|                 if (value instanceof Serializable)
 | |
|                 {
 | |
|                     value = getValueConverter().convertValueForRepo((Serializable)value);
 | |
|                     map.put(createQName(key), (Serializable)value);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Remove aspect from the node.
 | |
|      * 
 | |
|      * Beware: Any unsaved property changes will be lost when this is called.  To preserve property changes call {@link #save()} first.
 | |
|      *    
 | |
|      * @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
 | |
| 
 | |
|     /**
 | |
|      * Ensures that this document has the cm:versionable aspect applied to it,
 | |
|      *  and that it has the initial version in the version store.
 | |
|      * Calling this on a versioned node with a version store entry will have 
 | |
|      *  no effect.
 | |
|      * Calling this on a newly uploaded share node will have versioning enabled
 | |
|      *  for it (Share currently does lazy versioning to improve performance of
 | |
|      *  documents that are uploaded but never edited, and multi upload performance).
 | |
|      * 
 | |
|      * @param autoVersion If the cm:versionable aspect is applied, should auto versioning be requested?
 | |
|      * @param autoVersionProps If the cm:versionable aspect is applied, should auto versioning of properties be requested?
 | |
|      */
 | |
|     public void ensureVersioningEnabled(boolean autoVersion, boolean autoVersionProps)
 | |
|     {
 | |
|         Map<QName, Serializable> props = new HashMap<QName, Serializable>(1, 1.0f);
 | |
|         props.put(ContentModel.PROP_AUTO_VERSION, autoVersion);
 | |
|         props.put(ContentModel.PROP_AUTO_VERSION_PROPS, autoVersionProps);
 | |
|         
 | |
|         this.services.getVersionService().ensureVersioningEnabled(nodeRef, props);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Ensures that this document has the cm:versionable aspect applied to it,
 | |
|      *  and that it has the initial version in the version store.
 | |
|      * Calling this on a versioned node with a version store entry will have 
 | |
|      *  no effect.
 | |
|      * Calling this on a newly uploaded share node will have versioning enabled
 | |
|      *  for it (Share currently does lazy versioning to improve performance of
 | |
|      *  documents that are uploaded but never edited, and multi upload performance).
 | |
|      * 
 | |
|      */
 | |
|     public void ensureVersioningEnabled()
 | |
|     {
 | |
|         this.services.getVersionService().ensureVersioningEnabled(nodeRef, null);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Create a version of this document.  Note: this will add the cm:versionable aspect.
 | |
|      * 
 | |
|      * @param history       Version history note
 | |
|      * @param majorVersion  True to save as a major version increment, false for minor version.
 | |
|      * 
 | |
|      * @return ScriptVersion object representing the newly added version node
 | |
|      */
 | |
|     public ScriptVersion createVersion(String history, boolean majorVersion)
 | |
|     {
 | |
|         Map<String, Serializable> props = new HashMap<String, Serializable>(2, 1.0f);
 | |
|         props.put(Version.PROP_DESCRIPTION, history);
 | |
|         props.put(VersionModel.PROP_VERSION_TYPE, majorVersion ? VersionType.MAJOR : VersionType.MINOR);
 | |
|         ScriptVersion version = new ScriptVersion(this.services.getVersionService().createVersion(this.nodeRef, props), this.services, this.scope);
 | |
|         this.versions = null;
 | |
|         return version;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Determines if this node is versioned
 | |
|      * 
 | |
|      * @return  true => is versioned
 | |
|      */
 | |
|     public boolean getIsVersioned()
 | |
|     {
 | |
|         return this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_VERSIONABLE);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Gets the version history
 | |
|      * 
 | |
|      * @return  version history
 | |
|      */
 | |
|     public Scriptable getVersionHistory()
 | |
|     {
 | |
|         if (this.versions == null && getIsVersioned())
 | |
|         {
 | |
|             VersionHistory history = this.services.getVersionService().getVersionHistory(this.nodeRef);
 | |
|             if (history != null)
 | |
|             {
 | |
|                 Collection<Version> allVersions = history.getAllVersions();
 | |
|                 Object[] versions = new Object[allVersions.size()];
 | |
|                 int i = 0;
 | |
|                 for (Version version : allVersions)
 | |
|                 {
 | |
|                     versions[i++] = new ScriptVersion(version, this.services, this.scope);
 | |
|                 }
 | |
|                 this.versions = Context.getCurrentContext().newArray(this.scope, versions);
 | |
|             }
 | |
|         }
 | |
|         return this.versions;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Gets the version of this node specified by version label
 | |
|      * 
 | |
|      * @param versionLabel  version label
 | |
|      * @return  version of node, or null if node is not versioned, or label does not exist
 | |
|      */
 | |
|     public ScriptVersion getVersion(String versionLabel)
 | |
|     {
 | |
|         if (!getIsVersioned())
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
|         VersionHistory history = this.services.getVersionService().getVersionHistory(this.nodeRef);
 | |
|         Version version = history.getVersion(versionLabel);
 | |
|         if (version == null)
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
|         return new ScriptVersion(version, this.services, this.scope); 
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Perform a check-out of this document into the current parent space.
 | |
|      * 
 | |
|      * @return the working copy Node for the checked out document
 | |
|      */
 | |
|     public ScriptNode checkout()
 | |
|     {
 | |
|         NodeRef workingCopyRef = this.services.getCheckOutCheckInService().checkout(this.nodeRef);
 | |
|         ScriptNode 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;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Performs a check-out of this document for the purposes of an upload
 | |
|      * 
 | |
|      * @return ScriptNode
 | |
|      */
 | |
|     public ScriptNode checkoutForUpload()
 | |
|     {
 | |
|         AlfrescoTransactionSupport.bindResource("checkoutforupload", Boolean.TRUE.toString());
 | |
|         services.getRuleService().disableRules();
 | |
|         try
 | |
|         {
 | |
|             return checkout();
 | |
|         }
 | |
|         finally
 | |
|         {
 | |
|             services.getRuleService().enableRules();
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * 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 ScriptNode checkout(ScriptNode 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());
 | |
|         ScriptNode 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 ScriptNode 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 ScriptNode 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 ScriptNode checkin(String history, boolean majorVersion)
 | |
|     {
 | |
|         Map<String, Serializable> props = new HashMap<String, Serializable>(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);
 | |
|         this.versions = null;
 | |
|         return newInstance(original, this.services, this.scope);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|       * Removes the lock on a node.
 | |
|       * 
 | |
|       */
 | |
|     public void unlock()
 | |
|     {
 | |
|          this.services.getLockService().unlock(this.nodeRef);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Gets the check-out of a working copy document
 | |
|      * @return the original Node that was checked out or null if it's not a working copy
 | |
|      */
 | |
|     public ScriptNode getCheckedOut()
 | |
|     {
 | |
|         NodeRef original = this.services.getCheckOutCheckInService().getCheckedOut(this.nodeRef);
 | |
| 
 | |
|         if(original != null)
 | |
|         {
 | |
|             return newInstance(original, this.services, this.scope);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * 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 ScriptNode 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 ScriptNode 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 ScriptNode transformDocument(String mimetype, ScriptNode destination)
 | |
|     {
 | |
|         return transformDocument(mimetype, destination.getNodeRef());
 | |
|     }
 | |
|     
 | |
|     private ScriptNode transformDocument(String mimetype, NodeRef destination)
 | |
|     {
 | |
|         ParameterCheck.mandatoryString("Mimetype", mimetype);
 | |
|         ParameterCheck.mandatory("Destination Node", destination);
 | |
|         final NodeRef sourceNodeRef = nodeRef;
 | |
|         
 | |
|         // the delegate definition for transforming a document
 | |
|         Transformer transformer = new AbstractTransformer()
 | |
|         {
 | |
|             protected void doTransform(ContentService contentService,
 | |
|                 ContentReader reader, ContentWriter writer)
 | |
|             {
 | |
|                 TransformationOptions options = new TransformationOptions();
 | |
|                 options.setSourceNodeRef(sourceNodeRef);
 | |
|                 contentService.transform(reader, writer, options);
 | |
|             }
 | |
|         };
 | |
|         
 | |
|         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 ScriptNode transformNode(Transformer transformer, String mimetype, NodeRef destination)
 | |
|     {
 | |
|         ScriptNode 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, true);
 | |
|             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 ScriptNode 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 ScriptNode 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 ScriptNode transformImage(String mimetype, ScriptNode 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 ScriptNode transformImage(String mimetype, String options, ScriptNode destination)
 | |
|     {
 | |
|         ParameterCheck.mandatory("Destination Node", destination);
 | |
|         return transformImage(mimetype, options, destination.getNodeRef());
 | |
|     }
 | |
|     
 | |
|     private ScriptNode transformImage(String mimetype, final String options, NodeRef destination)
 | |
|     {
 | |
|         ParameterCheck.mandatoryString("Mimetype", mimetype);
 | |
|         final NodeRef sourceNodeRef = nodeRef;
 | |
|         
 | |
|         // the delegate definition for transforming an image
 | |
|         Transformer transformer = new AbstractTransformer()
 | |
|         {
 | |
|             protected void doTransform(ContentService contentService,
 | |
|                 ContentReader reader, ContentWriter writer)
 | |
|             {
 | |
|                 ImageTransformationOptions imageOptions = new ImageTransformationOptions();
 | |
|                 imageOptions.setSourceNodeRef(sourceNodeRef);
 | |
| 
 | |
|                 if (options != null)
 | |
|                 {
 | |
|                     imageOptions.setCommandOptions(options);
 | |
|                 }
 | |
|                 contentService.getImageTransformer().transform(reader, writer, imageOptions);
 | |
|             }
 | |
|         };
 | |
|         
 | |
|         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(ScriptNode 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(ScriptNode 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)
 | |
|     {
 | |
|         Object person = (Object)scope.get("person", scope);
 | |
|         Object companyhome = (Object)scope.get("companyhome", scope);
 | |
|         Object userhome = (Object)scope.get("userhome", scope);
 | |
|         
 | |
|         // build default model for the template processing
 | |
|         Map<String, Object> model = this.services.getTemplateService().buildDefaultModel(
 | |
|             (person.equals(UniqueTag.NOT_FOUND)) ? null : ((ScriptNode)((Wrapper)person).unwrap()).getNodeRef(),
 | |
|             (companyhome.equals(UniqueTag.NOT_FOUND)) ? null : ((ScriptNode)((Wrapper)companyhome).unwrap()).getNodeRef(),
 | |
|             (userhome.equals(UniqueTag.NOT_FOUND)) ? null : ((ScriptNode)((Wrapper)userhome).unwrap()).getNodeRef(),
 | |
|             templateRef,
 | |
|             null);
 | |
|         
 | |
|         // add the current node as either the document/space as appropriate
 | |
|         if (this.getIsDocument())
 | |
|         {
 | |
|             model.put("document", this.nodeRef);
 | |
|             model.put("space", getPrimaryParentAssoc().getParentRef());
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             model.put("space", this.nodeRef);
 | |
|         }
 | |
|         
 | |
|         // 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<String, String> templateArgs = new HashMap<String, String>(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);
 | |
|     }
 | |
|     
 | |
|     // ------------------------------------------------------------------------------
 | |
|     // Thumbnail Methods
 | |
|     
 | |
|     /**
 | |
|      * Creates a thumbnail for the content property of the node.
 | |
|      * 
 | |
|      * The thumbnail name correspionds to pre-set thumbnail details stored in the 
 | |
|      * repository.
 | |
|      * 
 | |
|      * @param  thumbnailName    the name of the thumbnail
 | |
|      * @return ScriptThumbnail  the newly create thumbnail node
 | |
|      */
 | |
|     public ScriptThumbnail createThumbnail(String thumbnailName)
 | |
|     {
 | |
|         return createThumbnail(thumbnailName, false);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Creates a thumbnail for the content property of the node.
 | |
|      * 
 | |
|      * The thumbnail name corresponds to pre-set thumbnail details stored in the 
 | |
|      * repository.
 | |
|      * 
 | |
|      * If the thumbnail is created asynchronously then the result will be null and creation
 | |
|      * of the thumbnail will occure at some point in the background.
 | |
|      * 
 | |
|      * @param  thumbnailName    the name of the thumbnail
 | |
|      * @param  async            indicates whether the thumbnail is create asynchronously or not
 | |
|      * @return ScriptThumbnail  the newly create thumbnail node or null if async creation occures
 | |
|      */
 | |
|     public ScriptThumbnail createThumbnail(String thumbnailName, boolean async)
 | |
|     {
 | |
|         return createThumbnail(thumbnailName, async, false);
 | |
|     }	
 | |
|     
 | |
|     /**
 | |
|      * Creates a thumbnail for the content property of the node.
 | |
|      * 
 | |
|      * The thumbnail name corresponds to pre-set thumbnail details stored in the 
 | |
|      * repository.
 | |
|      * 
 | |
|      * If the thumbnail is created asynchronously then the result will be null and creation
 | |
|      * of the thumbnail will occure at some point in the background.
 | |
|      * 
 | |
|      * If foce param specified system.thumbnail.generate is ignoring. Could be used for preview creation
 | |
|      * 
 | |
|      * @param  thumbnailName    the name of the thumbnail
 | |
|      * @param  async            indicates whether the thumbnail is create asynchronously or not
 | |
|      * @param  force            ignore system.thumbnail.generate=false
 | |
|      * @return ScriptThumbnail  the newly create thumbnail node or null if async creation occures
 | |
|      */
 | |
|     public ScriptThumbnail createThumbnail(String thumbnailName, boolean async, boolean force)
 | |
|     {
 | |
|         final ThumbnailService thumbnailService = services.getThumbnailService();
 | |
|         
 | |
|         ScriptThumbnail result = null;
 | |
|         
 | |
|         // If thumbnail generation has been configured off, then don't bother with any of this.
 | |
|         // We need to create preview for node even if system.thumbnail.generate=false
 | |
|         if (force || thumbnailService.getThumbnailsEnabled())
 | |
|         {
 | |
|             // Use the thumbnail registy to get the details of the thumbail
 | |
|             ThumbnailRegistry registry = thumbnailService.getThumbnailRegistry();
 | |
|             ThumbnailDefinition details = registry.getThumbnailDefinition(thumbnailName);
 | |
|             if (details == null)
 | |
|             {
 | |
|                 // Throw exception 
 | |
|                 throw new ScriptException("The thumbnail name '" + thumbnailName + "' is not registered");
 | |
|             }
 | |
|             
 | |
|             // If there's nothing currently registered to generate thumbnails for the
 | |
|             // specified mimetype, then log a message and bail out
 | |
|             String nodeMimeType = getMimetype();
 | |
|             Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT);
 | |
|             ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value);
 | |
|             if (!ContentData.hasContent(contentData))
 | |
|             {
 | |
|                 if (logger.isDebugEnabled())
 | |
|                     logger.debug("Unable to create thumbnail '" + details.getName() + "' as there is no content");
 | |
|                 return null;
 | |
|             }
 | |
|             if (!registry.isThumbnailDefinitionAvailable(contentData.getContentUrl(), nodeMimeType, getSize(), nodeRef, details))
 | |
|             {
 | |
|                 logger.info("Unable to create thumbnail '" + details.getName() + "' for " +
 | |
|                             nodeMimeType + " as no transformer is currently available.");
 | |
|                 return null;
 | |
|             }
 | |
|             
 | |
|             // Have the thumbnail created
 | |
|             if (async == false)
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     // Create the thumbnail
 | |
|                     NodeRef thumbnailNodeRef = thumbnailService.createThumbnail(
 | |
|                         this.nodeRef, 
 | |
|                         ContentModel.PROP_CONTENT, 
 | |
|                         details.getMimetype(), 
 | |
|                         details.getTransformationOptions(), 
 | |
|                         details.getName());
 | |
|                     
 | |
|                     // Create the thumbnail script object
 | |
|                     result = new ScriptThumbnail(thumbnailNodeRef, this.services, this.scope);
 | |
|                 }
 | |
|                 catch (AlfrescoRuntimeException e)
 | |
|                 {
 | |
|                     Throwable rootCause = e.getRootCause();
 | |
|                     if (rootCause instanceof UnimportantTransformException)
 | |
|                     {
 | |
|                         logger.debug("Unable to create thumbnail '" + details.getName() + "' as "+rootCause.getMessage());
 | |
|                         return null;
 | |
|                     }
 | |
|                     throw e;
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 Action action = ThumbnailHelper.createCreateThumbnailAction(details, services);
 | |
|                 
 | |
|                 // Queue async creation of thumbnail
 | |
|                 this.services.getActionService().executeAction(action, this.nodeRef, true, true);
 | |
|             }
 | |
|         }
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Get the given thumbnail for the content property
 | |
|      * 
 | |
|      * @param thumbnailName     the thumbnail name
 | |
|      * @return ScriptThumbnail  the thumbnail
 | |
|      */
 | |
|     public ScriptThumbnail getThumbnail(String thumbnailName)
 | |
|     {
 | |
|         ScriptThumbnail result = null;
 | |
|         NodeRef thumbnailNodeRef = this.services.getThumbnailService().getThumbnailByName(
 | |
|                 this.nodeRef, 
 | |
|                 ContentModel.PROP_CONTENT, 
 | |
|                 thumbnailName);
 | |
|         if (thumbnailNodeRef != null)
 | |
|         {
 | |
|             result = new ScriptThumbnail(thumbnailNodeRef, this.services, this.scope);
 | |
|         }
 | |
|         return result;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Get the all the thumbnails for a given node's content property.
 | |
|      * 
 | |
|      * @return  Scriptable     list of thumbnails, empty if none available
 | |
|      */
 | |
|     public ScriptThumbnail[] getThumbnails()
 | |
|     {
 | |
|         List<NodeRef> thumbnails = this.services.getThumbnailService().getThumbnails(
 | |
|                 this.nodeRef, 
 | |
|                 ContentModel.PROP_CONTENT, 
 | |
|                 null, 
 | |
|                 null);
 | |
|         
 | |
|         List<ScriptThumbnail> result = new ArrayList<ScriptThumbnail>(thumbnails.size());
 | |
|         for (NodeRef thumbnail : thumbnails)
 | |
|         {
 | |
|             ScriptThumbnail scriptThumbnail = new ScriptThumbnail(thumbnail, this.services, this.scope);
 | |
|             result.add(scriptThumbnail);
 | |
|         }
 | |
|         return (ScriptThumbnail[])result.toArray(new ScriptThumbnail[result.size()]);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Returns the names of the thumbnail defintions that can be applied to the content property of
 | |
|      * this node.
 | |
|      * <p>
 | |
|      * Thumbanil defintions only appear in this list if they can produce a thumbnail for the content
 | |
|      * found in the content property.  This will be determined by looking at the mimetype of the content
 | |
|      * and the destinatino mimetype of the thumbnail.
 | |
|      * 
 | |
|      * @return  String[]    array of thumbnail names that are valid for the current content type
 | |
|      */
 | |
|     public String[] getThumbnailDefinitions()
 | |
|     {
 | |
|         ThumbnailService thumbnailService = this.services.getThumbnailService();
 | |
|         
 | |
|         List<String> result = new ArrayList<String>(7);
 | |
|         
 | |
|         Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT);
 | |
|         ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value);
 | |
|         
 | |
|         if (ContentData.hasContent(contentData))
 | |
|         {
 | |
|             String mimetype = contentData.getMimetype();
 | |
|             List<ThumbnailDefinition> thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumbnailDefinitions(mimetype, contentData.getSize());
 | |
|             for (ThumbnailDefinition thumbnailDefinition : thumbnailDefinitions)
 | |
|             {
 | |
|                 result.add(thumbnailDefinition.getName());
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         return (String[])result.toArray(new String[result.size()]);
 | |
|     }
 | |
|     /**
 | |
|      * This version of the method name spelling is retained (for now) for backwards compatibility
 | |
|      * @see #getThumbnailDefinitions() 
 | |
|      */
 | |
|     @Deprecated
 | |
|     public String[] getThumbnailDefintions()
 | |
|     {
 | |
|         return getThumbnailDefinitions();
 | |
|     }
 | |
|     
 | |
|     
 | |
|     // ------------------------------------------------------------------------------
 | |
|     // Tag methods
 | |
|     
 | |
|     /**
 | |
|      * Clear the node's tags
 | |
|      */
 | |
|     public void clearTags()
 | |
|     {
 | |
|         this.services.getTaggingService().clearTags(this.nodeRef);
 | |
|         updateTagProperty();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Adds a tag to the node
 | |
|      * 
 | |
|      * @param tag   tag name
 | |
|      */
 | |
|     public void addTag(String tag)
 | |
|     {
 | |
|         this.services.getTaggingService().addTag(this.nodeRef, tag);
 | |
|         updateTagProperty();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Adds all the tags to the node
 | |
|      * 
 | |
|      * @param tags  array of tag names
 | |
|      */
 | |
|     public void addTags(String[] tags)
 | |
|     {
 | |
|         this.services.getTaggingService().addTags(this.nodeRef, Arrays.asList(tags));
 | |
|         updateTagProperty();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Removes a tag from the node
 | |
|      * 
 | |
|      * @param tag   tag name
 | |
|      */
 | |
|     public void removeTag(String tag)
 | |
|     {
 | |
|         this.services.getTaggingService().removeTag(this.nodeRef, tag);
 | |
|         updateTagProperty();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Removes all the tags from the node
 | |
|      * 
 | |
|      * @param tags  array of tag names
 | |
|      */
 | |
|     public void removeTags(String[] tags)
 | |
|     {
 | |
|         this.services.getTaggingService().removeTags(this.nodeRef, Arrays.asList(tags));
 | |
|         updateTagProperty();
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Get all the tags applied to this node
 | |
|      * 
 | |
|      * @return String[]     array containing all the tag applied to this node
 | |
|      */
 | |
|     public String[] getTags()
 | |
|     {
 | |
|         String[] result = null;
 | |
|         List<String> tags = this.services.getTaggingService().getTags(this.nodeRef);
 | |
|         if (tags.isEmpty() == true)
 | |
|         {
 | |
|             result = new String[0];
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             result = (String[])tags.toArray(new String[tags.size()]);
 | |
|         }
 | |
|         return result;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Set the tags applied to this node.  This overwirtes the list of tags currently applied to the 
 | |
|      * node.
 | |
|      * 
 | |
|      * @param tags  array of tags
 | |
|      */
 | |
|     public void setTags(String[] tags)
 | |
|     {
 | |
|         this.services.getTaggingService().setTags(this.nodeRef, Arrays.asList(tags));
 | |
|         updateTagProperty();
 | |
|     }
 | |
|     
 | |
|     private void updateTagProperty()
 | |
|     {
 | |
|         Serializable tags = this.services.getNodeService().getProperty(this.nodeRef, ContentModel.PROP_TAGS);
 | |
|         if (this.properties != null)
 | |
|         {
 | |
|             this.properties.put(ContentModel.PROP_TAGS.toString(), getValueConverter().convertValueForScript(ContentModel.PROP_TAGS, tags));
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Sets whether this node is a tag scope or not
 | |
|      * 
 | |
|      * @param value     true if this node is a tag scope, false otherwise
 | |
|      */
 | |
|     public void setIsTagScope(boolean value)
 | |
|     {
 | |
|         boolean currentValue = this.services.getTaggingService().isTagScope(this.nodeRef);
 | |
|         if (currentValue != value)
 | |
|         {
 | |
|             if (value == true)
 | |
|             {
 | |
|                 // Add the tag scope
 | |
|                 this.services.getTaggingService().addTagScope(this.nodeRef);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 // Remove the tag scope
 | |
|                 this.services.getTaggingService().removeTagScope(this.nodeRef);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Gets whether this node is a tag scope
 | |
|      * 
 | |
|      * @return  boolean     true if this node is a tag scope, false otherwise
 | |
|      */
 | |
|     public boolean getIsTagScope()
 | |
|     {
 | |
|         return this.services.getTaggingService().isTagScope(this.nodeRef);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Gets the 'nearest' tag scope to this node by travesing up the parent hierarchy untill one is found.
 | |
|      * <p>
 | |
|      * If none is found, null is returned.
 | |
|      *
 | |
|      * @return  TagScope    the 'nearest' tag scope
 | |
|      */
 | |
|     public TagScope getTagScope()
 | |
|     {
 | |
|         TagScope tagScope = null;
 | |
|         org.alfresco.service.cmr.tagging.TagScope tagScopeImpl = this.services.getTaggingService().findTagScope(this.nodeRef);
 | |
|         if (tagScopeImpl != null)
 | |
|         {
 | |
|             tagScope = new TagScope(this.services.getTaggingService(), tagScopeImpl);
 | |
|         }        
 | |
|         return tagScope;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Gets all (deep) children of this node that have the tag specified.
 | |
|      * 
 | |
|      * @param tag               tag name
 | |
|      * @return ScriptNode[]     nodes that are deep children of the node with the tag
 | |
|      */
 | |
|     public ScriptNode[] childrenByTags(String tag)
 | |
|     {
 | |
|         List<NodeRef> nodeRefs = this.services.getTaggingService().findTaggedNodes(this.nodeRef.getStoreRef(), tag, this.nodeRef);
 | |
|         ScriptNode[] nodes = new ScriptNode[nodeRefs.size()];
 | |
|         int index = 0;
 | |
|         for (NodeRef node : nodeRefs)
 | |
|         {
 | |
|             nodes[index] = new ScriptNode(node, this.services, this.scope);
 | |
|             index ++;
 | |
|         }
 | |
|         return nodes;
 | |
|     }
 | |
| 
 | |
|     
 | |
|     // ------------------------------------------------------------------------------
 | |
|     // Workflow methods
 | |
| 
 | |
|     /**
 | |
|      * Get active workflow instances this node belongs to
 | |
|      * 
 | |
|      * @return the active workflow instances this node belongs to
 | |
|      */
 | |
|     public Scriptable getActiveWorkflows()
 | |
|     {
 | |
|         if (this.activeWorkflows == null)
 | |
|         {
 | |
|             WorkflowService workflowService = this.services.getWorkflowService();
 | |
|             
 | |
|             List<WorkflowInstance> workflowInstances = workflowService.getWorkflowsForContent(this.nodeRef, true);
 | |
|             Object[] jsWorkflowInstances = new Object[workflowInstances.size()];
 | |
|             int index = 0;
 | |
|             for (WorkflowInstance workflowInstance : workflowInstances)
 | |
|             {
 | |
|                 jsWorkflowInstances[index++] = new JscriptWorkflowInstance(workflowInstance, this.services, this.scope);
 | |
|             }
 | |
|             this.activeWorkflows = Context.getCurrentContext().newArray(this.scope, jsWorkflowInstances);		
 | |
|         }
 | |
|     
 | |
|         return this.activeWorkflows;
 | |
|     }
 | |
| 
 | |
|     // ------------------------------------------------------------------------------
 | |
|     // Site methods
 | |
|     
 | |
|     /**
 | |
|      * Returns the short name of the site this node is located within. If the 
 | |
|      * node is not located within a site null is returned.
 | |
|      * 
 | |
|      * @return The short name of the site this node is located within, null
 | |
|      *         if the node is not located within a site.
 | |
|      */
 | |
|     public String getSiteShortName()
 | |
|     {
 | |
|         if (!this.siteNameResolved)
 | |
|         {
 | |
|             this.siteNameResolved = true;
 | |
|             
 | |
|             Path path = this.services.getNodeService().getPath(getNodeRef());
 | |
|             
 | |
|             if (logger.isDebugEnabled())
 | |
|                 logger.debug("Determing if node is within a site using path: " + path);
 | |
|             
 | |
|             for (int i = 0; i < path.size(); i++)
 | |
|             {
 | |
|                 if ("st:sites".equals(path.get(i).getPrefixedString(this.services.getNamespaceService())))
 | |
|                 {
 | |
|                     // we now know the node is in a site, find the next element in the array (if there is one)
 | |
|                     if ((i+1) < path.size())
 | |
|                     {
 | |
|                         // get the site name
 | |
|                         Path.Element siteName = path.get(i+1);
 | |
|                      
 | |
|                         // remove the "cm:" prefix and add to result object
 | |
|                         this.siteName = ISO9075.decode(siteName.getPrefixedString(
 | |
|                                     this.services.getNamespaceService()).substring(3));
 | |
|                     }
 | |
|                   
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         if (logger.isDebugEnabled())
 | |
|         {
 | |
|             logger.debug(this.siteName != null ? 
 | |
|                         "Node is in the site named \"" + this.siteName + "\"" : "Node is not in a site");
 | |
|         }
 | |
|         
 | |
|         return this.siteName;
 | |
|     }
 | |
| 
 | |
|     // ------------------------------------------------------------------------------
 | |
|     // Helper methods
 | |
|     
 | |
|     /**
 | |
|      * Override Object.toString() to provide useful debug output
 | |
|      */
 | |
|     public String toString()
 | |
|     {
 | |
|         if (this.nodeService.exists(nodeRef))
 | |
|         {
 | |
|             if (this.services.getPermissionService().hasPermission(nodeRef, PermissionService.READ_PROPERTIES) == AccessStatus.ALLOWED)
 | |
|             {
 | |
|                 // 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() + ", Node Aspects: " + getAspectsSet().toString();
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 return "Access denied to node " + nodeRef;
 | |
|             }
 | |
| 
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             return "Node no longer exists: " + nodeRef;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the JSON representation of this node.
 | |
|      * 
 | |
|      * @param useShortQNames if true short-form qnames will be returned, else long-form.
 | |
|      * @return The JSON representation of this node
 | |
|      */
 | |
|     public String toJSON(boolean useShortQNames)
 | |
|     {
 | |
|         // This method is used by the /api/metadata web script
 | |
|        String jsonStr = "{}";
 | |
|        
 | |
|        if (this.nodeService.exists(nodeRef))
 | |
|        {
 | |
|            if(this.services.getPublicServiceAccessService().hasAccess(ServiceRegistry.NODE_SERVICE.getLocalName(), "getProperties", this.nodeRef) == AccessStatus.ALLOWED)
 | |
|            {
 | |
|                JSONObject json = new JSONObject();
 | |
|               
 | |
|                try
 | |
|                {
 | |
|                    // add type info
 | |
|                    json.put("nodeRef", this.getNodeRef().toString());
 | |
|                    
 | |
|                    String typeString = useShortQNames ? getShortQName(this.getQNameType())
 | |
|                            : this.getType();
 | |
|                    json.put("type", typeString);
 | |
|                    json.put("mimetype", this.getMimetype());
 | |
|                    
 | |
|                    // Fetch all properties
 | |
|                    Map<QName, Serializable> nodeProperties = this.nodeService.getProperties(this.nodeRef);
 | |
|                    
 | |
|                    // Do any special conversion steps that are needed
 | |
|                    for (QName longQName : nodeProperties.keySet())
 | |
|                    {
 | |
|                        Serializable value = nodeProperties.get(longQName);
 | |
|                        
 | |
|                        if (value instanceof Date)
 | |
|                        {
 | |
|                            value = ISO8601DateFormat.format((Date)value);
 | |
|                            nodeProperties.put(longQName, value);
 | |
|                        }
 | |
|                        if (value instanceof NodeRef)
 | |
|                        {
 | |
|                            value = ((NodeRef)value).toString();
 | |
|                            nodeProperties.put(longQName, value);
 | |
|                        }
 | |
|                    }
 | |
|                    
 | |
|                    if (useShortQNames)
 | |
|                    {
 | |
|                        Map<String, Serializable> nodePropertiesShortQNames
 | |
|                                = new LinkedHashMap<String, Serializable>(nodeProperties.size());
 | |
|                        for (QName nextLongQName : nodeProperties.keySet())
 | |
|                        {
 | |
|                            try
 | |
|                            {
 | |
|                                String nextShortQName = getShortQName(nextLongQName);
 | |
|                                nodePropertiesShortQNames.put(nextShortQName, nodeProperties.get(nextLongQName));
 | |
|                            }
 | |
|                            catch (NamespaceException ne)
 | |
|                            {
 | |
|                                // ignore properties that do not have a registered namespace
 | |
|                                
 | |
|                                if (logger.isDebugEnabled())
 | |
|                                    logger.debug("Ignoring property '" + nextLongQName + "' as it's namespace is not registered");
 | |
|                            }
 | |
|                        }
 | |
|                        json.put("properties", nodePropertiesShortQNames);
 | |
|                    }
 | |
|                    else
 | |
|                    {
 | |
|                        json.put("properties", nodeProperties);
 | |
|                    }
 | |
|                    
 | |
|                    // add aspects as an array
 | |
|                    Set<QName> nodeAspects = this.nodeService.getAspects(this.nodeRef);
 | |
|                    if (useShortQNames)
 | |
|                    {
 | |
|                        Set<String> nodeAspectsShortQNames
 | |
|                                = new LinkedHashSet<String>(nodeAspects.size());
 | |
|                        for (QName nextLongQName : nodeAspects)
 | |
|                        {
 | |
|                            String nextShortQName = getShortQName(nextLongQName);
 | |
|                            nodeAspectsShortQNames.add(nextShortQName);
 | |
|                        }
 | |
|                        json.put("aspects", nodeAspectsShortQNames);
 | |
|                    }
 | |
|                    else
 | |
|                    {
 | |
|                        json.put("aspects", nodeAspects);
 | |
|                    }
 | |
|                }
 | |
|                catch (JSONException error)
 | |
|                {
 | |
|                    error.printStackTrace();
 | |
|                }
 | |
|               
 | |
|                jsonStr = json.toString();
 | |
|            }
 | |
|        }
 | |
|        
 | |
|        return jsonStr;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Returns the JSON representation of this node. Long-form QNames are used in the
 | |
|      * result.
 | |
|      * 
 | |
|      * @return The JSON representation of this node
 | |
|      */
 | |
|     public String toJSON()
 | |
|     {
 | |
|         return this.toJSON(false);
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Given a long-form QName, this method uses the namespace service to create a
 | |
|      * short-form QName string.
 | |
|      * 
 | |
|      * @param longQName QName
 | |
|      * @return the short form of the QName string, e.g. "cm:content"
 | |
|      */
 | |
|     protected String getShortQName(QName longQName)
 | |
|     {
 | |
|         return longQName.toPrefixString(services.getNamespaceService());
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * 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
 | |
|      */
 | |
|     protected 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.targetAssocs = null;
 | |
|         this.sourceAssocs = null;
 | |
|         this.childAssocs = null;
 | |
|         this.children = null;
 | |
|         this.hasChildren = null;
 | |
|         this.parentAssocs = null;
 | |
|         this.displayPath = null;
 | |
|         this.qnamePath = null;
 | |
|         this.isDocument = null;
 | |
|         this.isContainer = null;
 | |
|         this.parent = null;
 | |
|         this.primaryParentAssoc = null;
 | |
|         this.activeWorkflows = null;
 | |
|         this.siteName = null;
 | |
|         this.siteNameResolved = false;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * 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 Object[] can be empty but never null
 | |
|      */
 | |
|     private Object[] getChildrenByXPath(String xpath, QueryParameterDefinition[] params, boolean firstOnly)
 | |
|     {
 | |
|         Object[] result = null;
 | |
|         
 | |
|         if (xpath.length() != 0)
 | |
|         {
 | |
|             if (logger.isDebugEnabled())
 | |
|             {
 | |
|                logger.debug("Executing xpath: " + xpath);
 | |
|                if (params != null)
 | |
|                {
 | |
|                   for (int i=0; i<params.length; i++)
 | |
|                   {
 | |
|                      logger.debug(" [" + params[i].getQName() + "]=" + params[i].getDefault());
 | |
|                   }
 | |
|                }
 | |
|             }
 | |
|             
 | |
|             List<NodeRef> nodes = this.services.getSearchService().selectNodes(this.nodeRef, xpath, params,
 | |
|                     this.services.getNamespaceService(), false);
 | |
|             
 | |
|             // see if we only want the first result
 | |
|             if (firstOnly == true)
 | |
|             {
 | |
|                 if (nodes.size() != 0)
 | |
|                 {
 | |
|                     result = new Object[1];
 | |
|                     result[0] = newInstance(nodes.get(0), this.services, this.scope);
 | |
|                 }
 | |
|             }
 | |
|             // or all the results
 | |
|             else
 | |
|             {
 | |
|                 result = new Object[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 Object[0];
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * Helper to return true if the supplied property value is a ScriptContentData object
 | |
|      * 
 | |
|      * @param o     Object to test
 | |
|      * 
 | |
|      * @return true if instanceof ScriptContentData, false otherwise
 | |
|      */
 | |
|     public boolean isScriptContent(Object o)
 | |
|     {
 | |
|         return (o instanceof ScriptContentData);
 | |
|     }
 | |
|     
 | |
|     
 | |
|     // ------------------------------------------------------------------------------
 | |
|     // 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 Content, 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;
 | |
|             this.isDirty = ContentData.hasContent(contentData);
 | |
|         }
 | |
| 
 | |
|         /* (non-Javadoc)
 | |
|          * @see org.alfresco.repo.jscript.ScriptNode.ScriptContent#getContent()
 | |
|          */
 | |
|         public String getContent()
 | |
|         {
 | |
|             ContentService contentService = services.getContentService();
 | |
|             ContentReader reader = contentService.getReader(nodeRef, property);
 | |
|             return (reader != null && reader.exists()) ? reader.getContentString() : "";
 | |
|         }
 | |
|         
 | |
|         /*
 | |
|          * (non-Javadoc)
 | |
|          * @see org.springframework.extensions.surf.util.Content#getInputStream()
 | |
|          */
 | |
|         public InputStream getInputStream()
 | |
|         {
 | |
|             ContentService contentService = services.getContentService();
 | |
|             ContentReader reader = contentService.getReader(nodeRef, property);
 | |
|             return (reader != null && reader.exists()) ? reader.getContentInputStream() : null;
 | |
|         }
 | |
|         
 | |
|         /*
 | |
|          * (non-Javadoc)
 | |
|          * @see org.springframework.extensions.surf.util.Content#getReader()
 | |
|          */
 | |
|         public Reader getReader()
 | |
|         {
 | |
|             ContentService contentService = services.getContentService();
 | |
|             ContentReader reader = contentService.getReader(nodeRef, property);
 | |
|             
 | |
|             if (reader != null && reader.exists())
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     return (contentData.getEncoding() == null) ? new InputStreamReader(reader.getContentInputStream()) : new InputStreamReader(reader.getContentInputStream(), contentData.getEncoding());
 | |
|                 }
 | |
|                 catch (IOException e)
 | |
|                 {
 | |
|                     // NOTE: fall-through
 | |
|                 }
 | |
|             }
 | |
|             return null;
 | |
|         }
 | |
|         
 | |
|         /**
 | |
|          * @return <code>true</code> if the contentData has a binary (content URL) associated and the updates on contentData and related properties should be saved. 
 | |
|          *         <code>false</code> if the contentData has a temporary value and no actual binary to be persisted.
 | |
|          */
 | |
|         public boolean isDirty()
 | |
|         {
 | |
|             return this.isDirty;
 | |
|         }
 | |
|         
 | |
|         /**
 | |
|          * 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()
 | |
|             updateContentData(true);
 | |
|         }
 | |
|         
 | |
|         /**
 | |
|          * Set the content stream from another content object.
 | |
|          *  
 | |
|          * @param content  ScriptContent to set
 | |
|          */
 | |
|         public void write(Content content)
 | |
|         {
 | |
|             ContentService contentService = services.getContentService();
 | |
|             ContentWriter writer = contentService.getWriter(nodeRef, this.property, true);
 | |
|             writer.setMimetype(content.getMimetype());
 | |
|             writer.setEncoding(content.getEncoding());
 | |
|             writer.putContent(content.getInputStream());
 | |
| 
 | |
|             // update cached variables after putContent()
 | |
|             updateContentData(true);
 | |
|         }
 | |
|         
 | |
|         /**
 | |
|          * Set the content stream from another content object.
 | |
|          * 
 | |
|          * @param content       ScriptContent to set
 | |
|          * @param applyMimetype If true, apply the mimetype from the Content object, else leave the original mimetype
 | |
|          * @param guessEncoding If true, guess the encoding from the underlying input stream, else use encoding set in
 | |
|          *                      the Content object as supplied.
 | |
|          */
 | |
|         public void write(Content content, boolean applyMimetype, boolean guessEncoding)
 | |
|         {
 | |
|             ContentService contentService = services.getContentService();
 | |
|             ContentWriter writer = contentService.getWriter(nodeRef, this.property, true);
 | |
|             InputStream is = null;
 | |
|             if (applyMimetype)
 | |
|             {
 | |
|                 writer.setMimetype(content.getMimetype());
 | |
|             }
 | |
|             if (guessEncoding)
 | |
|             {
 | |
|                 is = new BufferedInputStream(content.getInputStream());
 | |
|                 is.mark(1024);
 | |
|                 writer.setEncoding(guessEncoding(is, false));
 | |
|                 try
 | |
|                 {
 | |
|                     is.reset();
 | |
|                 }
 | |
|                 catch (IOException e)
 | |
|                 {
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 writer.setEncoding(content.getEncoding());
 | |
|                 is = content.getInputStream();
 | |
|             }
 | |
|             writer.putContent(is);
 | |
|             
 | |
|             // update cached variables after putContent()
 | |
|             updateContentData(true);
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Set the content stream from another input stream.
 | |
|          *  
 | |
|          * @param inputStream InputStream
 | |
|          */
 | |
|         public void write(InputStream inputStream)
 | |
|         {
 | |
|             ContentService contentService = services.getContentService();
 | |
|             ContentWriter writer = contentService.getWriter(nodeRef, this.property, true);
 | |
|             writer.putContent(inputStream);
 | |
| 
 | |
|             // update cached variables after putContent()
 | |
|             updateContentData(true);
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Delete the content stream
 | |
|          */
 | |
|         public void delete()
 | |
|         {
 | |
|             ContentService contentService = services.getContentService();
 | |
|             ContentWriter writer = contentService.getWriter(nodeRef, this.property, true);
 | |
|             OutputStream output = writer.getContentOutputStream();
 | |
|             try
 | |
|             {
 | |
|                 output.close();
 | |
|             }
 | |
|             catch (IOException e)
 | |
|             {
 | |
|                 // NOTE: fall-through
 | |
|             }
 | |
|             writer.setMimetype(null);
 | |
|             writer.setEncoding(null);
 | |
|             
 | |
|             // update cached variables after putContent()
 | |
|             updateContentData(true);
 | |
|         }
 | |
|         
 | |
|         /**
 | |
|          * @return download URL to the content
 | |
|          */
 | |
|         public String getUrl()
 | |
|         {
 | |
|             return MessageFormat.format(CONTENT_PROP_URL, new Object[] { nodeRef.getStoreRef().getProtocol(),
 | |
|                     nodeRef.getStoreRef().getIdentifier(), nodeRef.getId(),
 | |
|                     URLEncoder.encode(getName()),
 | |
|                     URLEncoder.encode(property.toString()) });
 | |
|         }
 | |
|         
 | |
|         /**
 | |
|          * @return download URL to the content for a document item only
 | |
|          */
 | |
|         public String getDownloadUrl()
 | |
|         {
 | |
|             if (getIsDocument() == true)
 | |
|             {
 | |
|                 return MessageFormat.format(CONTENT_DOWNLOAD_PROP_URL, new Object[] {
 | |
|                         nodeRef.getStoreRef().getProtocol(),
 | |
|                         nodeRef.getStoreRef().getIdentifier(),
 | |
|                         nodeRef.getId(),
 | |
|                         URLEncoder.encode(getName()),
 | |
|                         URLEncoder.encode(property.toString()) });
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 return "";
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public long getSize()
 | |
|         {
 | |
|             return contentData.getSize();
 | |
|         }
 | |
|         
 | |
|         public String getMimetype()
 | |
|         {
 | |
|             return contentData.getMimetype();
 | |
|         }
 | |
|         
 | |
|         public String getEncoding()
 | |
|         {
 | |
|             return contentData.getEncoding();
 | |
|         }
 | |
|         
 | |
|         public void setEncoding(String encoding)
 | |
|         {
 | |
|             this.contentData = ContentData.setEncoding(this.contentData, encoding);
 | |
|             services.getNodeService().setProperty(nodeRef, this.property, this.contentData);
 | |
|             updateContentData(false);
 | |
|         }
 | |
| 
 | |
|         public void setMimetype(String mimetype)
 | |
|         {
 | |
|             this.contentData = ContentData.setMimetype(this.contentData, mimetype);
 | |
|             services.getNodeService().setProperty(nodeRef, this.property, this.contentData);
 | |
|             updateContentData(false);
 | |
|         }
 | |
|         
 | |
|         /**
 | |
|          * Guess the mimetype for the given filename - uses the extension to match on system mimetype map
 | |
|          */
 | |
|         public void guessMimetype(String filename)
 | |
|         {
 | |
|             ContentService contentService = services.getContentService();
 | |
|             ContentReader reader = contentService.getReader(nodeRef, property); 
 | |
|             // MNT-12265 Browser sets a mimetype based on extension of file. But mimeType from browser can be
 | |
|             // different as mapped in Alfresco for current extension. Therefore we need to guess a mimetype based on
 | |
|             // map in Alfresco
 | |
|             String typeByExt = services.getMimetypeService().guessMimetype(filename);
 | |
|             if (reader != null && reader.getMimetype() != null && !typeByExt.equals(MimetypeMap.MIMETYPE_BINARY))
 | |
|             {
 | |
|                 setMimetype(typeByExt);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 setMimetype(services.getMimetypeService().guessMimetype(filename, reader));
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         /**
 | |
|          * Guess the character encoding of a file. For non-text files UTF-8 default is applied, otherwise
 | |
|          * the appropriate encoding (such as UTF-16 or similar) will be appiled if detected.
 | |
|          */
 | |
|         public void guessEncoding()
 | |
|         {
 | |
|             setEncoding(guessEncoding(getInputStream(), true));
 | |
|         }
 | |
|         
 | |
|         private String guessEncoding(InputStream in, boolean close)
 | |
|         {
 | |
|             String encoding = "UTF-8";
 | |
|             try
 | |
|             {
 | |
|                 if (in != null)
 | |
|                 {
 | |
|                     Charset charset = services.getMimetypeService().getContentCharsetFinder().getCharset(in, getMimetype());
 | |
|                     encoding = charset.name();
 | |
|                 }
 | |
|             }
 | |
|             finally
 | |
|             {
 | |
|                 try
 | |
|                 {
 | |
|                     if (close && in != null)
 | |
|                     {
 | |
|                         in.close();
 | |
|                     }
 | |
|                 }
 | |
|                 catch (IOException ioErr)
 | |
|                 {
 | |
|                 }
 | |
|             }
 | |
|             return encoding;
 | |
|         }
 | |
|         
 | |
|         /**
 | |
|          * Update cached contentData and the isDirty flag
 | |
|          */
 | |
|         private void updateContentData(boolean touchContent)
 | |
|         {
 | |
|             this.contentData = (ContentData) services.getNodeService().getProperty(nodeRef, this.property);
 | |
|             this.isDirty = touchContent ? true : this.isDirty;
 | |
|         }
 | |
|         
 | |
|         private ContentData contentData;
 | |
|         private QName property;
 | |
|         private boolean isDirty;
 | |
|     }
 | |
|     
 | |
|     /**
 | |
|      * 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
 | |
|          */
 | |
|         ScriptNode transform(ContentService contentService, NodeRef noderef,
 | |
|             ContentReader reader, ContentWriter writer);
 | |
|     }
 | |
|     
 | |
|     private abstract class AbstractTransformer implements Transformer
 | |
|     {
 | |
|         public ScriptNode transform(ContentService contentService, NodeRef nodeRef,
 | |
|             ContentReader reader, ContentWriter writer)
 | |
|         {
 | |
|             ScriptNode transformedNode = null;
 | |
| 
 | |
|             try
 | |
|             {
 | |
|                 doTransform(contentService, reader, writer);
 | |
|                 transformedNode = newInstance(nodeRef, services, scope);
 | |
|             }
 | |
|             catch (NoTransformerException e)
 | |
|             {
 | |
|                 // ignore
 | |
|             }
 | |
|             catch (AlfrescoRuntimeException e)
 | |
|             {
 | |
|                 Throwable rootCause = ((AlfrescoRuntimeException)e).getRootCause();
 | |
|                 String message = rootCause.getMessage();
 | |
|                 message = message == null ? "" : message;
 | |
|                 if (rootCause instanceof UnimportantTransformException)
 | |
|                 {
 | |
|                     logger.debug(message);
 | |
|                     // ignore
 | |
|                 }
 | |
|                 else if (rootCause instanceof UnsupportedTransformationException)
 | |
|                 {
 | |
|                     logger.error(message);
 | |
|                     // ignore
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     throw e;
 | |
|                 }
 | |
|             }
 | |
|             return transformedNode;
 | |
|         }
 | |
| 
 | |
|         protected abstract void doTransform(ContentService contentService,
 | |
|             ContentReader reader, ContentWriter writer);
 | |
|     };
 | |
| 
 | |
|     /**
 | |
|      * NamespacePrefixResolverProvider getter implementation
 | |
|      */
 | |
|     public NamespacePrefixResolver getNamespacePrefixResolver()
 | |
|     {
 | |
|         return this.services.getNamespaceService();
 | |
|     }
 | |
| }
 |