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 files return nodes of type files.
- * @param folders return nodes of type folders.
- * @param folderFilter 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)
- */
- @Override
- @Extend(traitAPI=FileFolderServiceTrait.class,extensionAPI=FileFolderServiceExtension.class)
- public FileInfo rename(NodeRef sourceNodeRef, String newName) throws FileExistsException, FileNotFoundException
- {
- return moveOrCopy(sourceNodeRef, null, null, newName, true);
- }
-
- /**
- * @see #moveOrCopy(NodeRef, 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, 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, NodeRef, String, boolean)
- */
- @Override
- 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 = preserveAuditableData && 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);
- }
-
- @Override
- public FileInfo create(NodeRef parentNodeRef, String name, QName typeQName) throws FileExistsException
- {
- return createImpl(parentNodeRef, name, typeQName, null);
- }
-
- @Override
- 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);
-
- // Check the type is valid for file/folder service
- FileFolderServiceType type = getType(typeQName);
-
- switch (type)
- {
- case SYSTEM_FOLDER:
- throw new InvalidTypeException("System Folders are not handled by this service :" + typeQName);
- case INVALID:
- throw new InvalidTypeException("Type is not handled by this service: " + typeQName);
- case FILE:
- case FOLDER:
- default:
- }
-
- // 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;
- }
-
- @Override
- 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
- */
- @Override
- 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
- */
- @Override
- 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;
- }
- }
-
- @Override
- public FileInfo resolveNamePath(NodeRef rootNodeRef, List pathElements) throws FileNotFoundException
- {
- return resolveNamePath(rootNodeRef, pathElements, true);
- }
-
- @Override
- 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);
- currentPath.append("/").append(pathElement);
- NodeRef folderNodeRef = searchSimple(parentNodeRef, pathElement);
- if (folderNodeRef == null)
- {
- if (mustExist)
- {
- throw new FileNotFoundException("Folder not found: " + currentPath + " (in " + rootNodeRef + ")");
- }
- else
- {
- return null;
- }
- }
- parentNodeRef = folderNodeRef;
- }
- // we have resolved the folder path - resolve the last component
- String pathElement = pathElements.get(pathElements.size() - 1);
- currentPath.append("/").append(pathElement);
- NodeRef fileNodeRef = searchSimple(parentNodeRef, pathElement);
- if (fileNodeRef == null)
- {
- if (mustExist)
- {
- throw new FileNotFoundException("File not found: " + currentPath + " (in " + rootNodeRef + ")");
- }
- 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;
- }
-
- @Override
- public FileInfo getFileInfo(NodeRef nodeRef)
- {
- try
- {
- return toFileInfo(nodeRef, true);
- }
- catch (InvalidTypeException e)
- {
- return null;
- }
- }
-
- @Override
- 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);
- }
-
- @Override
- 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)
- {
- hiddenAspect.showNode(nodeRef, true);
- }
- else
- {
- hiddenAspect.hideNode(nodeRef, mask, true, true, false);
- }
- }
-
- @Override
- public boolean isHidden(NodeRef nodeRef)
- {
- return hiddenAspect.getVisibility(FileFilterMode.getClient(), nodeRef) != Visibility.Visible;
- }
-
- @Override
- public ExtendedTrait getTrait(Class extends M> traitAPI)
- {
- return (ExtendedTrait) fileFolderTrait;
- }
-
- private FileFolderServiceTrait createFileFolderTrait()
- {
- return new FileFolderServiceTraitImpl(this);
- }
-
- public static class FileFolderServiceTraitImpl implements FileFolderServiceTrait
- {
- private FileFolderServiceImpl thisService;
-
- public FileFolderServiceTraitImpl(FileFolderServiceImpl fileFolderServiceImpl)
- {
- thisService = fileFolderServiceImpl;
- }
-
- @Override
- public Pair, Set> buildSearchTypesAndIgnoreAspects(boolean files, boolean folders,
- Set ignoreQNameTypes)
- {
- return thisService.buildSearchTypesAndIgnoreAspects(files,
- folders,
- ignoreQNameTypes);
- }
-
- @Override
- public FileInfo createFileInfo(NodeRef nodeRef, QName typeQName, boolean isFolder, boolean isHidden,
- Map properties)
- {
- return new FileInfoImpl(nodeRef,
- typeQName,
- isFolder,
- isHidden,
- properties);
- }
-
- @Override
- public FileFolderServiceType getType(QName typeQName)
- {
- return thisService.getType(typeQName);
- }
-
- @Override
- public List list(final NodeRef contextNodeRef)
- {
-
- return thisService.list(contextNodeRef);
- }
-
- @Override
- public PagingResults list(final NodeRef contextNodeRef, final boolean files, final boolean folders,
- final String pattern, final Set ignoreQNames, final List> sortProps,
- final PagingRequest pagingRequest)
- {
- return thisService.list(contextNodeRef,
- files,
- folders,
- pattern,
- ignoreQNames,
- sortProps,
- pagingRequest);
- }
-
- @Override
- public PagingResults list(final NodeRef rootNodeRef, final Set searchTypeQNames,
- final Set ignoreAspectQNames, final List> sortProps,
- final PagingRequest pagingRequest)
- {
- return thisService.list(rootNodeRef,
- searchTypeQNames,
- ignoreAspectQNames,
- sortProps,
- pagingRequest);
- }
-
- @Override
- public List search(final NodeRef contextNodeRef, final String namePattern, final boolean fileSearch,
- final boolean folderSearch, final boolean includeSubFolders)
- {
- return thisService.search(contextNodeRef,
- namePattern,
- fileSearch,
- folderSearch,
- includeSubFolders);
- }
-
- @Override
- public FileInfo rename(final NodeRef sourceNodeRef, final String newName) throws FileExistsException, FileNotFoundException
- {
-
- return thisService.rename(sourceNodeRef, newName);
-
- }
-
-
- public PagingResults list(NodeRef contextNodeRef,
- boolean files,
- boolean folders,
- Set ignoreQNames,
- List> sortProps,
- PagingRequest pagingRequest)
- {
- return thisService.list(contextNodeRef,
- files,
- folders,
- ignoreQNames,
- sortProps,
- pagingRequest);
- }
- };
-}
+ * 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 .
+ * #L%
+ */
+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.LinkedList;
+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.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.extensions.surf.util.I18NUtil;
+
+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.model.filefolder.traitextender.FileFolderServiceExtension;
+import org.alfresco.repo.model.filefolder.traitextender.FileFolderServiceTrait;
+import org.alfresco.repo.node.getchildren.FilterProp;
+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.traitextender.AJProxyTrait;
+import org.alfresco.traitextender.Extend;
+import org.alfresco.traitextender.ExtendedTrait;
+import org.alfresco.traitextender.Extensible;
+import org.alfresco.traitextender.Trait;
+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;
+
+/**
+ * Implementation of the file/folder-specific service.
+ *
+ * @author Derek Hulley
+ */
+public class FileFolderServiceImpl extends AbstractBaseCopyService implements FileFolderService, Extensible
+{
+ 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 preserveAuditableData = 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;
+
+ private final ExtendedTrait fileFolderTrait;
+
+ /**
+ * Default constructor
+ */
+ public FileFolderServiceImpl()
+ {
+ super();
+
+ fileFolderTrait = new ExtendedTrait(AJProxyTrait.create(createFileFolderTrait(), FileFolderServiceTrait.class));
+ }
+
+ 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 setPreserveAuditableData(boolean preserveAuditableData)
+ {
+ this.preserveAuditableData = preserveAuditableData;
+ }
+
+ public boolean isPreserveAuditableData()
+ {
+ return preserveAuditableData;
+ }
+
+ 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;
+ }
+
+ @Override
+ public List toFileInfoList(List nodeRefs)
+ {
+ List fileInfos = new LinkedList();
+ for (NodeRef nodeRef : nodeRefs)
+ {
+ // Use service proxy
+ FileInfo fileInfo = this.getFileInfo(nodeRef);
+ if (fileInfo != null)
+ {
+ fileInfos.add(fileInfo);
+ }
+ }
+ return fileInfos;
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ @Override
+ public boolean exists(NodeRef nodeRef)
+ {
+ return nodeService.exists(nodeRef);
+ }
+
+ @Override
+ 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;
+ }
+ }
+
+ @Override
+ @Extend(traitAPI = FileFolderServiceTrait.class, extensionAPI = FileFolderServiceExtension.class)
+ 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"})
+ @Override
+ @Extend(traitAPI = FileFolderServiceTrait.class, extensionAPI = FileFolderServiceExtension.class)
+ 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
+ @Extend(traitAPI = FileFolderServiceTrait.class, extensionAPI = FileFolderServiceExtension.class)
+ 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);
+ }
+
+ @Override
+ @Extend(traitAPI = FileFolderServiceTrait.class, extensionAPI = FileFolderServiceExtension.class)
+ 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);
+ }
+
+ @Override
+ public PagingResults list(NodeRef rootNodeRef,
+ Set assocTypeQNames,
+ Set searchTypeQNames,
+ Set ignoreAspectQNames,
+ List> sortProps,
+ List filterProps,
+ PagingRequest pagingRequest)
+ {
+ CannedQueryResults results = listImpl(rootNodeRef, null, assocTypeQNames, searchTypeQNames, ignoreAspectQNames, sortProps, filterProps, 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)
+ {
+ return listImpl(contextNodeRef, pattern, Collections.singleton(ContentModel.ASSOC_CONTAINS), searchTypeQNames, ignoreAspectQNames, sortProps, null, pagingRequest);
+ }
+
+ private CannedQueryResults listImpl(NodeRef contextNodeRef, String pattern, Set assocTypeQNames, Set searchTypeQNames, Set ignoreAspectQNames,
+ List> sortProps, List filterProps, 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, assocTypeQNames, searchTypeQNames, ignoreAspectQNames, filterProps, 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;
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ 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)
+ */
+ @Override
+ @Extend(traitAPI = FileFolderServiceTrait.class, extensionAPI = FileFolderServiceExtension.class)
+ 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
+ */
+ @Override
+ @Extend(traitAPI = FileFolderServiceTrait.class, extensionAPI = FileFolderServiceExtension.class)
+ 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