diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java index a22dc3a605..72eae3d899 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java @@ -30,8 +30,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.servlet.http.HttpServletRequest; @@ -462,11 +464,19 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr PagingRequest pageRequest = new PagingRequest(skip, max, null); pageRequest.setRequestTotalCountMax(skip + 10000); // TODO make this optional/configurable - // - affects whether numItems may be returned - + // - affects whether numItems may be returned + Set typeqnames = new HashSet(); + List allTypes = connector.getOpenCMISDictionaryService().getAllTypes(); + for (TypeDefinitionWrapper type : allTypes) + { + typeqnames.add(type.getAlfrescoClass()); + } PagingResults pageOfNodeInfos = connector.getFileFolderService().list( - folderNodeRef, true, true, - null, sortProps, pageRequest); + folderNodeRef, + typeqnames, + null, //ignoreAspectQNames, + sortProps, + pageRequest); if (max > 0) { @@ -733,7 +743,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr throw new CmisConstraintException("Relationships are not fileable!"); } - if (info.isFolder() && !info.isRootFolder()) + if (info.isItem() || (info.isFolder() && !info.isRootFolder())) { List parentInfos = info.getParents(); if (!parentInfos.isEmpty()) @@ -1072,11 +1082,21 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr NamespaceService.CONTENT_MODEL_1_0_URI, QName.createValidLocalName(name)); - ChildAssociationRef newRef = connector.getNodeService().createNode(parentInfo.getNodeRef(), ContentModel.ASSOC_CONTAINS, assocQName, type.getAlfrescoClass()); - + Map props = new HashMap(11); + props.put(ContentModel.PROP_NAME, (Serializable) assocQName.getLocalName()); + + ChildAssociationRef newRef = connector.getNodeService().createNode( + parentInfo.getNodeRef(), + ContentModel.ASSOC_CONTAINS, + assocQName, + type.getAlfrescoClass(), + props); + NodeRef nodeRef = newRef.getChildRef(); - + connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, PropertyIds.OBJECT_TYPE_ID }); + connector.getNodeService().setProperty(nodeRef, ContentModel.PROP_NAME, assocQName.getLocalName()); + connector.applyPolicies(nodeRef, type, policies); connector.applyACL(nodeRef, type, addAces, removeAces); @@ -2780,6 +2800,11 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr info.setSupportsDescendants(true); info.setSupportsFolderTree(true); } + else if (ni.isItem()) + { + info.setHasAcl(true); + info.setHasContent(false); + } return info; } diff --git a/source/java/org/alfresco/opencmis/CMISConnector.java b/source/java/org/alfresco/opencmis/CMISConnector.java index 301976994c..b59ced16d8 100644 --- a/source/java/org/alfresco/opencmis/CMISConnector.java +++ b/source/java/org/alfresco/opencmis/CMISConnector.java @@ -58,6 +58,8 @@ import org.alfresco.opencmis.dictionary.CMISNodeInfo; import org.alfresco.opencmis.dictionary.CMISObjectVariant; import org.alfresco.opencmis.dictionary.CMISPropertyAccessor; import org.alfresco.opencmis.dictionary.DocumentTypeDefinitionWrapper; +import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper; +import org.alfresco.opencmis.dictionary.ItemTypeDefinitionWrapper; import org.alfresco.opencmis.dictionary.PropertyDefinitionWrapper; import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; import org.alfresco.opencmis.mapping.DirectProperty; @@ -1262,15 +1264,15 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen */ public String createObjectId(NodeRef currentVersionNodeRef) { - FileInfo fileInfo = getFileFolderService().getFileInfo(currentVersionNodeRef); - - if(fileInfo == null) + QName typeQName = nodeService.getType(currentVersionNodeRef); + TypeDefinitionWrapper type = getOpenCMISDictionaryService().findNodeType(typeQName); + + if(type instanceof ItemTypeDefinitionWrapper) { - // not a file or a folder return constructObjectId(currentVersionNodeRef, null); } - if(fileInfo.isFolder()) + if(type instanceof FolderTypeDefintionWrapper) { return constructObjectId(currentVersionNodeRef, null); } diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index a5727e5cb7..c727021547 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -1,1626 +1,1647 @@ -/* - * Copyright (C) 2005-2013 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - */ -package org.alfresco.repo.model.filefolder; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.ResourceBundle.Control; -import java.util.Set; -import java.util.Stack; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.query.CannedQueryFactory; -import org.alfresco.query.CannedQueryResults; -import org.alfresco.query.PagingRequest; -import org.alfresco.query.PagingResults; -import org.alfresco.repo.copy.AbstractBaseCopyService; -import org.alfresco.repo.model.filefolder.HiddenAspect.Visibility; -import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; -import org.alfresco.repo.policy.BehaviourFilter; -import org.alfresco.repo.search.QueryParameterDefImpl; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.permissions.PermissionCheckedCollection.PermissionCheckedCollectionMixin; -import org.alfresco.repo.security.permissions.PermissionCheckedValue.PermissionCheckedValueMixin; -import org.alfresco.service.Auditable; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.model.FileExistsException; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileFolderServiceType; -import org.alfresco.service.cmr.model.FileFolderUtil; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.model.FileNotFoundException; -import org.alfresco.service.cmr.model.SubFolderFilter; -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.CopyService; -import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.MimetypeService; -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.search.QueryParameterDefinition; -import org.alfresco.service.cmr.search.SearchService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.FileFilterMode; -import org.alfresco.util.FileFilterMode.Client; -import org.alfresco.util.GUID; -import org.alfresco.util.Pair; -import org.alfresco.util.ParameterCheck; -import org.alfresco.util.SearchLanguageConversion; -import org.alfresco.util.registry.NamedObjectRegistry; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.extensions.surf.util.I18NUtil; - -/** - * Implementation of the file/folder-specific service. - * - * @author Derek Hulley - */ -public class FileFolderServiceImpl extends AbstractBaseCopyService implements FileFolderService -{ - private static final String CANNED_QUERY_FILEFOLDER_LIST = "fileFolderGetChildrenCannedQueryFactory"; - - /** Shallow search for files and folders with a name pattern */ - private static final String XPATH_QUERY_SHALLOW_ALL = - "./*" + - "[like(@cm:name, $cm:name, false)" + - " and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" + - " and (subtypeOf('" + ContentModel.TYPE_FOLDER + "') or subtypeOf('" + ContentModel.TYPE_CONTENT + "')" + - " or subtypeOf('" + ContentModel.TYPE_LINK + "'))]"; - - /** Deep search for files and folders with a name pattern */ - private static final String XPATH_QUERY_DEEP_ALL = - ".//*" + - "[like(@cm:name, $cm:name, false)" + - " and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" + - " and (subtypeOf('" + ContentModel.TYPE_FOLDER + "') or subtypeOf('" + ContentModel.TYPE_CONTENT + "')" + - " or subtypeOf('" + ContentModel.TYPE_LINK + "'))]"; - - /** Deep search for folders with a name pattern */ - private static final String XPATH_QUERY_DEEP_FOLDERS = - ".//*" + - "[like(@cm:name, $cm:name, false)" + - " and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" + - " and (subtypeOf('" + ContentModel.TYPE_FOLDER + "'))]"; - - /** Deep search for files with a name pattern */ - private static final String XPATH_QUERY_DEEP_FILES = - ".//*" + - "[like(@cm:name, $cm:name, false)" + - " and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" + - " and (subtypeOf('" + ContentModel.TYPE_CONTENT + "')" + - " or subtypeOf('" + ContentModel.TYPE_LINK + "'))]"; - - private static Log logger = LogFactory.getLog(FileFolderServiceImpl.class); - - private HiddenAspect hiddenAspect; - private NamespaceService namespaceService; - private DictionaryService dictionaryService; - private NodeService nodeService; - private CopyService copyService; - private SearchService searchService; - private ContentService contentService; - private MimetypeService mimetypeService; - private BehaviourFilter behaviourFilter; - private NamedObjectRegistry> cannedQueryRegistry; - - private boolean preserveModificationData = true; - - // TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) - private List systemPaths; - - // default cutoff - applies to list "all" methods - private int defaultListMaxResults = 5000; - - /** - * Default constructor - */ - public FileFolderServiceImpl() - { - super(); - } - - public void setNamespaceService(NamespaceService namespaceService) - { - this.namespaceService = namespaceService; - } - - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setCopyService(CopyService copyService) - { - this.copyService = copyService; - } - - public void setSearchService(SearchService searchService) - { - this.searchService = searchService; - } - - public void setContentService(ContentService contentService) - { - this.contentService = contentService; - } - - public void setMimetypeService(MimetypeService mimetypeService) - { - this.mimetypeService = mimetypeService; - } - - public void setHiddenAspect(HiddenAspect hiddenAspect) - { - this.hiddenAspect = hiddenAspect; - } - - /** - * Set the registry of {@link CannedQueryFactory canned queries} - */ - public void setCannedQueryRegistry(NamedObjectRegistry> cannedQueryRegistry) - { - this.cannedQueryRegistry = cannedQueryRegistry; - } - - // TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) - public void setSystemPaths(List systemPaths) - { - this.systemPaths = systemPaths; - } - - public void setDefaultListMaxResults(int defaultListMaxResults) - { - this.defaultListMaxResults = defaultListMaxResults; - } - - public void setBehaviourFilter(BehaviourFilter behaviourFilter) - { - this.behaviourFilter = behaviourFilter; - } - - public void setPreserveModificationData(boolean preserveModificationData) - { - this.preserveModificationData = preserveModificationData; - } - - public boolean isPreserveModificationData() - { - return preserveModificationData; - } - - - public void init() - { - } - - /** - * Helper method to convert node reference instances to file info - * - * @param nodeRefs the node references - * @return Return a list of file info - * @throws InvalidTypeException if the node is not a valid type - */ - private List toFileInfo(List nodeRefs) throws InvalidTypeException - { - List results = new ArrayList(nodeRefs.size()); - for (NodeRef nodeRef : nodeRefs) - { - try - { - FileInfo fileInfo = toFileInfo(nodeRef, true); - results.add(fileInfo); - } - catch (InvalidNodeRefException inre) - { - logger.warn("toFileInfo: "+inre); - // skip - } - } - return results; - } - - /** - * Helper method to convert a node reference instance to a file info - */ - private FileInfo toFileInfo(NodeRef nodeRef, boolean addTranslations) throws InvalidTypeException - { - // Get the file attributes - Map properties = nodeService.getProperties(nodeRef); - // Is it a folder - QName typeQName = nodeService.getType(nodeRef); - boolean isFolder = isFolder(typeQName); - boolean isHidden = false; - - Client client = FileFilterMode.getClient(); - if(hiddenAspect.getVisibility(client, nodeRef) == Visibility.HiddenAttribute) - { - isHidden = true; - } - - // Construct the file info and add to the results - FileInfo fileInfo = new FileInfoImpl(nodeRef, typeQName, isFolder, isHidden, properties); - - // Done - return fileInfo; - } - - /** - * Exception when the type is not a valid File or Folder type - * - * @see ContentModel#TYPE_CONTENT - * @see ContentModel#TYPE_FOLDER - * - * @author Derek Hulley - */ - public static class InvalidTypeException extends RuntimeException - { - private static final long serialVersionUID = -310101369475434280L; - - public InvalidTypeException(String msg) - { - super(msg); - } - } - - /** - * Checks the type for whether it is a file or folder. All invalid types - * lead to runtime exceptions. - * - * @param typeQName the type to check - * @return Returns true if the type is a valid folder type, false if it is a file. - * @throws AlfrescoRuntimeException if the type is not handled by this service - */ - private boolean isFolder(QName typeQName) throws InvalidTypeException - { - FileFolderServiceType type = getType(typeQName); - - switch (type) - { - case FILE: - return false; - case FOLDER: - return true; - case SYSTEM_FOLDER: - throw new InvalidTypeException("This service should ignore type " + ContentModel.TYPE_SYSTEM_FOLDER); - case INVALID: - default: - throw new InvalidTypeException("Type is not handled by this service: " + typeQName); - } - } - - public boolean exists(NodeRef nodeRef) - { - return nodeService.exists(nodeRef); - } - - public FileFolderServiceType getType(QName typeQName) - { - if (dictionaryService.isSubClass(typeQName, ContentModel.TYPE_FOLDER)) - { - if (dictionaryService.isSubClass(typeQName, ContentModel.TYPE_SYSTEM_FOLDER)) - { - return FileFolderServiceType.SYSTEM_FOLDER; - } - return FileFolderServiceType.FOLDER; - } - else if (dictionaryService.isSubClass(typeQName, ContentModel.TYPE_CONTENT) || - dictionaryService.isSubClass(typeQName, ContentModel.TYPE_LINK)) - { - // it is a regular file - return FileFolderServiceType.FILE; - } - else - { - // unhandled type - return FileFolderServiceType.INVALID; - } - } - - public List list(NodeRef contextNodeRef) - { - // execute the query - List results = listSimple(contextNodeRef, true, true); - // done - if (logger.isTraceEnabled()) - { - logger.trace("List files and folders: \n" + - " context: " + contextNodeRef + "\n" + - " results: " + results); - } - return results; - } - - private PagingResults getPagingResults(PagingRequest pagingRequest, final CannedQueryResults results) - { - List nodeRefs = null; - if (results.getPageCount() > 0) - { - nodeRefs = results.getPages().get(0); - } - else - { - nodeRefs = Collections.emptyList(); - } - - // set total count - final Pair totalCount; - if (pagingRequest.getRequestTotalCountMax() > 0) - { - totalCount = results.getTotalResultCount(); - } - else - { - totalCount = null; - } - - final List nodeInfos = new ArrayList(nodeRefs.size()); - for (NodeRef nodeRef : nodeRefs) - { - nodeInfos.add(toFileInfo(nodeRef, true)); - } - PermissionCheckedCollectionMixin.create(nodeInfos, nodeRefs); - - return new PagingResults() - { - @Override - public String getQueryExecutionId() - { - return results.getQueryExecutionId(); - } - @Override - public List getPage() - { - return nodeInfos; - } - @Override - public boolean hasMoreItems() - { - return results.hasMoreItems(); - } - @Override - public Pair getTotalResultCount() - { - return totalCount; - } - }; - } - - /* (non-Javadoc) - * @see org.alfresco.service.cmr.model.FileFolderService#list(org.alfresco.service.cmr.repository.NodeRef, boolean, boolean, java.util.Set, org.alfresco.service.cmr.model.PagingSortRequest) - */ - @Auditable(parameters = {"contextNodeRef", "files", "folders", "ignoreQNames", "sortProps", "pagingRequest"}) - public PagingResults list(NodeRef contextNodeRef, - boolean files, - boolean folders, - Set ignoreQNames, - List> sortProps, - PagingRequest pagingRequest) - { - ParameterCheck.mandatory("contextNodeRef", contextNodeRef); - ParameterCheck.mandatory("pagingRequest", pagingRequest); - - Pair,Set> pair = buildSearchTypesAndIgnoreAspects(files, folders, ignoreQNames); - Set searchTypeQNames = pair.getFirst(); - Set ignoreAspectQNames = pair.getSecond(); - - // execute query - final CannedQueryResults results = listImpl(contextNodeRef, null, searchTypeQNames, ignoreAspectQNames, sortProps, pagingRequest); - return getPagingResults(pagingRequest, results); - } - - @Override - public PagingResults list(NodeRef contextNodeRef, boolean files, boolean folders, String pattern, Set ignoreQNames, List> sortProps, PagingRequest pagingRequest) - { - ParameterCheck.mandatory("contextNodeRef", contextNodeRef); - ParameterCheck.mandatory("pagingRequest", pagingRequest); - - Pair,Set> pair = buildSearchTypesAndIgnoreAspects(files, folders, ignoreQNames); - Set searchTypeQNames = pair.getFirst(); - Set ignoreAspectQNames = pair.getSecond(); - - // execute query - final CannedQueryResults results = listImpl(contextNodeRef, pattern, searchTypeQNames, ignoreAspectQNames, sortProps, pagingRequest); - return getPagingResults(pagingRequest, results); - } - - private CannedQueryResults listImpl(NodeRef contextNodeRef, boolean files, boolean folders) - { - Set searchTypeQNames = buildSearchTypesAndIgnoreAspects(files, folders, null).getFirst(); - return listImpl(contextNodeRef, searchTypeQNames); - } - - private CannedQueryResults listImpl(NodeRef contextNodeRef, Set searchTypeQNames) - { - return listImpl(contextNodeRef, null, searchTypeQNames, null, null, new PagingRequest(defaultListMaxResults, null)); - } - - // note: similar to getChildAssocs(contextNodeRef, searchTypeQNames) but enables paging features, including max items, sorting etc (with permissions per-applied) - private CannedQueryResults listImpl(NodeRef contextNodeRef, String pattern, Set searchTypeQNames, Set ignoreAspectQNames, List> sortProps, PagingRequest pagingRequest) - { - Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); - - // get canned query - GetChildrenCannedQueryFactory getChildrenCannedQueryFactory = (GetChildrenCannedQueryFactory)cannedQueryRegistry.getNamedObject(CANNED_QUERY_FILEFOLDER_LIST); - - GetChildrenCannedQuery cq = (GetChildrenCannedQuery)getChildrenCannedQueryFactory.getCannedQuery(contextNodeRef, pattern, Collections.singleton(ContentModel.ASSOC_CONTAINS), searchTypeQNames, ignoreAspectQNames, null, sortProps, pagingRequest); - - // execute canned query - CannedQueryResults results = cq.execute(); - - if (start != null) - { - int cnt = results.getPagedResultCount(); - int skipCount = pagingRequest.getSkipCount(); - int maxItems = pagingRequest.getMaxItems(); - boolean hasMoreItems = results.hasMoreItems(); - Pair totalCount = (pagingRequest.getRequestTotalCountMax() > 0 ? results.getTotalResultCount() : null); - int pageNum = (skipCount / maxItems) + 1; - - logger.debug("List: "+cnt+" items in "+(System.currentTimeMillis()-start)+" msecs [pageNum="+pageNum+",skip="+skipCount+",max="+maxItems+",hasMorePages="+hasMoreItems+",totalCount="+totalCount+",parentNodeRef="+contextNodeRef+"]"); - } - - return results; - } - - public List listFiles(NodeRef contextNodeRef) - { - // execute the query - List results = listSimple(contextNodeRef, true, false); - // done - if (logger.isTraceEnabled()) - { - logger.trace("List files: \n" + - " context: " + contextNodeRef + "\n" + - " results: " + results); - } - return results; - } - - public List listFolders(NodeRef contextNodeRef) - { - // execute the query - List results = listSimple(contextNodeRef, false, true); - // done - if (logger.isTraceEnabled()) - { - logger.trace("List for folders: \n" + - " context: " + contextNodeRef + "\n" + - " results: " + results); - } - return results; - } - - public List listDeepFolders(NodeRef contextNodeRef, - SubFolderFilter filter) - { - List nodeRefs = listSimpleDeep(contextNodeRef, false, true, filter); - - List results = toFileInfo(nodeRefs); - - // done - if (logger.isTraceEnabled()) - { - logger.trace("Deep search for files: \n" + - " context: " + contextNodeRef + "\n" + - " results: " + results.size()); - } - return results; - - } - - @Override - public NodeRef getLocalizedSibling(NodeRef nodeRef) - { - Locale userLocale = I18NUtil.getLocale(); - - String name = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); - NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef(); - // Work out the base name we are working with - Pair split = getExtension(name, false); - String base = split.getFirst(); - String ext = split.getSecond(); - - NodeRef resultNodeRef = nodeRef; - // Search for siblings with the same name - Control resourceHelper = Control.getControl(Control.FORMAT_DEFAULT); - List candidateLocales = resourceHelper.getCandidateLocales(base, userLocale); - for (Locale candidateLocale : candidateLocales) - { - String filename = resourceHelper.toBundleName(base, candidateLocale) + "." + ext; - // Attempt to find the file - NodeRef foundNodeRef = searchSimple(parentNodeRef, filename); - if (foundNodeRef != null) // TODO: Check for read permissions - { - resultNodeRef = foundNodeRef; - break; - } - } - // Done - return resultNodeRef; - } - - public NodeRef searchSimple(NodeRef contextNodeRef, String name) - { - ParameterCheck.mandatory("name", name); - ParameterCheck.mandatory("contextNodeRef", contextNodeRef); - - NodeRef childNodeRef = nodeService.getChildByName(contextNodeRef, ContentModel.ASSOC_CONTAINS, name); - if (logger.isTraceEnabled()) - { - logger.trace( - "Simple name search results: \n" + - " parent: " + contextNodeRef + "\n" + - " name: " + name + "\n" + - " result: " + childNodeRef); - } - return childNodeRef; - } - - /** - * @see #search(NodeRef, String, boolean, boolean, boolean) - */ - public List search(NodeRef contextNodeRef, String namePattern, boolean includeSubFolders) - { - return search(contextNodeRef, namePattern, true, true, includeSubFolders); - } - - private static final String LUCENE_MULTI_CHAR_WILDCARD = "*"; - - /** - * Full search with all options - */ - public List search( - NodeRef contextNodeRef, - String namePattern, - boolean fileSearch, - boolean folderSearch, - boolean includeSubFolders) - { - // get the raw nodeRefs - List nodeRefs = searchInternal(contextNodeRef, namePattern, fileSearch, folderSearch, includeSubFolders); - - List results = toFileInfo(nodeRefs); - - // eliminate unwanted files/folders - Iterator iterator = results.iterator(); - while (iterator.hasNext()) - { - FileInfo file = iterator.next(); - if (file.isFolder() && !folderSearch) - { - iterator.remove(); - } - else if (!file.isFolder() && !fileSearch) - { - iterator.remove(); - } - } - // done - if (logger.isTraceEnabled()) - { - logger.trace("Deep search: \n" + - " context: " + contextNodeRef + "\n" + - " pattern: " + namePattern + "\n" + - " files: " + fileSearch + "\n" + - " folders: " + folderSearch + "\n" + - " deep: " + includeSubFolders + "\n" + - " results: " + results); - } - return results; - } - - /** - * Performs a full search, but doesn't translate the node references into - * file info objects. This allows {@link #checkExists(NodeRef, String)} to - * bypass the retrieval of node properties. - */ - private List searchInternal( - NodeRef contextNodeRef, - String namePattern, - boolean fileSearch, - boolean folderSearch, - boolean includeSubFolders) - { - // shortcut if the search is requesting nothing - if (!fileSearch && !folderSearch) - { - return Collections.emptyList(); - } - - if (namePattern == null) - { - namePattern = LUCENE_MULTI_CHAR_WILDCARD; // default to wildcard - } - // now check if we can use Lucene to handle this query - boolean anyName = namePattern.equals(LUCENE_MULTI_CHAR_WILDCARD); - - List nodeRefs = null; - if (anyName) - { - // This is search for any name - if(includeSubFolders) - { - nodeRefs = listSimpleDeep(contextNodeRef, fileSearch, folderSearch, null); - } - else - { - nodeRefs = listImpl(contextNodeRef, fileSearch, folderSearch).getPage(); - } - } - else - { - // TODO - we need to get rid of this xpath stuff - // if the name pattern is null, then we use the ANY pattern - QueryParameterDefinition[] params = null; - if (namePattern != null) - { - // the interface specifies the Lucene syntax, so perform a conversion - namePattern = SearchLanguageConversion.convert( - SearchLanguageConversion.DEF_LUCENE, - SearchLanguageConversion.DEF_XPATH_LIKE, - namePattern); - - params = new QueryParameterDefinition[1]; - params[0] = new QueryParameterDefImpl( - ContentModel.PROP_NAME, - dictionaryService.getDataType(DataTypeDefinition.TEXT), - true, - namePattern); - } - // determine the correct query to use - String query = null; - if (includeSubFolders) - { - // this is a deep search - if(!fileSearch && folderSearch) - { - // This is a folder search only; - query = XPATH_QUERY_DEEP_FOLDERS; - } - else if(fileSearch && !folderSearch) - { - // This is a folder search only; - query = XPATH_QUERY_DEEP_FILES; - } - else - { - query = XPATH_QUERY_DEEP_ALL; - } - } - else - { - // this is a shallow search - query = XPATH_QUERY_SHALLOW_ALL; - } - // execute the query - nodeRefs = searchService.selectNodes( - contextNodeRef, - query, - params, - namespaceService, - false); - } - // done - return nodeRefs; - } - - private List listSimple(NodeRef contextNodeRef, boolean files, boolean folders) throws InvalidTypeException - { - CannedQueryResults cq = listImpl(contextNodeRef, files, folders); - List nodeRefs = cq.getPage(); - - List results = toFileInfo(nodeRefs); - - // avoid re-applying permissions (for "list" canned queries) - return PermissionCheckedValueMixin.create(results); - } - - private Pair, Set> buildSearchTypesAndIgnoreAspects(boolean files, boolean folders, Set ignoreQNameTypes) - { - Set searchTypeQNames = new HashSet(100); - Set ignoreAspectQNames = null; - - // Build a list of file and folder types - if (folders) - { - searchTypeQNames.addAll(buildFolderTypes()); - } - if (files) - { - searchTypeQNames.addAll(buildFileTypes()); - } - - if (ignoreQNameTypes != null) - { - Set ignoreQNamesNotSearchTypes = new HashSet(ignoreQNameTypes); - ignoreQNamesNotSearchTypes.removeAll(searchTypeQNames); - ignoreQNamesNotSearchTypes.remove(ContentModel.TYPE_SYSTEM_FOLDER); // note: not included in buildFolderTypes() - - if (ignoreQNamesNotSearchTypes.size() > 0) - { - ignoreAspectQNames = getAspectsToIgnore(ignoreQNamesNotSearchTypes); - } - - searchTypeQNames.removeAll(ignoreQNameTypes); - } - - return new Pair, Set>(searchTypeQNames, ignoreAspectQNames); - } - - private Set getAspectsToIgnore(Set ignoreQNames) - { - Set ignoreQNameAspects = new HashSet(ignoreQNames.size()); - for (QName qname : ignoreQNames) - { - if (dictionaryService.getAspect(qname) != null) - { - ignoreQNameAspects.add(qname); - } - } - return ignoreQNameAspects; - } - - private Set buildFolderTypes() - { - Set folderTypeQNames = new HashSet(50); - - // Build a list of folder types - Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_FOLDER, true); - folderTypeQNames.addAll(qnames); - folderTypeQNames.add(ContentModel.TYPE_FOLDER); - - // Remove 'system' folders - qnames = dictionaryService.getSubTypes(ContentModel.TYPE_SYSTEM_FOLDER, true); - folderTypeQNames.removeAll(qnames); - folderTypeQNames.remove(ContentModel.TYPE_SYSTEM_FOLDER); - - return folderTypeQNames; - } - - private Set buildFileTypes() - { - Set fileTypeQNames = new HashSet(50); - - // Build a list of file types - Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_CONTENT, true); - fileTypeQNames.addAll(qnames); - fileTypeQNames.add(ContentModel.TYPE_CONTENT); - qnames = dictionaryService.getSubTypes(ContentModel.TYPE_LINK, true); - fileTypeQNames.addAll(qnames); - fileTypeQNames.add(ContentModel.TYPE_LINK); - - return fileTypeQNames; - } - - /** - * A deep version of listSimple. Which recursively walks down the tree from a given starting point, returning - * the node refs of files or folders found along the way. - *

