org.alfresco.repo.model.filefolder.FileFolderServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.model.filefolder.FileFolderServiceImpl.java

Source

/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.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.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;
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, 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<CannedQueryFactory<NodeRef>> cannedQueryRegistry;

    private boolean preserveAuditableData = true;

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

    private final ExtendedTrait<FileFolderServiceTrait> fileFolderTrait;

    /**
     * Default constructor
     */
    public FileFolderServiceImpl() {
        super();

        fileFolderTrait = new ExtendedTrait<FileFolderServiceTrait>(
                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<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 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<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);

        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<FileInfo> toFileInfoList(List<NodeRef> nodeRefs) {
        List<FileInfo> fileInfos = new LinkedList<FileInfo>();
        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<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" })
    @Override
    @Extend(traitAPI = FileFolderServiceTrait.class, extensionAPI = FileFolderServiceExtension.class)
    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
    @Extend(traitAPI = FileFolderServiceTrait.class, extensionAPI = FileFolderServiceExtension.class)
    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);
    }

    @Override
    @Extend(traitAPI = FileFolderServiceTrait.class, extensionAPI = FileFolderServiceExtension.class)
    public PagingResults<FileInfo> list(NodeRef rootNodeRef, Set<QName> searchTypeQNames,
            Set<QName> ignoreAspectQNames, List<Pair<QName, Boolean>> sortProps, PagingRequest pagingRequest) {
        CannedQueryResults<NodeRef> results = listImpl(rootNodeRef, null, searchTypeQNames, ignoreAspectQNames,
                sortProps, pagingRequest);
        return getPagingResults(pagingRequest, results);
    }

    @Override
    public PagingResults<FileInfo> list(NodeRef rootNodeRef, Set<QName> assocTypeQNames,
            Set<QName> searchTypeQNames, Set<QName> ignoreAspectQNames, List<Pair<QName, Boolean>> sortProps,
            List<FilterProp> filterProps, PagingRequest pagingRequest) {
        CannedQueryResults<NodeRef> results = listImpl(rootNodeRef, null, assocTypeQNames, searchTypeQNames,
                ignoreAspectQNames, sortProps, filterProps, 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)

    /**
     *
     * @param contextNodeRef
     * @param pattern
     * @param searchTypeQNames
     * @param ignoreAspectQNames
     * @param sortProps
     * @param pagingRequest
     * @return
     */
    private CannedQueryResults<NodeRef> listImpl(NodeRef contextNodeRef, String pattern,
            Set<QName> searchTypeQNames, Set<QName> ignoreAspectQNames, List<Pair<QName, Boolean>> sortProps,
            PagingRequest pagingRequest) {
        return listImpl(contextNodeRef, pattern, Collections.singleton(ContentModel.ASSOC_CONTAINS),
                searchTypeQNames, ignoreAspectQNames, sortProps, null, pagingRequest);
    }

    private CannedQueryResults<NodeRef> listImpl(NodeRef contextNodeRef, String pattern, Set<QName> assocTypeQNames,
            Set<QName> searchTypeQNames, Set<QName> ignoreAspectQNames, List<Pair<QName, Boolean>> sortProps,
            List<FilterProp> 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<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;
    }

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

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

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

    @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<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
     */
    @Override
    @Extend(traitAPI = FileFolderServiceTrait.class, extensionAPI = FileFolderServiceExtension.class)
    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 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
     */
    /* <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)
     */
    @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<QName, Serializable> properties = new HashMap<QName, Serializable>(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.
     * <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
     */
    @Override
    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
     */
    @Override
    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;
        }
    }

    @Override
    public FileInfo resolveNamePath(NodeRef rootNodeRef, List<String> pathElements) throws FileNotFoundException {
        return resolveNamePath(rootNodeRef, pathElements, true);
    }

    @Override
    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);
            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<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) {
            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 <M extends Trait> ExtendedTrait<M> getTrait(Class<? extends M> traitAPI) {
        return (ExtendedTrait<M>) 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<QName>, Set<QName>> buildSearchTypesAndIgnoreAspects(boolean files, boolean folders,
                Set<QName> ignoreQNameTypes) {
            return thisService.buildSearchTypesAndIgnoreAspects(files, folders, ignoreQNameTypes);
        }

        @Override
        public FileInfo createFileInfo(NodeRef nodeRef, QName typeQName, boolean isFolder, boolean isHidden,
                Map<QName, Serializable> properties) {
            return new FileInfoImpl(nodeRef, typeQName, isFolder, isHidden, properties);
        }

        @Override
        public FileFolderServiceType getType(QName typeQName) {
            return thisService.getType(typeQName);
        }

        @Override
        public List<FileInfo> list(final NodeRef contextNodeRef) {

            return thisService.list(contextNodeRef);
        }

        @Override
        public PagingResults<FileInfo> list(final NodeRef contextNodeRef, final boolean files,
                final boolean folders, final String pattern, final Set<QName> ignoreQNames,
                final List<Pair<QName, Boolean>> sortProps, final PagingRequest pagingRequest) {
            return thisService.list(contextNodeRef, files, folders, pattern, ignoreQNames, sortProps,
                    pagingRequest);
        }

        @Override
        public PagingResults<FileInfo> list(final NodeRef rootNodeRef, final Set<QName> searchTypeQNames,
                final Set<QName> ignoreAspectQNames, final List<Pair<QName, Boolean>> sortProps,
                final PagingRequest pagingRequest) {
            return thisService.list(rootNodeRef, searchTypeQNames, ignoreAspectQNames, sortProps, pagingRequest);
        }

        @Override
        public List<FileInfo> 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<FileInfo> list(NodeRef contextNodeRef, boolean files, boolean folders,
                Set<QName> ignoreQNames, List<Pair<QName, Boolean>> sortProps, PagingRequest pagingRequest) {
            return thisService.list(contextNodeRef, files, folders, ignoreQNames, sortProps, pagingRequest);
        }
    };
}