mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
57053: Merged V4.2-BUG-FIX (4.2.1) to HEAD-BUG-FIX (Cloud/4.3) 56520: Merged HEAD-BUG-FIX to V4.2-BUG-FIX (4.2.1) 56403: Various format fixes made during investigations e.g. ALF-20221 git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@61687 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
1592 lines
61 KiB
Java
1592 lines
61 KiB
Java
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
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.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 NamedObjectRegistry<CannedQueryFactory<NodeRef>> cannedQueryRegistry;
|
|
|
|
// TODO: Replace this with a more formal means of identifying "system" folders (i.e. aspect or UUID)
|
|
private List<String> 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<CannedQueryFactory<NodeRef>> 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<String> systemPaths)
|
|
{
|
|
this.systemPaths = systemPaths;
|
|
}
|
|
|
|
public void setDefaultListMaxResults(int defaultListMaxResults)
|
|
{
|
|
this.defaultListMaxResults = defaultListMaxResults;
|
|
}
|
|
|
|
|
|
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<FileInfo> toFileInfo(List<NodeRef> nodeRefs) throws InvalidTypeException
|
|
{
|
|
List<FileInfo> results = new ArrayList<FileInfo>(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<QName, Serializable> 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<FileInfo> list(NodeRef contextNodeRef)
|
|
{
|
|
// execute the query
|
|
List<FileInfo> 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<FileInfo> getPagingResults(PagingRequest pagingRequest, final CannedQueryResults<NodeRef> results)
|
|
{
|
|
List<NodeRef> nodeRefs = null;
|
|
if (results.getPageCount() > 0)
|
|
{
|
|
nodeRefs = results.getPages().get(0);
|
|
}
|
|
else
|
|
{
|
|
nodeRefs = Collections.emptyList();
|
|
}
|
|
|
|
// set total count
|
|
final Pair<Integer, Integer> totalCount;
|
|
if (pagingRequest.getRequestTotalCountMax() > 0)
|
|
{
|
|
totalCount = results.getTotalResultCount();
|
|
}
|
|
else
|
|
{
|
|
totalCount = null;
|
|
}
|
|
|
|
final List<FileInfo> nodeInfos = new ArrayList<FileInfo>(nodeRefs.size());
|
|
for (NodeRef nodeRef : nodeRefs)
|
|
{
|
|
nodeInfos.add(toFileInfo(nodeRef, true));
|
|
}
|
|
PermissionCheckedCollectionMixin.create(nodeInfos, nodeRefs);
|
|
|
|
return new PagingResults<FileInfo>()
|
|
{
|
|
@Override
|
|
public String getQueryExecutionId()
|
|
{
|
|
return results.getQueryExecutionId();
|
|
}
|
|
@Override
|
|
public List<FileInfo> getPage()
|
|
{
|
|
return nodeInfos;
|
|
}
|
|
@Override
|
|
public boolean hasMoreItems()
|
|
{
|
|
return results.hasMoreItems();
|
|
}
|
|
@Override
|
|
public Pair<Integer, Integer> 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<FileInfo> list(NodeRef contextNodeRef,
|
|
boolean files,
|
|
boolean folders,
|
|
Set<QName> ignoreQNames,
|
|
List<Pair<QName, Boolean>> sortProps,
|
|
PagingRequest pagingRequest)
|
|
{
|
|
ParameterCheck.mandatory("contextNodeRef", contextNodeRef);
|
|
ParameterCheck.mandatory("pagingRequest", pagingRequest);
|
|
|
|
Pair<Set<QName>,Set<QName>> pair = buildSearchTypesAndIgnoreAspects(files, folders, ignoreQNames);
|
|
Set<QName> searchTypeQNames = pair.getFirst();
|
|
Set<QName> ignoreAspectQNames = pair.getSecond();
|
|
|
|
// execute query
|
|
final CannedQueryResults<NodeRef> results = listImpl(contextNodeRef, null, searchTypeQNames, ignoreAspectQNames, sortProps, pagingRequest);
|
|
return getPagingResults(pagingRequest, results);
|
|
}
|
|
|
|
@Override
|
|
public PagingResults<FileInfo> list(NodeRef contextNodeRef, boolean files, boolean folders, String pattern, Set<QName> ignoreQNames, List<Pair<QName, Boolean>> sortProps, PagingRequest pagingRequest)
|
|
{
|
|
ParameterCheck.mandatory("contextNodeRef", contextNodeRef);
|
|
ParameterCheck.mandatory("pagingRequest", pagingRequest);
|
|
|
|
Pair<Set<QName>,Set<QName>> pair = buildSearchTypesAndIgnoreAspects(files, folders, ignoreQNames);
|
|
Set<QName> searchTypeQNames = pair.getFirst();
|
|
Set<QName> ignoreAspectQNames = pair.getSecond();
|
|
|
|
// execute query
|
|
final CannedQueryResults<NodeRef> results = listImpl(contextNodeRef, pattern, searchTypeQNames, ignoreAspectQNames, sortProps, pagingRequest);
|
|
return getPagingResults(pagingRequest, results);
|
|
}
|
|
|
|
private CannedQueryResults<NodeRef> listImpl(NodeRef contextNodeRef, boolean files, boolean folders)
|
|
{
|
|
Set<QName> searchTypeQNames = buildSearchTypesAndIgnoreAspects(files, folders, null).getFirst();
|
|
return listImpl(contextNodeRef, searchTypeQNames);
|
|
}
|
|
|
|
private CannedQueryResults<NodeRef> listImpl(NodeRef contextNodeRef, Set<QName> 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<NodeRef> listImpl(NodeRef contextNodeRef, String pattern, Set<QName> searchTypeQNames, Set<QName> ignoreAspectQNames, List<Pair<QName, Boolean>> 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<NodeRef> results = cq.execute();
|
|
|
|
if (start != null)
|
|
{
|
|
int cnt = results.getPagedResultCount();
|
|
int skipCount = pagingRequest.getSkipCount();
|
|
int maxItems = pagingRequest.getMaxItems();
|
|
boolean hasMoreItems = results.hasMoreItems();
|
|
Pair<Integer, Integer> 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<FileInfo> listFiles(NodeRef contextNodeRef)
|
|
{
|
|
// execute the query
|
|
List<FileInfo> results = listSimple(contextNodeRef, true, false);
|
|
// done
|
|
if (logger.isTraceEnabled())
|
|
{
|
|
logger.trace("List files: \n" +
|
|
" context: " + contextNodeRef + "\n" +
|
|
" results: " + results);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
public List<FileInfo> listFolders(NodeRef contextNodeRef)
|
|
{
|
|
// execute the query
|
|
List<FileInfo> results = listSimple(contextNodeRef, false, true);
|
|
// done
|
|
if (logger.isTraceEnabled())
|
|
{
|
|
logger.trace("List for folders: \n" +
|
|
" context: " + contextNodeRef + "\n" +
|
|
" results: " + results);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
public List<FileInfo> listDeepFolders(NodeRef contextNodeRef,
|
|
SubFolderFilter filter)
|
|
{
|
|
List<NodeRef> nodeRefs = listSimpleDeep(contextNodeRef, false, true, filter);
|
|
|
|
List<FileInfo> 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<String, String> 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<Locale> 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<FileInfo> 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<FileInfo> search(
|
|
NodeRef contextNodeRef,
|
|
String namePattern,
|
|
boolean fileSearch,
|
|
boolean folderSearch,
|
|
boolean includeSubFolders)
|
|
{
|
|
// get the raw nodeRefs
|
|
List<NodeRef> nodeRefs = searchInternal(contextNodeRef, namePattern, fileSearch, folderSearch, includeSubFolders);
|
|
|
|
List<FileInfo> results = toFileInfo(nodeRefs);
|
|
|
|
// eliminate unwanted files/folders
|
|
Iterator<FileInfo> 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<NodeRef> 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<NodeRef> 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<FileInfo> listSimple(NodeRef contextNodeRef, boolean files, boolean folders) throws InvalidTypeException
|
|
{
|
|
CannedQueryResults<NodeRef> cq = listImpl(contextNodeRef, files, folders);
|
|
List<NodeRef> nodeRefs = cq.getPage();
|
|
|
|
List<FileInfo> results = toFileInfo(nodeRefs);
|
|
|
|
// avoid re-applying permissions (for "list" canned queries)
|
|
return PermissionCheckedValueMixin.create(results);
|
|
}
|
|
|
|
private Pair<Set<QName>, Set<QName>> buildSearchTypesAndIgnoreAspects(boolean files, boolean folders, Set<QName> ignoreQNameTypes)
|
|
{
|
|
Set<QName> searchTypeQNames = new HashSet<QName>(100);
|
|
Set<QName> ignoreAspectQNames = null;
|
|
|
|
// Build a list of file and folder types
|
|
if (folders)
|
|
{
|
|
searchTypeQNames.addAll(buildFolderTypes());
|
|
}
|
|
if (files)
|
|
{
|
|
searchTypeQNames.addAll(buildFileTypes());
|
|
}
|
|
|
|
if (ignoreQNameTypes != null)
|
|
{
|
|
Set<QName> ignoreQNamesNotSearchTypes = new HashSet<QName>(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<QName>, Set<QName>>(searchTypeQNames, ignoreAspectQNames);
|
|
}
|
|
|
|
private Set<QName> getAspectsToIgnore(Set<QName> ignoreQNames)
|
|
{
|
|
Set<QName> ignoreQNameAspects = new HashSet<QName>(ignoreQNames.size());
|
|
for (QName qname : ignoreQNames)
|
|
{
|
|
if (dictionaryService.getAspect(qname) != null)
|
|
{
|
|
ignoreQNameAspects.add(qname);
|
|
}
|
|
}
|
|
return ignoreQNameAspects;
|
|
}
|
|
|
|
private Set<QName> buildFolderTypes()
|
|
{
|
|
Set<QName> folderTypeQNames = new HashSet<QName>(50);
|
|
|
|
// Build a list of folder types
|
|
Collection<QName> 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<QName> buildFileTypes()
|
|
{
|
|
Set<QName> fileTypeQNames = new HashSet<QName>(50);
|
|
|
|
// Build a list of file types
|
|
Collection<QName> 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.
|
|
* <p>
|
|
* 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
|
|
*/
|
|
/* <p>
|
|
* 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<NodeRef> listSimpleDeep(NodeRef contextNodeRef, boolean files, boolean folders, SubFolderFilter folderFilter)
|
|
{
|
|
if(logger.isDebugEnabled())
|
|
{
|
|
logger.debug("searchSimpleDeep contextNodeRef:" + contextNodeRef);
|
|
}
|
|
|
|
// To hold the results.
|
|
List<NodeRef> result = new ArrayList<NodeRef>();
|
|
|
|
// Build a list of folder types
|
|
Set<QName> folderTypeQNames = buildFolderTypes();
|
|
Set<QName> fileTypeQNames = (files ? buildFileTypes() : new HashSet<QName>(0));
|
|
|
|
if(!folders && !files)
|
|
{
|
|
return Collections.emptyList();
|
|
|
|
}
|
|
|
|
// Shortcut
|
|
if (folderTypeQNames.size() == 0)
|
|
{
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
Stack<NodeRef> toSearch = new Stack<NodeRef>();
|
|
toSearch.push(contextNodeRef);
|
|
|
|
// Now we need to walk down the folders.
|
|
while(!toSearch.empty())
|
|
{
|
|
NodeRef currentDir = toSearch.pop();
|
|
|
|
List<ChildAssociationRef> 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<ChildAssociationRef> 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
|
|
newAssocRef = nodeService.moveNode(sourceNodeRef, targetParentRef, assocTypeQname, qname);
|
|
}
|
|
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<QName, Serializable> properties = new HashMap<QName, Serializable>(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.
|
|
* <p>
|
|
* 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<String> 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.
|
|
* <p>
|
|
* 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<String> 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.
|
|
* <ul>
|
|
* <li>The root node can be of any type and is not included in the path list.</li>
|
|
* <li>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.</li>
|
|
* <li>If an invalid type is encountered along the path, then an exception is generated.</li>
|
|
* </ul>
|
|
*
|
|
* @param rootNodeRef the start of the returned path, or null if the <b>store</b> 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<FileInfo> getNamePath(NodeRef rootNodeRef, NodeRef nodeRef) throws FileNotFoundException
|
|
{
|
|
// check the root
|
|
if (rootNodeRef == null)
|
|
{
|
|
rootNodeRef = nodeService.getRootNode(nodeRef.getStoreRef());
|
|
}
|
|
try
|
|
{
|
|
ArrayList<FileInfo> results = new ArrayList<FileInfo>(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<FileInfo>()
|
|
{
|
|
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.
|
|
* <ul>
|
|
* <li>The root node can be of any type and is not included in the path list.</li>
|
|
* <li>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.</li>
|
|
* <li>If an invalid type is encountered along the path, then an exception is generated.</li>
|
|
* </ul>
|
|
*
|
|
* @param rootNodeRef the start of the returned path, or null if the <b>store</b> 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<String> 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<String> results = new ArrayList<String>(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<Void>()
|
|
{
|
|
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<String> pathElements) throws FileNotFoundException
|
|
{
|
|
return resolveNamePath(rootNodeRef, pathElements, true);
|
|
}
|
|
|
|
public FileInfo resolveNamePath(NodeRef rootNodeRef, List<String> 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<String, String> 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<String, String>(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;
|
|
}
|
|
|
|
}
|