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
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 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;
}
}