- * The folder filter is called for each sub-folder to determine whether to search in that sub-folder, should a subfolder be excluded - * then all its chidren are excluded as well. - * - * @param contextNodeRef the starting point. - * @param folders return nodes of type folders. - * @param files return nodes of type files. - * @param subfolder filter controls which folders to search. If null then all subfolders are searched. - * @return list of node references - */ - /*

- * MER: I've added this rather than changing listSimple to minimise the risk of breaking - * the existing code. This is a quick performance improvement between using - * XPath which is awful or adding new methods to the NodeService/DB This is also a dangerous method in that it can return a - * lot of data and take a long time. - */ - private List listSimpleDeep(NodeRef contextNodeRef, boolean files, boolean folders, SubFolderFilter folderFilter) - { - if(logger.isDebugEnabled()) - { - logger.debug("searchSimpleDeep contextNodeRef:" + contextNodeRef); - } - - // To hold the results. - List result = new ArrayList(); - - // Build a list of folder types - Set folderTypeQNames = buildFolderTypes(); - Set fileTypeQNames = (files ? buildFileTypes() : new HashSet(0)); - - if(!folders && !files) - { - return Collections.emptyList(); - - } - - // Shortcut - if (folderTypeQNames.size() == 0) - { - return Collections.emptyList(); - } - - Stack toSearch = new Stack(); - toSearch.push(contextNodeRef); - - // Now we need to walk down the folders. - while(!toSearch.empty()) - { - NodeRef currentDir = toSearch.pop(); - - List folderAssocRefs = nodeService.getChildAssocs(currentDir, folderTypeQNames); - - for (ChildAssociationRef folderRef : folderAssocRefs) - { - // We have some child folders - boolean include = true; - if(folderFilter != null) - { - include = folderFilter.isEnterSubfolder(folderRef); - if(include) - { - // yes search in these subfolders - toSearch.push(folderRef.getChildRef()); - } - } - else - { - // No filter - Add the folders in the currentDir - toSearch.push(folderRef.getChildRef()); - } - - if(folders && include) - { - result.add(folderRef.getChildRef()); - } - } - - if(files) - { - // Add the files in the current dir - List fileAssocRefs = nodeService.getChildAssocs(currentDir, fileTypeQNames); - for (ChildAssociationRef fileRef : fileAssocRefs) - { - result.add(fileRef.getChildRef()); - } - } - } - - - if(logger.isDebugEnabled()) - { - logger.debug("searchSimpleDeep finished size:" + result.size()); - } - - // Done - return result; - } - - /** - * @see #move(NodeRef, NodeRef, String) - */ - public FileInfo rename(NodeRef sourceNodeRef, String newName) throws FileExistsException, FileNotFoundException - { - return moveOrCopy(sourceNodeRef, null, null, newName, true); - } - - /** - * @see #moveOrCopy(NodeRef, NodeRef, String, boolean) - */ - @Override - public FileInfo move(NodeRef sourceNodeRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException - { - return moveOrCopy(sourceNodeRef, null, targetParentRef, newName, true); - } - - /** - * @see #moveOrCopy(NodeRef, NodeRef, String, boolean) - */ - @Override - public FileInfo moveFrom(NodeRef sourceNodeRef, NodeRef sourceParentRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException - { - return moveOrCopy(sourceNodeRef, sourceParentRef, targetParentRef, newName, true); - } - - /** - * @deprecated - */ - @Override - public FileInfo move(NodeRef sourceNodeRef, NodeRef sourceParentRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException - { - return moveOrCopy(sourceNodeRef, sourceParentRef, targetParentRef, newName, true); - } - - /** - * @see #moveOrCopy(NodeRef, NodeRef, String, boolean) - */ - public FileInfo copy(NodeRef sourceNodeRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException - { - return moveOrCopy(sourceNodeRef, null, targetParentRef, newName, false); - } - - /** - * Implements both move and copy behaviour - * - * @param move true to move, otherwise false to copy - */ - private FileInfo moveOrCopy(NodeRef sourceNodeRef, NodeRef sourceParentRef, NodeRef targetParentRef, String newName, boolean move) throws FileExistsException, FileNotFoundException - { - // get file/folder in its current state - FileInfo beforeFileInfo = toFileInfo(sourceNodeRef, true); - // check the name - null means keep the existing name - if (newName == null) - { - newName = beforeFileInfo.getName(); - } - - boolean nameChanged = (newName.equals(beforeFileInfo.getName()) == false); - - AssociationCopyInfo targetInfo = getAssociationCopyInfo(nodeService, sourceNodeRef, sourceParentRef, newName, nameChanged); - QName qname = targetInfo.getTargetAssocQName(); - boolean isPrimaryParent = targetInfo.getSourceParentAssoc().isPrimary(); - ChildAssociationRef assocRef = targetInfo.getSourceParentAssoc(); - - if (targetParentRef == null) - { - targetParentRef = assocRef.getParentRef(); - } - - boolean changedParent = !targetParentRef.equals(assocRef.getParentRef()); - // there is nothing to do if both the name and parent folder haven't changed - if (!nameChanged && !changedParent) - { - if (logger.isDebugEnabled()) - { - logger.debug("Doing nothing - neither filename or parent has changed: \n" + - " parent: " + targetParentRef + "\n" + - " before: " + beforeFileInfo + "\n" + - " new name: " + newName); - } - return beforeFileInfo; - } - - QName targetParentType = nodeService.getType(targetParentRef); - - // Fix AWC-1517 & ALF-5569 - QName assocTypeQname = null; - if (nameChanged && move) - { - // if it's a rename use the existing assoc type - assocTypeQname = assocRef.getTypeQName(); - } - else - { - if (dictionaryService.isSubClass(targetParentType, ContentModel.TYPE_FOLDER)) - { - assocTypeQname = ContentModel.ASSOC_CONTAINS; // cm:folder -> cm:contains - } - else if (dictionaryService.isSubClass(targetParentType, ContentModel.TYPE_CONTAINER)) - { - assocTypeQname = ContentModel.ASSOC_CHILDREN; // sys:container -> sys:children - } - else - { - throw new InvalidTypeException("Unexpected type (" + targetParentType + ") for target parent: " + targetParentRef); - } - } - - // move or copy - NodeRef targetNodeRef = null; - if (move) - { - // TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) - if (!isSystemPath(sourceNodeRef)) - { - // The cm:name might clash with another node in the target location. - if (nameChanged) - { - // The name will be changing, so we really need to set the node's name to the new - // name. This can't be done at the same time as the move - to avoid incorrect violations - // of the name constraints, the cm:name is set to something random and will be reset - // to the correct name later. - nodeService.setProperty(sourceNodeRef, ContentModel.PROP_NAME, GUID.generate()); - } - try - { - ChildAssociationRef newAssocRef = null; - - if (isPrimaryParent) - { - // move the node so that the association moves as well - boolean auditableBehaviorWasDisabled = preserveModificationData && behaviourFilter.isEnabled(ContentModel.ASPECT_AUDITABLE); - if (auditableBehaviorWasDisabled) - { - behaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE); - } - - try - { - newAssocRef = nodeService.moveNode(sourceNodeRef, targetParentRef, assocTypeQname, qname); - } - finally - { - if (auditableBehaviorWasDisabled) - { - behaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE); - } - } - } - else - { - nodeService.removeChild(sourceParentRef, sourceNodeRef); - newAssocRef = nodeService.addChild(targetParentRef, sourceNodeRef, assocRef.getTypeQName(), assocRef.getQName()); - } - - targetNodeRef = newAssocRef.getChildRef(); - } - catch (DuplicateChildNodeNameException e) - { - throw new FileExistsException(targetParentRef, newName); - } - } - else - { - // system path folders do not need to be moved - targetNodeRef = sourceNodeRef; - } - } - else - { - // Check if during copy top level name will be changed to some new - String newNameAfterCopy = copyService.getTopLevelNodeNewName(sourceNodeRef, targetParentRef, assocTypeQname, qname); - if (newNameAfterCopy != null && !newNameAfterCopy.equals(newName)) - { - newName = newNameAfterCopy; - qname = QName.createQName( - assocRef.getQName().getNamespaceURI(), - QName.createValidLocalName(newNameAfterCopy)); - } - - try - { - // Copy the node. The cm:name will be dropped and reset later. - targetNodeRef = copyService.copy( - sourceNodeRef, - targetParentRef, - assocTypeQname, - qname, - true); - } - catch (DuplicateChildNodeNameException e) - { - throw new FileExistsException(targetParentRef, newName); - } - } - - // Only update the name if it has changed - String currentName = (String)nodeService.getProperty(targetNodeRef, ContentModel.PROP_NAME); - - // ALF-13949: WorkingCopyAspect intentionally generates new names for all copies of working copies (which no - // longer have the working copy aspect) so leave these alone after copy - if (!currentName.equals(newName) && (move || !nodeService.hasAspect(sourceNodeRef, ContentModel.ASPECT_WORKING_COPY))) - { - try - { - // changed the name property - nodeService.setProperty(targetNodeRef, ContentModel.PROP_NAME, newName); - - // May need to update the mimetype, to support apps using .tmp files when saving - ContentData contentData = (ContentData)nodeService.getProperty(targetNodeRef, ContentModel.PROP_CONTENT); - - // Check the newName and oldName extensions. - // Keep previous mimetype if - // 1. new extension is empty - // 2. new extension is '.tmp' - // 3. extension was not changed, - // - // It fixes the ETWOTWO-16 issue. - String oldExt = getExtension(beforeFileInfo.getName(), true).getSecond(); - String newExt = getExtension(newName, true).getSecond(); - if (contentData != null && - newExt.length() != 0 && - !"tmp".equalsIgnoreCase(newExt) && - !newExt.equalsIgnoreCase(oldExt)) - { - String targetMimetype = contentData.getMimetype(); - final ContentReader reader = contentService.getReader(targetNodeRef, ContentModel.PROP_CONTENT); - String newMimetype = mimetypeService.guessMimetype(newName, reader); - if (!targetMimetype.equalsIgnoreCase(newMimetype)) - { - contentData = ContentData.setMimetype(contentData, newMimetype); - nodeService.setProperty(targetNodeRef, ContentModel.PROP_CONTENT, contentData); - } - } - } - catch (DuplicateChildNodeNameException e) - { - throw new FileExistsException(targetParentRef, newName); - } - } - - // get the details after the operation - FileInfo afterFileInfo = toFileInfo(targetNodeRef, true); - // done - if (logger.isDebugEnabled()) - { - logger.debug("" + (move ? "Moved" : "Copied") + " node: \n" + - " parent: " + targetParentRef + "\n" + - " before: " + beforeFileInfo + "\n" + - " after: " + afterFileInfo); - } - return afterFileInfo; - } - - /** - * Determine if the specified node is a special "system" folder path based node - * - * TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) - * - * @param nodeRef node to check - * @return true => system folder path based node - */ - private boolean isSystemPath(NodeRef nodeRef) - { - Path path = nodeService.getPath(nodeRef); - String prefixedPath = path.toPrefixString(namespaceService); - return systemPaths.contains(prefixedPath); - } - - public FileInfo create(NodeRef parentNodeRef, String name, QName typeQName) throws FileExistsException - { - return createImpl(parentNodeRef, name, typeQName, null); - } - - public FileInfo create(NodeRef parentNodeRef, String name, QName typeQName, QName assocQName) throws FileExistsException - { - return createImpl(parentNodeRef, name, typeQName, assocQName); - } - - private FileInfo createImpl(NodeRef parentNodeRef, String name, QName typeQName, QName assocQName) throws FileExistsException - { - // set up initial properties - Map properties = new HashMap(11); - properties.put(ContentModel.PROP_NAME, (Serializable) name); - - // create the node - if (assocQName == null) - { - assocQName = QName.createQName( - NamespaceService.CONTENT_MODEL_1_0_URI, - QName.createValidLocalName(name)); - } - ChildAssociationRef assocRef = null; - try - { - assocRef = nodeService.createNode( - parentNodeRef, - ContentModel.ASSOC_CONTAINS, - assocQName, - typeQName, - properties); - } - catch (DuplicateChildNodeNameException e) - { - throw new FileExistsException(parentNodeRef, name); - } - - NodeRef nodeRef = assocRef.getChildRef(); - - FileInfo fileInfo = toFileInfo(nodeRef, true); - // done - if (logger.isDebugEnabled()) - { - logger.debug("Created: \n" + - " parent: " + parentNodeRef + "\n" + - " created: " + fileInfo); - } - return fileInfo; - } - - public void delete(NodeRef nodeRef) - { - nodeService.deleteNode(nodeRef); - // Done - if (logger.isDebugEnabled()) - { - logger.debug("Deleted: \n" + - " node: " + nodeRef); - } - } - - /** - * Checks for the presence of, and creates as necessary, the folder structure in the provided path. - *

- * An empty path list is not allowed as it would be impossible to necessarily return file info - * for the parent node - it might not be a folder node. - * @param parentNodeRef the node under which the path will be created - * @param pathElements the folder name path to create - may not be empty - * @param folderTypeQName the types of nodes to create. This must be a valid subtype of - * {@link org.alfresco.model.ContentModel#TYPE_FOLDER they folder type}. - * @return Returns the info of the last folder in the path. - * @deprecated Use FileFolderUtil.makeFolders rather than directly accessing this implementation class. - */ - public FileInfo makeFolders(NodeRef parentNodeRef, List pathElements, QName folderTypeQName) - { - return FileFolderUtil.makeFolders(this, parentNodeRef, pathElements, folderTypeQName); - } - - /** - * Checks for the presence of, and creates as necessary, the folder structure in the provided path. - *

- * An empty path list is not allowed as it would be impossible to necessarily return file info - * for the parent node - it might not be a folder node. - * @param parentNodeRef the node under which the path will be created - * @param pathElements the folder name path to create - may not be empty - * @param folderTypeQName the types of nodes to create. This must be a valid subtype of - * {@link org.alfresco.model.ContentModel#TYPE_FOLDER they folder type}. - * @return Returns the info of the last folder in the path. - * @deprecated Use FileFolderUtil.makeFolders rather than directly accessing this implementation class. - */ - public static FileInfo makeFolders(FileFolderService service, NodeRef parentNodeRef, List pathElements, QName folderTypeQName) - { - return FileFolderUtil.makeFolders(service, parentNodeRef, pathElements, folderTypeQName); - } - - /** - * Get the file or folder information from the root down to and including the node provided. - *

    - *
  • The root node can be of any type and is not included in the path list.
  • - *
  • Only the primary path is considered. If the target node is not a descendant of the - * root along purely primary associations, then an exception is generated.
  • - *
  • If an invalid type is encountered along the path, then an exception is generated.
  • - *
- * - * @param rootNodeRef the start of the returned path, or null if the store root - * node must be assumed. - * @param nodeRef a reference to the file or folder - * @return Returns a list of file/folder infos from the root (excluded) down to and - * including the destination file or folder - * @throws FileNotFoundException if the node could not be found - */ - public List getNamePath(NodeRef rootNodeRef, NodeRef nodeRef) throws FileNotFoundException - { - // check the root - if (rootNodeRef == null) - { - rootNodeRef = nodeService.getRootNode(nodeRef.getStoreRef()); - } - try - { - ArrayList results = new ArrayList(10); - // get the primary path - Path path = nodeService.getPath(nodeRef); - // iterate and turn the results into file info objects - boolean foundRoot = false; - for (Path.Element element : path) - { - // ignore everything down to the root - Path.ChildAssocElement assocElement = (Path.ChildAssocElement) element; - final NodeRef childNodeRef = assocElement.getRef().getChildRef(); - if (childNodeRef.equals(rootNodeRef)) - { - // just found the root - but we don't put in an entry for it - foundRoot = true; - continue; - } - else if (!foundRoot) - { - // keep looking for the root - continue; - } - // we found the root and expect to be building the path up - // Run as system as the user could not have access to all folders in the path, see ALF-13816 - FileInfo pathInfo = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public FileInfo doWork() throws Exception - { - return toFileInfo(childNodeRef, true); - } - }, AuthenticationUtil.getSystemUserName()); - - // we can't append a path element to the results if there is already a (non-folder) file at the tail - // since this would result in a path anomoly - file's cannot contain other files. - if (!results.isEmpty() && !results.get(results.size()-1).isFolder()) - { - throw new InvalidTypeException( - "File is not the last element in path: files cannot contain other files."); - } - results.add(pathInfo); - } - // check that we found the root - if (!foundRoot) - { - throw new FileNotFoundException(nodeRef); - } - // done - if (logger.isDebugEnabled()) - { - logger.debug("Built name path for node: \n" + - " root: " + rootNodeRef + "\n" + - " node: " + nodeRef + "\n" + - " path: " + results); - } - return results; - } - catch (InvalidNodeRefException e) - { - throw new FileNotFoundException(nodeRef); - } - } - - /** - * Get the file or folder names from the root down to and including the node provided. - *
    - *
  • The root node can be of any type and is not included in the path list.
  • - *
  • Only the primary path is considered. If the target node is not a descendant of the - * root along purely primary associations, then an exception is generated.
  • - *
  • If an invalid type is encountered along the path, then an exception is generated.
  • - *
- * - * @param rootNodeRef the start of the returned path, or null if the store root - * node must be assumed. - * @param nodeRef a reference to the file or folder - * @return Returns a list of file/folder names from the root (excluded) down to and - * including the destination file or folder - * @throws FileNotFoundException if the node could not be found - */ - public List getNameOnlyPath(NodeRef rootNodeRef, final NodeRef nodeRef) throws FileNotFoundException - { - // check the root - if (rootNodeRef == null) - { - rootNodeRef = nodeService.getRootNode(nodeRef.getStoreRef()); - } - try - { - final NodeRef rNodeRef = rootNodeRef; - final ArrayList results = new ArrayList(10); - // Run as system as the user could not have access to all folders in the path, see ALF-13816 - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public Void doWork() throws Exception - { - // get the primary path - Path path = nodeService.getPath(nodeRef); - // iterate and turn the results into file info objects - boolean foundRoot = false; - for (Path.Element element : path) - { - // ignore everything down to the root - Path.ChildAssocElement assocElement = (Path.ChildAssocElement) element; - final NodeRef childNodeRef = assocElement.getRef().getChildRef(); - if (childNodeRef.equals(rNodeRef)) - { - // just found the root - but we don't put in an entry for it - foundRoot = true; - continue; - } - else if (!foundRoot) - { - // keep looking for the root - continue; - } - results.add(nodeService.getProperty(childNodeRef, ContentModel.PROP_NAME).toString()); - } - // check that we found the root - if (!foundRoot) - { - throw new FileNotFoundException(nodeRef); - } - // done - if (logger.isDebugEnabled()) - { - logger.debug("Built name path for node: \n" + - " root: " + rNodeRef + "\n" + - " node: " + nodeRef + "\n" + - " path: " + results); - } - return null; - } - }, AuthenticationUtil.getSystemUserName()); - - return results; - } - catch (InvalidNodeRefException e) - { - throw new FileNotFoundException(nodeRef); - } - catch (RuntimeException e) - { - // the runAs() is too keen on wrapping everything in an outer RuntimeException - which we don't want. - if (e.getCause() instanceof FileNotFoundException) - { - throw (FileNotFoundException)e.getCause(); - } - else throw e; - } - } - - public FileInfo resolveNamePath(NodeRef rootNodeRef, List pathElements) throws FileNotFoundException - { - return resolveNamePath(rootNodeRef, pathElements, true); - } - - public FileInfo resolveNamePath(NodeRef rootNodeRef, List pathElements, boolean mustExist) throws FileNotFoundException - { - if (pathElements.size() == 0) - { - throw new IllegalArgumentException("Path elements list is empty"); - } - // walk the folder tree first - NodeRef parentNodeRef = rootNodeRef; - StringBuilder currentPath = new StringBuilder(pathElements.size() << 4); - int folderCount = pathElements.size() - 1; - for (int i = 0; i < folderCount; i++) - { - String pathElement = pathElements.get(i); - NodeRef folderNodeRef = searchSimple(parentNodeRef, pathElement); - if (folderNodeRef == null) - { - if (mustExist) - { - throw new FileNotFoundException("Folder not found: " + currentPath); - } - else - { - return null; - } - } - parentNodeRef = folderNodeRef; - } - // we have resolved the folder path - resolve the last component - String pathElement = pathElements.get(pathElements.size() - 1); - NodeRef fileNodeRef = searchSimple(parentNodeRef, pathElement); - if (fileNodeRef == null) - { - if (mustExist) - { - throw new FileNotFoundException("File not found: " + currentPath); - } - else - { - return null; - } - } - FileInfo result = getFileInfo(fileNodeRef); - // found it - if (logger.isDebugEnabled()) - { - logger.debug("Resoved path element: \n" + - " root: " + rootNodeRef + "\n" + - " path: " + currentPath + "\n" + - " node: " + result); - } - return result; - } - - public FileInfo getFileInfo(NodeRef nodeRef) - { - try - { - return toFileInfo(nodeRef, true); - } - catch (InvalidTypeException e) - { - return null; - } - } - - public ContentReader getReader(NodeRef nodeRef) - { - FileInfo fileInfo = toFileInfo(nodeRef, false); - if (fileInfo.isFolder()) - { - throw new InvalidTypeException("Unable to get a content reader for a folder: " + fileInfo); - } - return contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); - } - - public ContentWriter getWriter(NodeRef nodeRef) - { - FileInfo fileInfo = toFileInfo(nodeRef, false); - if (fileInfo.isFolder()) - { - throw new InvalidTypeException("Unable to get a content writer for a folder: " + fileInfo); - } - final ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); - // Ensure that a mimetype is set based on the filename (ALF-6560) - // This has been removed from the create code in 3.4 to prevent insert-update behaviour - // of the ContentData. - if (writer.getMimetype() == null) - { - final String name = fileInfo.getName(); - writer.guessMimetype(name); - } - // Done - return writer; - } - - /** - * Split a filename into the base (part before the '.') and the extension (part after the '.') - */ - private Pair getExtension(String name, boolean useLastDot) - { - String ext = ""; - String base = name; - if (name != null) - { - name = name.trim(); - int index = useLastDot ? name.lastIndexOf('.') : name.indexOf('.'); - if (index > -1 && (index < name.length() - 1)) - { - base = name.substring(0, index); - ext = name.substring(index + 1); - } - } - return new Pair(base, ext); - } - +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.model.filefolder; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle.Control; +import java.util.Set; +import java.util.Stack; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.query.CannedQueryFactory; +import org.alfresco.query.CannedQueryResults; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.copy.AbstractBaseCopyService; +import org.alfresco.repo.model.filefolder.HiddenAspect.Visibility; +import org.alfresco.repo.node.getchildren.GetChildrenCannedQuery; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.search.QueryParameterDefImpl; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.permissions.PermissionCheckedCollection.PermissionCheckedCollectionMixin; +import org.alfresco.repo.security.permissions.PermissionCheckedValue.PermissionCheckedValueMixin; +import org.alfresco.service.Auditable; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileFolderServiceType; +import org.alfresco.service.cmr.model.FileFolderUtil; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.model.SubFolderFilter; +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.CopyService; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.MimetypeService; +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.search.QueryParameterDefinition; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.FileFilterMode; +import org.alfresco.util.FileFilterMode.Client; +import org.alfresco.util.GUID; +import org.alfresco.util.Pair; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.SearchLanguageConversion; +import org.alfresco.util.registry.NamedObjectRegistry; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * Implementation of the file/folder-specific service. + * + * @author Derek Hulley + */ +public class FileFolderServiceImpl extends AbstractBaseCopyService implements FileFolderService +{ + private static final String CANNED_QUERY_FILEFOLDER_LIST = "fileFolderGetChildrenCannedQueryFactory"; + + /** Shallow search for files and folders with a name pattern */ + private static final String XPATH_QUERY_SHALLOW_ALL = + "./*" + + "[like(@cm:name, $cm:name, false)" + + " and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" + + " and (subtypeOf('" + ContentModel.TYPE_FOLDER + "') or subtypeOf('" + ContentModel.TYPE_CONTENT + "')" + + " or subtypeOf('" + ContentModel.TYPE_LINK + "'))]"; + + /** Deep search for files and folders with a name pattern */ + private static final String XPATH_QUERY_DEEP_ALL = + ".//*" + + "[like(@cm:name, $cm:name, false)" + + " and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" + + " and (subtypeOf('" + ContentModel.TYPE_FOLDER + "') or subtypeOf('" + ContentModel.TYPE_CONTENT + "')" + + " or subtypeOf('" + ContentModel.TYPE_LINK + "'))]"; + + /** Deep search for folders with a name pattern */ + private static final String XPATH_QUERY_DEEP_FOLDERS = + ".//*" + + "[like(@cm:name, $cm:name, false)" + + " and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" + + " and (subtypeOf('" + ContentModel.TYPE_FOLDER + "'))]"; + + /** Deep search for files with a name pattern */ + private static final String XPATH_QUERY_DEEP_FILES = + ".//*" + + "[like(@cm:name, $cm:name, false)" + + " and not (subtypeOf('" + ContentModel.TYPE_SYSTEM_FOLDER + "'))" + + " and (subtypeOf('" + ContentModel.TYPE_CONTENT + "')" + + " or subtypeOf('" + ContentModel.TYPE_LINK + "'))]"; + + private static Log logger = LogFactory.getLog(FileFolderServiceImpl.class); + + private HiddenAspect hiddenAspect; + private NamespaceService namespaceService; + private DictionaryService dictionaryService; + private NodeService nodeService; + private CopyService copyService; + private SearchService searchService; + private ContentService contentService; + private MimetypeService mimetypeService; + private BehaviourFilter behaviourFilter; + private NamedObjectRegistry> cannedQueryRegistry; + + private boolean preserveModificationData = true; + + // TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) + private List systemPaths; + + // default cutoff - applies to list "all" methods + private int defaultListMaxResults = 5000; + + /** + * Default constructor + */ + public FileFolderServiceImpl() + { + super(); + } + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setCopyService(CopyService copyService) + { + this.copyService = copyService; + } + + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + public void setHiddenAspect(HiddenAspect hiddenAspect) + { + this.hiddenAspect = hiddenAspect; + } + + /** + * Set the registry of {@link CannedQueryFactory canned queries} + */ + public void setCannedQueryRegistry(NamedObjectRegistry> cannedQueryRegistry) + { + this.cannedQueryRegistry = cannedQueryRegistry; + } + + // TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) + public void setSystemPaths(List systemPaths) + { + this.systemPaths = systemPaths; + } + + public void setDefaultListMaxResults(int defaultListMaxResults) + { + this.defaultListMaxResults = defaultListMaxResults; + } + + public void setBehaviourFilter(BehaviourFilter behaviourFilter) + { + this.behaviourFilter = behaviourFilter; + } + + public void setPreserveModificationData(boolean preserveModificationData) + { + this.preserveModificationData = preserveModificationData; + } + + public boolean isPreserveModificationData() + { + return preserveModificationData; + } + + + public void init() + { + } + + /** + * Helper method to convert node reference instances to file info + * + * @param nodeRefs the node references + * @return Return a list of file info + * @throws InvalidTypeException if the node is not a valid type + */ + private List toFileInfo(List nodeRefs) throws InvalidTypeException + { + List results = new ArrayList(nodeRefs.size()); + for (NodeRef nodeRef : nodeRefs) + { + try + { + FileInfo fileInfo = toFileInfo(nodeRef, true); + results.add(fileInfo); + } + catch (InvalidNodeRefException inre) + { + logger.warn("toFileInfo: "+inre); + // skip + } + } + return results; + } + + /** + * Helper method to convert a node reference instance to a file info + */ + private FileInfo toFileInfo(NodeRef nodeRef, boolean addTranslations) throws InvalidTypeException + { + // Get the file attributes + Map properties = nodeService.getProperties(nodeRef); + // Is it a folder + QName typeQName = nodeService.getType(nodeRef); + + FileFolderServiceType type = getType(typeQName); + + boolean isFolder = type.equals(FileFolderServiceType.FOLDER); + boolean isHidden = false; + + Client client = FileFilterMode.getClient(); + if(hiddenAspect.getVisibility(client, nodeRef) == Visibility.HiddenAttribute) + { + isHidden = true; + } + + // Construct the file info and add to the results + FileInfo fileInfo = new FileInfoImpl(nodeRef, typeQName, isFolder, isHidden, properties); + + // Done + return fileInfo; + } + + /** + * Exception when the type is not a valid File or Folder type + * + * @see ContentModel#TYPE_CONTENT + * @see ContentModel#TYPE_FOLDER + * + * @author Derek Hulley + */ + public static class InvalidTypeException extends RuntimeException + { + private static final long serialVersionUID = -310101369475434280L; + + public InvalidTypeException(String msg) + { + super(msg); + } + } + + /** + * Checks the type for whether it is a file or folder. All invalid types + * lead to runtime exceptions. + * + * @param typeQName the type to check + * @return Returns true if the type is a valid folder type, false if it is a file. + * @throws AlfrescoRuntimeException if the type is not handled by this service + */ + private boolean isFolder(QName typeQName) throws InvalidTypeException + { + FileFolderServiceType type = getType(typeQName); + + switch (type) + { + case FILE: + return false; + case FOLDER: + return true; + case SYSTEM_FOLDER: + throw new InvalidTypeException("This service should ignore type " + ContentModel.TYPE_SYSTEM_FOLDER); + case INVALID: + default: + throw new InvalidTypeException("Type is not handled by this service: " + typeQName); + } + } + + public boolean exists(NodeRef nodeRef) + { + return nodeService.exists(nodeRef); + } + + public FileFolderServiceType getType(QName typeQName) + { + if (dictionaryService.isSubClass(typeQName, ContentModel.TYPE_FOLDER)) + { + if (dictionaryService.isSubClass(typeQName, ContentModel.TYPE_SYSTEM_FOLDER)) + { + return FileFolderServiceType.SYSTEM_FOLDER; + } + return FileFolderServiceType.FOLDER; + } + else if (dictionaryService.isSubClass(typeQName, ContentModel.TYPE_CONTENT) || + dictionaryService.isSubClass(typeQName, ContentModel.TYPE_LINK)) + { + // it is a regular file + return FileFolderServiceType.FILE; + } + else + { + // unhandled type + return FileFolderServiceType.INVALID; + } + } + + public List list(NodeRef contextNodeRef) + { + // execute the query + List results = listSimple(contextNodeRef, true, true); + // done + if (logger.isTraceEnabled()) + { + logger.trace("List files and folders: \n" + + " context: " + contextNodeRef + "\n" + + " results: " + results); + } + return results; + } + + private PagingResults getPagingResults(PagingRequest pagingRequest, final CannedQueryResults results) + { + List nodeRefs = null; + if (results.getPageCount() > 0) + { + nodeRefs = results.getPages().get(0); + } + else + { + nodeRefs = Collections.emptyList(); + } + + // set total count + final Pair totalCount; + if (pagingRequest.getRequestTotalCountMax() > 0) + { + totalCount = results.getTotalResultCount(); + } + else + { + totalCount = null; + } + + final List nodeInfos = new ArrayList(nodeRefs.size()); + for (NodeRef nodeRef : nodeRefs) + { + nodeInfos.add(toFileInfo(nodeRef, true)); + } + PermissionCheckedCollectionMixin.create(nodeInfos, nodeRefs); + + return new PagingResults() + { + @Override + public String getQueryExecutionId() + { + return results.getQueryExecutionId(); + } + @Override + public List getPage() + { + return nodeInfos; + } + @Override + public boolean hasMoreItems() + { + return results.hasMoreItems(); + } + @Override + public Pair getTotalResultCount() + { + return totalCount; + } + }; + } + + /* (non-Javadoc) + * @see org.alfresco.service.cmr.model.FileFolderService#list(org.alfresco.service.cmr.repository.NodeRef, boolean, boolean, java.util.Set, org.alfresco.service.cmr.model.PagingSortRequest) + */ + @Auditable(parameters = {"contextNodeRef", "files", "folders", "ignoreQNames", "sortProps", "pagingRequest"}) + public PagingResults list(NodeRef contextNodeRef, + boolean files, + boolean folders, + Set ignoreQNames, + List> sortProps, + PagingRequest pagingRequest) + { + ParameterCheck.mandatory("contextNodeRef", contextNodeRef); + ParameterCheck.mandatory("pagingRequest", pagingRequest); + + Pair,Set> pair = buildSearchTypesAndIgnoreAspects(files, folders, ignoreQNames); + Set searchTypeQNames = pair.getFirst(); + Set ignoreAspectQNames = pair.getSecond(); + + // execute query + final CannedQueryResults results = listImpl(contextNodeRef, null, searchTypeQNames, ignoreAspectQNames, sortProps, pagingRequest); + return getPagingResults(pagingRequest, results); + } + @Override - public void setHidden(NodeRef nodeRef, boolean isHidden) - { - int mask = 0; - boolean allVisible = true; - Visibility webDavVisibility = isHidden ? Visibility.NotVisible : Visibility.Visible; - for (Client client : hiddenAspect.getClients()) - { - Visibility clientVisibility = client == FileFilterMode.getClient() ? webDavVisibility : hiddenAspect - .getVisibility(client, nodeRef); - if (clientVisibility != Visibility.Visible) - { - allVisible = false; - } - mask |= hiddenAspect.getClientVisibilityMask(client, clientVisibility); - } - if (allVisible) - { - nodeService.removeAspect(nodeRef, ContentModel.ASPECT_HIDDEN); - } - else - { - hiddenAspect.hideNode(nodeRef, mask, true, true, false); - } - } - - @Override - public boolean isHidden(NodeRef nodeRef) - { - return hiddenAspect.getVisibility(FileFilterMode.getClient(), nodeRef) != Visibility.Visible; - } - -} + public PagingResults list(NodeRef contextNodeRef, boolean files, boolean folders, String pattern, Set ignoreQNames, List> sortProps, PagingRequest pagingRequest) + { + ParameterCheck.mandatory("contextNodeRef", contextNodeRef); + ParameterCheck.mandatory("pagingRequest", pagingRequest); + + Pair,Set> pair = buildSearchTypesAndIgnoreAspects(files, folders, ignoreQNames); + Set searchTypeQNames = pair.getFirst(); + Set ignoreAspectQNames = pair.getSecond(); + + // execute query + final CannedQueryResults results = listImpl(contextNodeRef, pattern, searchTypeQNames, ignoreAspectQNames, sortProps, pagingRequest); + return getPagingResults(pagingRequest, results); + } + + + public PagingResults list(NodeRef rootNodeRef, Set searchTypeQNames, Set ignoreAspectQNames, List> sortProps, PagingRequest pagingRequest) + { + CannedQueryResults results = listImpl(rootNodeRef, null, searchTypeQNames, ignoreAspectQNames, sortProps, pagingRequest); + return getPagingResults(pagingRequest, results); + } + + private CannedQueryResults listImpl(NodeRef contextNodeRef, boolean files, boolean folders) + { + Set searchTypeQNames = buildSearchTypesAndIgnoreAspects(files, folders, null).getFirst(); + return listImpl(contextNodeRef, searchTypeQNames); + } + + private CannedQueryResults listImpl(NodeRef contextNodeRef, Set searchTypeQNames) + { + return listImpl(contextNodeRef, null, searchTypeQNames, null, null, new PagingRequest(defaultListMaxResults, null)); + } + + // note: similar to getChildAssocs(contextNodeRef, searchTypeQNames) but enables paging features, including max items, sorting etc (with permissions per-applied) + + /** + * + * @param contextNodeRef + * @param pattern + * @param searchTypeQNames + * @param ignoreAspectQNames + * @param sortProps + * @param pagingRequest + * @return + */ + private CannedQueryResults listImpl(NodeRef contextNodeRef, String pattern, Set searchTypeQNames, Set ignoreAspectQNames, List> sortProps, PagingRequest pagingRequest) + { + Long start = (logger.isDebugEnabled() ? System.currentTimeMillis() : null); + + // get canned query + GetChildrenCannedQueryFactory getChildrenCannedQueryFactory = (GetChildrenCannedQueryFactory)cannedQueryRegistry.getNamedObject(CANNED_QUERY_FILEFOLDER_LIST); + + GetChildrenCannedQuery cq = (GetChildrenCannedQuery)getChildrenCannedQueryFactory.getCannedQuery(contextNodeRef, pattern, Collections.singleton(ContentModel.ASSOC_CONTAINS), searchTypeQNames, ignoreAspectQNames, null, sortProps, pagingRequest); + + // execute canned query + CannedQueryResults results = cq.execute(); + + if (start != null) + { + int cnt = results.getPagedResultCount(); + int skipCount = pagingRequest.getSkipCount(); + int maxItems = pagingRequest.getMaxItems(); + boolean hasMoreItems = results.hasMoreItems(); + Pair totalCount = (pagingRequest.getRequestTotalCountMax() > 0 ? results.getTotalResultCount() : null); + int pageNum = (skipCount / maxItems) + 1; + + logger.debug("List: "+cnt+" items in "+(System.currentTimeMillis()-start)+" msecs [pageNum="+pageNum+",skip="+skipCount+",max="+maxItems+",hasMorePages="+hasMoreItems+",totalCount="+totalCount+",parentNodeRef="+contextNodeRef+"]"); + } + + return results; + } + + public List listFiles(NodeRef contextNodeRef) + { + // execute the query + List results = listSimple(contextNodeRef, true, false); + // done + if (logger.isTraceEnabled()) + { + logger.trace("List files: \n" + + " context: " + contextNodeRef + "\n" + + " results: " + results); + } + return results; + } + + public List listFolders(NodeRef contextNodeRef) + { + // execute the query + List results = listSimple(contextNodeRef, false, true); + // done + if (logger.isTraceEnabled()) + { + logger.trace("List for folders: \n" + + " context: " + contextNodeRef + "\n" + + " results: " + results); + } + return results; + } + + public List listDeepFolders(NodeRef contextNodeRef, + SubFolderFilter filter) + { + List nodeRefs = listSimpleDeep(contextNodeRef, false, true, filter); + + List results = toFileInfo(nodeRefs); + + // done + if (logger.isTraceEnabled()) + { + logger.trace("Deep search for files: \n" + + " context: " + contextNodeRef + "\n" + + " results: " + results.size()); + } + return results; + + } + + @Override + public NodeRef getLocalizedSibling(NodeRef nodeRef) + { + Locale userLocale = I18NUtil.getLocale(); + + String name = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef(); + // Work out the base name we are working with + Pair split = getExtension(name, false); + String base = split.getFirst(); + String ext = split.getSecond(); + + NodeRef resultNodeRef = nodeRef; + // Search for siblings with the same name + Control resourceHelper = Control.getControl(Control.FORMAT_DEFAULT); + List candidateLocales = resourceHelper.getCandidateLocales(base, userLocale); + for (Locale candidateLocale : candidateLocales) + { + String filename = resourceHelper.toBundleName(base, candidateLocale) + "." + ext; + // Attempt to find the file + NodeRef foundNodeRef = searchSimple(parentNodeRef, filename); + if (foundNodeRef != null) // TODO: Check for read permissions + { + resultNodeRef = foundNodeRef; + break; + } + } + // Done + return resultNodeRef; + } + + public NodeRef searchSimple(NodeRef contextNodeRef, String name) + { + ParameterCheck.mandatory("name", name); + ParameterCheck.mandatory("contextNodeRef", contextNodeRef); + + NodeRef childNodeRef = nodeService.getChildByName(contextNodeRef, ContentModel.ASSOC_CONTAINS, name); + if (logger.isTraceEnabled()) + { + logger.trace( + "Simple name search results: \n" + + " parent: " + contextNodeRef + "\n" + + " name: " + name + "\n" + + " result: " + childNodeRef); + } + return childNodeRef; + } + + /** + * @see #search(NodeRef, String, boolean, boolean, boolean) + */ + public List search(NodeRef contextNodeRef, String namePattern, boolean includeSubFolders) + { + return search(contextNodeRef, namePattern, true, true, includeSubFolders); + } + + private static final String LUCENE_MULTI_CHAR_WILDCARD = "*"; + + /** + * Full search with all options + */ + public List search( + NodeRef contextNodeRef, + String namePattern, + boolean fileSearch, + boolean folderSearch, + boolean includeSubFolders) + { + // get the raw nodeRefs + List nodeRefs = searchInternal(contextNodeRef, namePattern, fileSearch, folderSearch, includeSubFolders); + + List results = toFileInfo(nodeRefs); + + // eliminate unwanted files/folders + Iterator iterator = results.iterator(); + while (iterator.hasNext()) + { + FileInfo file = iterator.next(); + if (file.isFolder() && !folderSearch) + { + iterator.remove(); + } + else if (!file.isFolder() && !fileSearch) + { + iterator.remove(); + } + } + // done + if (logger.isTraceEnabled()) + { + logger.trace("Deep search: \n" + + " context: " + contextNodeRef + "\n" + + " pattern: " + namePattern + "\n" + + " files: " + fileSearch + "\n" + + " folders: " + folderSearch + "\n" + + " deep: " + includeSubFolders + "\n" + + " results: " + results); + } + return results; + } + + /** + * Performs a full search, but doesn't translate the node references into + * file info objects. This allows {@link #checkExists(NodeRef, String)} to + * bypass the retrieval of node properties. + */ + private List searchInternal( + NodeRef contextNodeRef, + String namePattern, + boolean fileSearch, + boolean folderSearch, + boolean includeSubFolders) + { + // shortcut if the search is requesting nothing + if (!fileSearch && !folderSearch) + { + return Collections.emptyList(); + } + + if (namePattern == null) + { + namePattern = LUCENE_MULTI_CHAR_WILDCARD; // default to wildcard + } + // now check if we can use Lucene to handle this query + boolean anyName = namePattern.equals(LUCENE_MULTI_CHAR_WILDCARD); + + List nodeRefs = null; + if (anyName) + { + // This is search for any name + if(includeSubFolders) + { + nodeRefs = listSimpleDeep(contextNodeRef, fileSearch, folderSearch, null); + } + else + { + nodeRefs = listImpl(contextNodeRef, fileSearch, folderSearch).getPage(); + } + } + else + { + // TODO - we need to get rid of this xpath stuff + // if the name pattern is null, then we use the ANY pattern + QueryParameterDefinition[] params = null; + if (namePattern != null) + { + // the interface specifies the Lucene syntax, so perform a conversion + namePattern = SearchLanguageConversion.convert( + SearchLanguageConversion.DEF_LUCENE, + SearchLanguageConversion.DEF_XPATH_LIKE, + namePattern); + + params = new QueryParameterDefinition[1]; + params[0] = new QueryParameterDefImpl( + ContentModel.PROP_NAME, + dictionaryService.getDataType(DataTypeDefinition.TEXT), + true, + namePattern); + } + // determine the correct query to use + String query = null; + if (includeSubFolders) + { + // this is a deep search + if(!fileSearch && folderSearch) + { + // This is a folder search only; + query = XPATH_QUERY_DEEP_FOLDERS; + } + else if(fileSearch && !folderSearch) + { + // This is a folder search only; + query = XPATH_QUERY_DEEP_FILES; + } + else + { + query = XPATH_QUERY_DEEP_ALL; + } + } + else + { + // this is a shallow search + query = XPATH_QUERY_SHALLOW_ALL; + } + // execute the query + nodeRefs = searchService.selectNodes( + contextNodeRef, + query, + params, + namespaceService, + false); + } + // done + return nodeRefs; + } + + private List listSimple(NodeRef contextNodeRef, boolean files, boolean folders) throws InvalidTypeException + { + CannedQueryResults cq = listImpl(contextNodeRef, files, folders); + List nodeRefs = cq.getPage(); + + List results = toFileInfo(nodeRefs); + + // avoid re-applying permissions (for "list" canned queries) + return PermissionCheckedValueMixin.create(results); + } + + private Pair, Set> buildSearchTypesAndIgnoreAspects(boolean files, boolean folders, Set ignoreQNameTypes) + { + Set searchTypeQNames = new HashSet(100); + Set ignoreAspectQNames = null; + + // Build a list of file and folder types + if (folders) + { + searchTypeQNames.addAll(buildFolderTypes()); + } + if (files) + { + searchTypeQNames.addAll(buildFileTypes()); + } + + if (ignoreQNameTypes != null) + { + Set ignoreQNamesNotSearchTypes = new HashSet(ignoreQNameTypes); + ignoreQNamesNotSearchTypes.removeAll(searchTypeQNames); + ignoreQNamesNotSearchTypes.remove(ContentModel.TYPE_SYSTEM_FOLDER); // note: not included in buildFolderTypes() + + if (ignoreQNamesNotSearchTypes.size() > 0) + { + ignoreAspectQNames = getAspectsToIgnore(ignoreQNamesNotSearchTypes); + } + + searchTypeQNames.removeAll(ignoreQNameTypes); + } + + return new Pair, Set>(searchTypeQNames, ignoreAspectQNames); + } + + private Set getAspectsToIgnore(Set ignoreQNames) + { + Set ignoreQNameAspects = new HashSet(ignoreQNames.size()); + for (QName qname : ignoreQNames) + { + if (dictionaryService.getAspect(qname) != null) + { + ignoreQNameAspects.add(qname); + } + } + return ignoreQNameAspects; + } + + private Set buildFolderTypes() + { + Set folderTypeQNames = new HashSet(50); + + // Build a list of folder types + Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_FOLDER, true); + folderTypeQNames.addAll(qnames); + folderTypeQNames.add(ContentModel.TYPE_FOLDER); + + // Remove 'system' folders + qnames = dictionaryService.getSubTypes(ContentModel.TYPE_SYSTEM_FOLDER, true); + folderTypeQNames.removeAll(qnames); + folderTypeQNames.remove(ContentModel.TYPE_SYSTEM_FOLDER); + + return folderTypeQNames; + } + + private Set buildFileTypes() + { + Set fileTypeQNames = new HashSet(50); + + // Build a list of file types + Collection qnames = dictionaryService.getSubTypes(ContentModel.TYPE_CONTENT, true); + fileTypeQNames.addAll(qnames); + fileTypeQNames.add(ContentModel.TYPE_CONTENT); + qnames = dictionaryService.getSubTypes(ContentModel.TYPE_LINK, true); + fileTypeQNames.addAll(qnames); + fileTypeQNames.add(ContentModel.TYPE_LINK); + + return fileTypeQNames; + } + + /** + * A deep version of listSimple. Which recursively walks down the tree from a given starting point, returning + * the node refs of files or folders found along the way. + *

+ * The folder filter is called for each sub-folder to determine whether to search in that sub-folder, should a subfolder be excluded + * then all its chidren are excluded as well. + * + * @param contextNodeRef the starting point. + * @param folders return nodes of type folders. + * @param files return nodes of type files. + * @param subfolder filter controls which folders to search. If null then all subfolders are searched. + * @return list of node references + */ + /*

+ * MER: I've added this rather than changing listSimple to minimise the risk of breaking + * the existing code. This is a quick performance improvement between using + * XPath which is awful or adding new methods to the NodeService/DB This is also a dangerous method in that it can return a + * lot of data and take a long time. + */ + private List listSimpleDeep(NodeRef contextNodeRef, boolean files, boolean folders, SubFolderFilter folderFilter) + { + if(logger.isDebugEnabled()) + { + logger.debug("searchSimpleDeep contextNodeRef:" + contextNodeRef); + } + + // To hold the results. + List result = new ArrayList(); + + // Build a list of folder types + Set folderTypeQNames = buildFolderTypes(); + Set fileTypeQNames = (files ? buildFileTypes() : new HashSet(0)); + + if(!folders && !files) + { + return Collections.emptyList(); + + } + + // Shortcut + if (folderTypeQNames.size() == 0) + { + return Collections.emptyList(); + } + + Stack toSearch = new Stack(); + toSearch.push(contextNodeRef); + + // Now we need to walk down the folders. + while(!toSearch.empty()) + { + NodeRef currentDir = toSearch.pop(); + + List folderAssocRefs = nodeService.getChildAssocs(currentDir, folderTypeQNames); + + for (ChildAssociationRef folderRef : folderAssocRefs) + { + // We have some child folders + boolean include = true; + if(folderFilter != null) + { + include = folderFilter.isEnterSubfolder(folderRef); + if(include) + { + // yes search in these subfolders + toSearch.push(folderRef.getChildRef()); + } + } + else + { + // No filter - Add the folders in the currentDir + toSearch.push(folderRef.getChildRef()); + } + + if(folders && include) + { + result.add(folderRef.getChildRef()); + } + } + + if(files) + { + // Add the files in the current dir + List fileAssocRefs = nodeService.getChildAssocs(currentDir, fileTypeQNames); + for (ChildAssociationRef fileRef : fileAssocRefs) + { + result.add(fileRef.getChildRef()); + } + } + } + + + if(logger.isDebugEnabled()) + { + logger.debug("searchSimpleDeep finished size:" + result.size()); + } + + // Done + return result; + } + + /** + * @see #move(NodeRef, NodeRef, String) + */ + public FileInfo rename(NodeRef sourceNodeRef, String newName) throws FileExistsException, FileNotFoundException + { + return moveOrCopy(sourceNodeRef, null, null, newName, true); + } + + /** + * @see #moveOrCopy(NodeRef, NodeRef, String, boolean) + */ + @Override + public FileInfo move(NodeRef sourceNodeRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException + { + return moveOrCopy(sourceNodeRef, null, targetParentRef, newName, true); + } + + /** + * @see #moveOrCopy(NodeRef, NodeRef, String, boolean) + */ + @Override + public FileInfo moveFrom(NodeRef sourceNodeRef, NodeRef sourceParentRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException + { + return moveOrCopy(sourceNodeRef, sourceParentRef, targetParentRef, newName, true); + } + + /** + * @deprecated + */ + @Override + public FileInfo move(NodeRef sourceNodeRef, NodeRef sourceParentRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException + { + return moveOrCopy(sourceNodeRef, sourceParentRef, targetParentRef, newName, true); + } + + /** + * @see #moveOrCopy(NodeRef, NodeRef, String, boolean) + */ + public FileInfo copy(NodeRef sourceNodeRef, NodeRef targetParentRef, String newName) throws FileExistsException, FileNotFoundException + { + return moveOrCopy(sourceNodeRef, null, targetParentRef, newName, false); + } + + /** + * Implements both move and copy behaviour + * + * @param move true to move, otherwise false to copy + */ + private FileInfo moveOrCopy(NodeRef sourceNodeRef, NodeRef sourceParentRef, NodeRef targetParentRef, String newName, boolean move) throws FileExistsException, FileNotFoundException + { + // get file/folder in its current state + FileInfo beforeFileInfo = toFileInfo(sourceNodeRef, true); + // check the name - null means keep the existing name + if (newName == null) + { + newName = beforeFileInfo.getName(); + } + + boolean nameChanged = (newName.equals(beforeFileInfo.getName()) == false); + + AssociationCopyInfo targetInfo = getAssociationCopyInfo(nodeService, sourceNodeRef, sourceParentRef, newName, nameChanged); + QName qname = targetInfo.getTargetAssocQName(); + boolean isPrimaryParent = targetInfo.getSourceParentAssoc().isPrimary(); + ChildAssociationRef assocRef = targetInfo.getSourceParentAssoc(); + + if (targetParentRef == null) + { + targetParentRef = assocRef.getParentRef(); + } + + boolean changedParent = !targetParentRef.equals(assocRef.getParentRef()); + // there is nothing to do if both the name and parent folder haven't changed + if (!nameChanged && !changedParent) + { + if (logger.isDebugEnabled()) + { + logger.debug("Doing nothing - neither filename or parent has changed: \n" + + " parent: " + targetParentRef + "\n" + + " before: " + beforeFileInfo + "\n" + + " new name: " + newName); + } + return beforeFileInfo; + } + + QName targetParentType = nodeService.getType(targetParentRef); + + // Fix AWC-1517 & ALF-5569 + QName assocTypeQname = null; + if (nameChanged && move) + { + // if it's a rename use the existing assoc type + assocTypeQname = assocRef.getTypeQName(); + } + else + { + if (dictionaryService.isSubClass(targetParentType, ContentModel.TYPE_FOLDER)) + { + assocTypeQname = ContentModel.ASSOC_CONTAINS; // cm:folder -> cm:contains + } + else if (dictionaryService.isSubClass(targetParentType, ContentModel.TYPE_CONTAINER)) + { + assocTypeQname = ContentModel.ASSOC_CHILDREN; // sys:container -> sys:children + } + else + { + throw new InvalidTypeException("Unexpected type (" + targetParentType + ") for target parent: " + targetParentRef); + } + } + + // move or copy + NodeRef targetNodeRef = null; + if (move) + { + // TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) + if (!isSystemPath(sourceNodeRef)) + { + // The cm:name might clash with another node in the target location. + if (nameChanged) + { + // The name will be changing, so we really need to set the node's name to the new + // name. This can't be done at the same time as the move - to avoid incorrect violations + // of the name constraints, the cm:name is set to something random and will be reset + // to the correct name later. + nodeService.setProperty(sourceNodeRef, ContentModel.PROP_NAME, GUID.generate()); + } + try + { + ChildAssociationRef newAssocRef = null; + + if (isPrimaryParent) + { + // move the node so that the association moves as well + boolean auditableBehaviorWasDisabled = preserveModificationData && behaviourFilter.isEnabled(ContentModel.ASPECT_AUDITABLE); + if (auditableBehaviorWasDisabled) + { + behaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE); + } + + try + { + newAssocRef = nodeService.moveNode(sourceNodeRef, targetParentRef, assocTypeQname, qname); + } + finally + { + if (auditableBehaviorWasDisabled) + { + behaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE); + } + } + } + else + { + nodeService.removeChild(sourceParentRef, sourceNodeRef); + newAssocRef = nodeService.addChild(targetParentRef, sourceNodeRef, assocRef.getTypeQName(), assocRef.getQName()); + } + + targetNodeRef = newAssocRef.getChildRef(); + } + catch (DuplicateChildNodeNameException e) + { + throw new FileExistsException(targetParentRef, newName); + } + } + else + { + // system path folders do not need to be moved + targetNodeRef = sourceNodeRef; + } + } + else + { + // Check if during copy top level name will be changed to some new + String newNameAfterCopy = copyService.getTopLevelNodeNewName(sourceNodeRef, targetParentRef, assocTypeQname, qname); + if (newNameAfterCopy != null && !newNameAfterCopy.equals(newName)) + { + newName = newNameAfterCopy; + qname = QName.createQName( + assocRef.getQName().getNamespaceURI(), + QName.createValidLocalName(newNameAfterCopy)); + } + + try + { + // Copy the node. The cm:name will be dropped and reset later. + targetNodeRef = copyService.copy( + sourceNodeRef, + targetParentRef, + assocTypeQname, + qname, + true); + } + catch (DuplicateChildNodeNameException e) + { + throw new FileExistsException(targetParentRef, newName); + } + } + + // Only update the name if it has changed + String currentName = (String)nodeService.getProperty(targetNodeRef, ContentModel.PROP_NAME); + + // ALF-13949: WorkingCopyAspect intentionally generates new names for all copies of working copies (which no + // longer have the working copy aspect) so leave these alone after copy + if (!currentName.equals(newName) && (move || !nodeService.hasAspect(sourceNodeRef, ContentModel.ASPECT_WORKING_COPY))) + { + try + { + // changed the name property + nodeService.setProperty(targetNodeRef, ContentModel.PROP_NAME, newName); + + // May need to update the mimetype, to support apps using .tmp files when saving + ContentData contentData = (ContentData)nodeService.getProperty(targetNodeRef, ContentModel.PROP_CONTENT); + + // Check the newName and oldName extensions. + // Keep previous mimetype if + // 1. new extension is empty + // 2. new extension is '.tmp' + // 3. extension was not changed, + // + // It fixes the ETWOTWO-16 issue. + String oldExt = getExtension(beforeFileInfo.getName(), true).getSecond(); + String newExt = getExtension(newName, true).getSecond(); + if (contentData != null && + newExt.length() != 0 && + !"tmp".equalsIgnoreCase(newExt) && + !newExt.equalsIgnoreCase(oldExt)) + { + String targetMimetype = contentData.getMimetype(); + final ContentReader reader = contentService.getReader(targetNodeRef, ContentModel.PROP_CONTENT); + String newMimetype = mimetypeService.guessMimetype(newName, reader); + if (!targetMimetype.equalsIgnoreCase(newMimetype)) + { + contentData = ContentData.setMimetype(contentData, newMimetype); + nodeService.setProperty(targetNodeRef, ContentModel.PROP_CONTENT, contentData); + } + } + } + catch (DuplicateChildNodeNameException e) + { + throw new FileExistsException(targetParentRef, newName); + } + } + + // get the details after the operation + FileInfo afterFileInfo = toFileInfo(targetNodeRef, true); + // done + if (logger.isDebugEnabled()) + { + logger.debug("" + (move ? "Moved" : "Copied") + " node: \n" + + " parent: " + targetParentRef + "\n" + + " before: " + beforeFileInfo + "\n" + + " after: " + afterFileInfo); + } + return afterFileInfo; + } + + /** + * Determine if the specified node is a special "system" folder path based node + * + * TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID) + * + * @param nodeRef node to check + * @return true => system folder path based node + */ + private boolean isSystemPath(NodeRef nodeRef) + { + Path path = nodeService.getPath(nodeRef); + String prefixedPath = path.toPrefixString(namespaceService); + return systemPaths.contains(prefixedPath); + } + + public FileInfo create(NodeRef parentNodeRef, String name, QName typeQName) throws FileExistsException + { + return createImpl(parentNodeRef, name, typeQName, null); + } + + public FileInfo create(NodeRef parentNodeRef, String name, QName typeQName, QName assocQName) throws FileExistsException + { + return createImpl(parentNodeRef, name, typeQName, assocQName); + } + + private FileInfo createImpl(NodeRef parentNodeRef, String name, QName typeQName, QName assocQName) throws FileExistsException + { + // set up initial properties + Map properties = new HashMap(11); + properties.put(ContentModel.PROP_NAME, (Serializable) name); + + // create the node + if (assocQName == null) + { + assocQName = QName.createQName( + NamespaceService.CONTENT_MODEL_1_0_URI, + QName.createValidLocalName(name)); + } + ChildAssociationRef assocRef = null; + try + { + assocRef = nodeService.createNode( + parentNodeRef, + ContentModel.ASSOC_CONTAINS, + assocQName, + typeQName, + properties); + } + catch (DuplicateChildNodeNameException e) + { + throw new FileExistsException(parentNodeRef, name); + } + + NodeRef nodeRef = assocRef.getChildRef(); + + FileInfo fileInfo = toFileInfo(nodeRef, true); + // done + if (logger.isDebugEnabled()) + { + logger.debug("Created: \n" + + " parent: " + parentNodeRef + "\n" + + " created: " + fileInfo); + } + return fileInfo; + } + + public void delete(NodeRef nodeRef) + { + nodeService.deleteNode(nodeRef); + // Done + if (logger.isDebugEnabled()) + { + logger.debug("Deleted: \n" + + " node: " + nodeRef); + } + } + + /** + * Checks for the presence of, and creates as necessary, the folder structure in the provided path. + *

+ * An empty path list is not allowed as it would be impossible to necessarily return file info + * for the parent node - it might not be a folder node. + * @param parentNodeRef the node under which the path will be created + * @param pathElements the folder name path to create - may not be empty + * @param folderTypeQName the types of nodes to create. This must be a valid subtype of + * {@link org.alfresco.model.ContentModel#TYPE_FOLDER they folder type}. + * @return Returns the info of the last folder in the path. + * @deprecated Use FileFolderUtil.makeFolders rather than directly accessing this implementation class. + */ + public FileInfo makeFolders(NodeRef parentNodeRef, List pathElements, QName folderTypeQName) + { + return FileFolderUtil.makeFolders(this, parentNodeRef, pathElements, folderTypeQName); + } + + /** + * Checks for the presence of, and creates as necessary, the folder structure in the provided path. + *

+ * An empty path list is not allowed as it would be impossible to necessarily return file info + * for the parent node - it might not be a folder node. + * @param parentNodeRef the node under which the path will be created + * @param pathElements the folder name path to create - may not be empty + * @param folderTypeQName the types of nodes to create. This must be a valid subtype of + * {@link org.alfresco.model.ContentModel#TYPE_FOLDER they folder type}. + * @return Returns the info of the last folder in the path. + * @deprecated Use FileFolderUtil.makeFolders rather than directly accessing this implementation class. + */ + public static FileInfo makeFolders(FileFolderService service, NodeRef parentNodeRef, List pathElements, QName folderTypeQName) + { + return FileFolderUtil.makeFolders(service, parentNodeRef, pathElements, folderTypeQName); + } + + /** + * Get the file or folder information from the root down to and including the node provided. + *

    + *
  • The root node can be of any type and is not included in the path list.
  • + *
  • Only the primary path is considered. If the target node is not a descendant of the + * root along purely primary associations, then an exception is generated.
  • + *
  • If an invalid type is encountered along the path, then an exception is generated.
  • + *
+ * + * @param rootNodeRef the start of the returned path, or null if the store root + * node must be assumed. + * @param nodeRef a reference to the file or folder + * @return Returns a list of file/folder infos from the root (excluded) down to and + * including the destination file or folder + * @throws FileNotFoundException if the node could not be found + */ + public List getNamePath(NodeRef rootNodeRef, NodeRef nodeRef) throws FileNotFoundException + { + // check the root + if (rootNodeRef == null) + { + rootNodeRef = nodeService.getRootNode(nodeRef.getStoreRef()); + } + try + { + ArrayList results = new ArrayList(10); + // get the primary path + Path path = nodeService.getPath(nodeRef); + // iterate and turn the results into file info objects + boolean foundRoot = false; + for (Path.Element element : path) + { + // ignore everything down to the root + Path.ChildAssocElement assocElement = (Path.ChildAssocElement) element; + final NodeRef childNodeRef = assocElement.getRef().getChildRef(); + if (childNodeRef.equals(rootNodeRef)) + { + // just found the root - but we don't put in an entry for it + foundRoot = true; + continue; + } + else if (!foundRoot) + { + // keep looking for the root + continue; + } + // we found the root and expect to be building the path up + // Run as system as the user could not have access to all folders in the path, see ALF-13816 + FileInfo pathInfo = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public FileInfo doWork() throws Exception + { + return toFileInfo(childNodeRef, true); + } + }, AuthenticationUtil.getSystemUserName()); + + // we can't append a path element to the results if there is already a (non-folder) file at the tail + // since this would result in a path anomoly - file's cannot contain other files. + if (!results.isEmpty() && !results.get(results.size()-1).isFolder()) + { + throw new InvalidTypeException( + "File is not the last element in path: files cannot contain other files."); + } + results.add(pathInfo); + } + // check that we found the root + if (!foundRoot) + { + throw new FileNotFoundException(nodeRef); + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Built name path for node: \n" + + " root: " + rootNodeRef + "\n" + + " node: " + nodeRef + "\n" + + " path: " + results); + } + return results; + } + catch (InvalidNodeRefException e) + { + throw new FileNotFoundException(nodeRef); + } + } + + /** + * Get the file or folder names from the root down to and including the node provided. + *
    + *
  • The root node can be of any type and is not included in the path list.
  • + *
  • Only the primary path is considered. If the target node is not a descendant of the + * root along purely primary associations, then an exception is generated.
  • + *
  • If an invalid type is encountered along the path, then an exception is generated.
  • + *
+ * + * @param rootNodeRef the start of the returned path, or null if the store root + * node must be assumed. + * @param nodeRef a reference to the file or folder + * @return Returns a list of file/folder names from the root (excluded) down to and + * including the destination file or folder + * @throws FileNotFoundException if the node could not be found + */ + public List getNameOnlyPath(NodeRef rootNodeRef, final NodeRef nodeRef) throws FileNotFoundException + { + // check the root + if (rootNodeRef == null) + { + rootNodeRef = nodeService.getRootNode(nodeRef.getStoreRef()); + } + try + { + final NodeRef rNodeRef = rootNodeRef; + final ArrayList results = new ArrayList(10); + // Run as system as the user could not have access to all folders in the path, see ALF-13816 + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Void doWork() throws Exception + { + // get the primary path + Path path = nodeService.getPath(nodeRef); + // iterate and turn the results into file info objects + boolean foundRoot = false; + for (Path.Element element : path) + { + // ignore everything down to the root + Path.ChildAssocElement assocElement = (Path.ChildAssocElement) element; + final NodeRef childNodeRef = assocElement.getRef().getChildRef(); + if (childNodeRef.equals(rNodeRef)) + { + // just found the root - but we don't put in an entry for it + foundRoot = true; + continue; + } + else if (!foundRoot) + { + // keep looking for the root + continue; + } + results.add(nodeService.getProperty(childNodeRef, ContentModel.PROP_NAME).toString()); + } + // check that we found the root + if (!foundRoot) + { + throw new FileNotFoundException(nodeRef); + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Built name path for node: \n" + + " root: " + rNodeRef + "\n" + + " node: " + nodeRef + "\n" + + " path: " + results); + } + return null; + } + }, AuthenticationUtil.getSystemUserName()); + + return results; + } + catch (InvalidNodeRefException e) + { + throw new FileNotFoundException(nodeRef); + } + catch (RuntimeException e) + { + // the runAs() is too keen on wrapping everything in an outer RuntimeException - which we don't want. + if (e.getCause() instanceof FileNotFoundException) + { + throw (FileNotFoundException)e.getCause(); + } + else throw e; + } + } + + public FileInfo resolveNamePath(NodeRef rootNodeRef, List pathElements) throws FileNotFoundException + { + return resolveNamePath(rootNodeRef, pathElements, true); + } + + public FileInfo resolveNamePath(NodeRef rootNodeRef, List pathElements, boolean mustExist) throws FileNotFoundException + { + if (pathElements.size() == 0) + { + throw new IllegalArgumentException("Path elements list is empty"); + } + // walk the folder tree first + NodeRef parentNodeRef = rootNodeRef; + StringBuilder currentPath = new StringBuilder(pathElements.size() << 4); + int folderCount = pathElements.size() - 1; + for (int i = 0; i < folderCount; i++) + { + String pathElement = pathElements.get(i); + NodeRef folderNodeRef = searchSimple(parentNodeRef, pathElement); + if (folderNodeRef == null) + { + if (mustExist) + { + throw new FileNotFoundException("Folder not found: " + currentPath); + } + else + { + return null; + } + } + parentNodeRef = folderNodeRef; + } + // we have resolved the folder path - resolve the last component + String pathElement = pathElements.get(pathElements.size() - 1); + NodeRef fileNodeRef = searchSimple(parentNodeRef, pathElement); + if (fileNodeRef == null) + { + if (mustExist) + { + throw new FileNotFoundException("File not found: " + currentPath); + } + else + { + return null; + } + } + FileInfo result = getFileInfo(fileNodeRef); + // found it + if (logger.isDebugEnabled()) + { + logger.debug("Resoved path element: \n" + + " root: " + rootNodeRef + "\n" + + " path: " + currentPath + "\n" + + " node: " + result); + } + return result; + } + + public FileInfo getFileInfo(NodeRef nodeRef) + { + try + { + return toFileInfo(nodeRef, true); + } + catch (InvalidTypeException e) + { + return null; + } + } + + public ContentReader getReader(NodeRef nodeRef) + { + FileInfo fileInfo = toFileInfo(nodeRef, false); + if (fileInfo.isFolder()) + { + throw new InvalidTypeException("Unable to get a content reader for a folder: " + fileInfo); + } + return contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); + } + + public ContentWriter getWriter(NodeRef nodeRef) + { + FileInfo fileInfo = toFileInfo(nodeRef, false); + if (fileInfo.isFolder()) + { + throw new InvalidTypeException("Unable to get a content writer for a folder: " + fileInfo); + } + final ContentWriter writer = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, true); + // Ensure that a mimetype is set based on the filename (ALF-6560) + // This has been removed from the create code in 3.4 to prevent insert-update behaviour + // of the ContentData. + if (writer.getMimetype() == null) + { + final String name = fileInfo.getName(); + writer.guessMimetype(name); + } + // Done + return writer; + } + + /** + * Split a filename into the base (part before the '.') and the extension (part after the '.') + */ + private Pair getExtension(String name, boolean useLastDot) + { + String ext = ""; + String base = name; + if (name != null) + { + name = name.trim(); + int index = useLastDot ? name.lastIndexOf('.') : name.indexOf('.'); + if (index > -1 && (index < name.length() - 1)) + { + base = name.substring(0, index); + ext = name.substring(index + 1); + } + } + return new Pair(base, ext); + } + + @Override + public void setHidden(NodeRef nodeRef, boolean isHidden) + { + int mask = 0; + boolean allVisible = true; + Visibility webDavVisibility = isHidden ? Visibility.NotVisible : Visibility.Visible; + for (Client client : hiddenAspect.getClients()) + { + Visibility clientVisibility = client == FileFilterMode.getClient() ? webDavVisibility : hiddenAspect + .getVisibility(client, nodeRef); + if (clientVisibility != Visibility.Visible) + { + allVisible = false; + } + mask |= hiddenAspect.getClientVisibilityMask(client, clientVisibility); + } + if (allVisible) + { + nodeService.removeAspect(nodeRef, ContentModel.ASPECT_HIDDEN); + } + else + { + hiddenAspect.hideNode(nodeRef, mask, true, true, false); + } + } + + @Override + public boolean isHidden(NodeRef nodeRef) + { + return hiddenAspect.getVisibility(FileFilterMode.getClient(), nodeRef) != Visibility.Visible; + } + +} diff --git a/source/java/org/alfresco/service/cmr/model/FileFolderService.java b/source/java/org/alfresco/service/cmr/model/FileFolderService.java index 01dc8af41e..dbabaf0144 100644 --- a/source/java/org/alfresco/service/cmr/model/FileFolderService.java +++ b/source/java/org/alfresco/service/cmr/model/FileFolderService.java @@ -429,4 +429,18 @@ public interface FileFolderService */ @Deprecated public void setHidden(NodeRef nodeRef, boolean isHidden); + + /** + * Lists page of immediate child objects of the given context node + * with specification of which types to list and optional filtering (exclusion of certain child file/folder subtypes) and sorting + * @param rootNodeRef + * @param searchTypeQNames QNames of types to list + * @param ignoreAspectQNames + * @param sortProps + * @param pagingRequest + * @return list of node refs, never null + */ + @Auditable(parameters = {"rootNodeRef"}) + public PagingResults list(NodeRef rootNodeRef, Set searchTypeQNames, Set ignoreAspectQNames, List> sortProps, PagingRequest pagingRequest); + }