org.alfresco.filesys.repo.ContentDiskDriver.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.filesys.repo.ContentDiskDriver.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.filesys.repo;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;

import javax.transaction.UserTransaction;

import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.filesys.alfresco.AlfrescoContext;
import org.alfresco.filesys.alfresco.AlfrescoNetworkFile;
import org.alfresco.filesys.alfresco.AlfrescoTxDiskDriver;
import org.alfresco.jlan.server.SrvSession;
import org.alfresco.jlan.server.core.DeviceContext;
import org.alfresco.jlan.server.core.DeviceContextException;
import org.alfresco.jlan.server.filesys.AccessDeniedException;
import org.alfresco.jlan.server.filesys.AccessMode;
import org.alfresco.jlan.server.filesys.DirectoryNotEmptyException;
import org.alfresco.jlan.server.filesys.DiskDeviceContext;
import org.alfresco.jlan.server.filesys.DiskFullException;
import org.alfresco.jlan.server.filesys.DiskInterface;
import org.alfresco.jlan.server.filesys.DiskSizeInterface;
import org.alfresco.jlan.server.filesys.FileAttribute;
import org.alfresco.jlan.server.filesys.FileExistsException;
import org.alfresco.jlan.server.filesys.FileInfo;
import org.alfresco.jlan.server.filesys.FileName;
import org.alfresco.jlan.server.filesys.FileOpenParams;
import org.alfresco.jlan.server.filesys.FileSharingException;
import org.alfresco.jlan.server.filesys.FileStatus;
import org.alfresco.jlan.server.filesys.NetworkFile;
import org.alfresco.jlan.server.filesys.SearchContext;
import org.alfresco.jlan.server.filesys.SrvDiskInfo;
import org.alfresco.jlan.server.filesys.TreeConnection;
import org.alfresco.jlan.server.filesys.cache.FileState;
import org.alfresco.jlan.server.filesys.pseudo.MemoryNetworkFile;
import org.alfresco.jlan.server.filesys.pseudo.PseudoFile;
import org.alfresco.jlan.server.filesys.pseudo.PseudoFileInterface;
import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList;
import org.alfresco.jlan.server.filesys.pseudo.PseudoNetworkFile;
import org.alfresco.jlan.server.filesys.quota.QuotaManager;
import org.alfresco.jlan.server.filesys.quota.QuotaManagerException;
import org.alfresco.jlan.server.locking.FileLockingInterface;
import org.alfresco.jlan.server.locking.LockManager;
import org.alfresco.jlan.server.locking.OpLockInterface;
import org.alfresco.jlan.server.locking.OpLockManager;
import org.alfresco.jlan.smb.SharingMode;
import org.alfresco.jlan.smb.WinNT;
import org.alfresco.jlan.smb.server.SMBServer;
import org.alfresco.jlan.smb.server.SMBSrvSession;
import org.alfresco.jlan.util.MemorySize;
import org.alfresco.jlan.util.WildCard;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.executer.ContentMetadataExtracter;
import org.alfresco.repo.admin.SysAdminParams;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.node.archive.NodeArchiveService;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.security.authentication.AuthenticationContext;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.lock.LockService;
import org.alfresco.service.cmr.lock.LockType;
import org.alfresco.service.cmr.lock.NodeLockedException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentService;
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.StoreRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.OwnableService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.Pair;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.config.ConfigElement;

/**
 * Content repository filesystem driver class
 * 
 * <p>Provides a filesystem interface for various protocols such as SMB/CIFS and FTP.
 * 
 * @author gkspencer
 */
public class ContentDiskDriver extends AlfrescoTxDiskDriver
        implements DiskInterface, FileLockingInterface, OpLockInterface, DiskSizeInterface {
    // Logging
    private static final Log logger = LogFactory.getLog(ContentDiskDriver.class);

    // Configuration key names

    private static final String KEY_STORE = "store";
    private static final String KEY_ROOT_PATH = "rootPath";
    private static final String KEY_RELATIVE_PATH = "relativePath";

    // File status values used in the file state cache

    public static final int FileUnknown = FileStatus.Unknown;
    public static final int FileNotExist = FileStatus.NotExist;
    public static final int FileExists = FileStatus.FileExists;
    public static final int DirectoryExists = FileStatus.DirectoryExists;

    public static final int CustomFileStatus = FileStatus.MaxStatus + 1;
    public static final int FileRenamed = CustomFileStatus;
    public static final int DeleteOnClose = CustomFileStatus + 1;

    // File state attributes

    public static final String AttrLinkNode = "ContentLinkNode";
    public static final String CanDeleteWithoutPerms = "CanDeleteWithoutPerms";

    // List of content properties to copy during rename

    private static QName[] _copyProperties = { ContentModel.PROP_AUTHOR, ContentModel.PROP_TITLE,
            ContentModel.PROP_DESCRIPTION };

    // List of property namespaces to exclude from copy during rename

    private static Set<String> _excludedNamespaces = new TreeSet<String>(Arrays.asList(
            new String[] { NamespaceService.CONTENT_MODEL_1_0_URI, NamespaceService.SYSTEM_MODEL_1_0_URI }));

    // Disk sizing contants

    private static final int DiskBlockSize = 512; // bytes per block
    private static final long DiskAllocationUnit = 32 * MemorySize.KILOBYTE;
    private static final long DiskBlocksPerUnit = DiskAllocationUnit / DiskBlockSize;

    // Disk size returned in the content store does not support free/total size

    protected static final long DiskSizeDefault = 1 * MemorySize.TERABYTE;
    protected static final long DiskFreeDefault = DiskSizeDefault / 2;

    private boolean isReadOnly;
    private boolean isLockedFilesAsOffline;
    // pattern for detect CSV files.
    private Pattern renameCSVShufflePattern = Pattern.compile(".*[a-f0-9]{8}+$");
    // Services and helpers

    private CifsHelper cifsHelper;
    private NamespaceService namespaceService;
    private NodeService nodeService;
    private CheckOutCheckInService checkOutCheckInService;
    private SearchService searchService;
    private ContentService contentService;
    private MimetypeService mimetypeService;
    private PermissionService permissionService;
    private FileFolderService fileFolderService;
    private NodeArchiveService nodeArchiveService;
    private LockService lockService;
    private DictionaryService dictionaryService;
    private OwnableService ownableService;
    private ActionService actionService;

    private AuthenticationContext authContext;
    private AuthenticationService authService;
    private SysAdminParams sysAdminParams;

    private BehaviourFilter policyBehaviourFilter;
    private NodeMonitorFactory m_nodeMonitorFactory;

    /**
     * Class constructor
     * 
     * @param cifsHelper to connect to the repository services
     */
    public ContentDiskDriver(CifsHelper cifsHelper) {
        this.cifsHelper = cifsHelper;
    }

    public void init() {
        PropertyCheck.mandatory(this, "nodeService", nodeService);
    }

    /**
     * Return the CIFS helper
     * 
     * @return CifsHelper
     */
    public final CifsHelper getCifsHelper() {
        return this.cifsHelper;
    }

    /**
     * Return the authentication service
     * 
     * @return AuthenticationService
     */
    public final AuthenticationService getAuthenticationService() {
        return authService;
    }

    /**
     * Return the authentication context
     * 
     * @return AuthenticationContext
     */
    public final AuthenticationContext getAuthenticationContext() {
        return authContext;
    }

    /**
     * Return the node service
     * 
     * @return NodeService
     */
    public final NodeService getNodeService() {
        return this.nodeService;
    }

    /**
     * @return          service to provide information on check-in and check-out
     */
    public CheckOutCheckInService getCheckOutCheckInService() {
        return checkOutCheckInService;
    }

    /**
     * Return the content service
     * 
     * @return ContentService
     */
    public final ContentService getContentService() {
        return this.contentService;
    }

    /**
     * Return the namespace service
     * 
     * @return NamespaceService
     */
    public final NamespaceService getNamespaceService() {
        return this.namespaceService;
    }

    /**
     * Return the search service
     * 
     * @return SearchService
     */
    public final SearchService getSearchService() {
        return this.searchService;
    }

    /**
     * Return the file folder service
     * 
     * @return FileFolderService
     */
    public final FileFolderService getFileFolderService() {
        return this.fileFolderService;
    }

    /**
     * Return the permission service
     * 
     * @return PermissionService
     */
    public final PermissionService getPermissionService() {
        return this.permissionService;
    }

    /**
     * Return the node archive service
     */
    public final NodeArchiveService getNodeArchiveService() {
        return nodeArchiveService;
    }

    /**
     * Return the lock service
     * 
     * @return LockService
     */
    public final LockService getLockService() {
        return lockService;
    }

    /**
     * Get the policy behaviour filter, used to inhibit versioning on a per transaction basis
     */
    public BehaviourFilter getPolicyFilter() {
        return policyBehaviourFilter;
    }

    /**
     * Return the dictionary service
     * 
     * @return DictionaryService
     */
    public final DictionaryService getDictionaryService() {
        return dictionaryService;
    }

    /**
     * Get the ownable service
     * 
     * @return OwnableService
     */
    public final OwnableService getOwnableService() {
        return ownableService;
    }

    /**
     * @param contentService the content service
     */
    public void setContentService(ContentService contentService) {
        this.contentService = contentService;
    }

    /**
     * @param namespaceService the namespace service
     */
    public void setNamespaceService(NamespaceService namespaceService) {
        this.namespaceService = namespaceService;
    }

    /**
     * @param nodeService the node service
     */
    public void setNodeService(NodeService nodeService) {
        this.nodeService = nodeService;
    }

    /**
     * @param checkOutCheckInService            used to check for checked out nodes
     */
    public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService) {
        this.checkOutCheckInService = checkOutCheckInService;
    }

    /**
     * @param searchService the search service
     */
    public void setSearchService(SearchService searchService) {
        this.searchService = searchService;
    }

    /**
     * Set the permission service
     * 
     * @param permissionService PermissionService
     */
    public void setPermissionService(PermissionService permissionService) {
        this.permissionService = permissionService;
    }

    /**
     * Set the authentication context
     * 
     * @param authContext AuthenticationContext
     */
    public void setAuthenticationContext(AuthenticationContext authContext) {
        this.authContext = authContext;
    }

    /**
     * Set the authentication service
     * 
     * @param authService AuthenticationService
     */
    public void setAuthenticationService(AuthenticationService authService) {
        this.authService = authService;
    }

    /**
     * Sets the sys admin params.
     * 
     * @param sysAdminParams
     *            the sys admin params
     */
    public void setSysAdminParams(SysAdminParams sysAdminParams) {
        this.sysAdminParams = sysAdminParams;
    }

    /**
     * Set the file folder service
     * 
     * @param fileService FileFolderService
     */
    public void setFileFolderService(FileFolderService fileService) {
        fileFolderService = fileService;
    }

    /**
     * @param mimetypeService       service for helping with mimetypes and encoding
     */
    public void setMimetypeService(MimetypeService mimetypeService) {
        this.mimetypeService = mimetypeService;
    }

    /**
     * Set the node monitor factory
     * 
     * @param nodeMonitorFactory NodeMonitorFactory
     */
    public void setNodeMonitorFactory(NodeMonitorFactory nodeMonitorFactory) {
        m_nodeMonitorFactory = nodeMonitorFactory;
    }

    /**
     * Set the node archive service
     * 
     * @param nodeArchiveService nodeArchiveService
     */
    public void setNodeArchiveService(NodeArchiveService nodeArchiveService) {
        this.nodeArchiveService = nodeArchiveService;
    }

    /**
     * Set the lock service
     * 
     * @param lockService LockService
     */
    public void setLockService(LockService lockService) {
        this.lockService = lockService;
    }

    /**
     * Set the policy behaviour filter, used to inhibit versioning on a per transaction basis
     * 
     * @param policyFilter PolicyBehaviourFilter
     */
    public void setPolicyFilter(BehaviourFilter policyFilter) {
        this.policyBehaviourFilter = policyFilter;
    }

    /**
     * Set the dictionary service
     * 
     * @param dictionaryService DictionaryService
     */
    public void setDictionaryService(DictionaryService dictionaryService) {
        this.dictionaryService = dictionaryService;
    }

    /**
     * Set the ownable servive
     * 
     * @param ownableService OwnableService
     */
    public void setOwnableService(OwnableService ownableService) {
        this.ownableService = ownableService;
    }

    /**
     * Set the regular expression that will be applied to CSV files during renames.
     * <b>MNT-211</b>
     * 
     * @param renameCSVShufflePattern      a regular expression CSV filename match
     */
    public void setRenameCSVShufflePattern(Pattern renameCSVShufflePattern) {
        this.renameCSVShufflePattern = renameCSVShufflePattern;
    }

    /**
     * Parse and validate the parameter string and create a device context object for this instance
     * of the shared device. The same DeviceInterface implementation may be used for multiple
     * shares.
     * <p>
     * WARNING: side effect, may commit or roll back current user transaction context.
     * 
     * @param deviceName The name of the device
     * @param cfg ConfigElement the configuration of the device context.
     * @return DeviceContext
     * @exception DeviceContextException
     */
    //  MER TODO - transaction handling in registerContext needs changing
    public DeviceContext createContext(String deviceName, ConfigElement cfg) throws DeviceContextException {
        ContentContext context = null;

        try {
            // Get the store
            ConfigElement storeElement = cfg.getChild(KEY_STORE);
            if (storeElement == null || storeElement.getValue() == null || storeElement.getValue().length() == 0) {
                throw new DeviceContextException("Device missing init value: " + KEY_STORE);
            }
            String storeValue = storeElement.getValue();

            // Get the root path

            ConfigElement rootPathElement = cfg.getChild(KEY_ROOT_PATH);
            if (rootPathElement == null || rootPathElement.getValue() == null
                    || rootPathElement.getValue().length() == 0) {
                throw new DeviceContextException("Device missing init value: " + KEY_ROOT_PATH);
            }
            String rootPath = rootPathElement.getValue();

            // Create the context

            context = new ContentContext();
            context.setDeviceName(deviceName);
            context.setStoreName(storeValue);
            context.setRootPath(rootPath);
            context.setSysAdminParams(this.sysAdminParams);

            // Check if a relative path has been specified

            ConfigElement relativePathElement = cfg.getChild(KEY_RELATIVE_PATH);

            if (relativePathElement != null) {
                // Make sure the path is in CIFS format

                String relPath = relativePathElement.getValue().replace('/', FileName.DOS_SEPERATOR);
                context.setRelativePath(relPath);
            }
        }
        /* 
         * MER - I changed the code below - resulted in a NPE anyway 
         * lower down
         */
        catch (DeviceContextException ex) {
            logger.error("Error during create context", ex);
            throw ex;

        }

        // Check if URL link files are enabled

        ConfigElement urlFileElem = cfg.getChild("urlFile");
        if (urlFileElem != null) {
            // Get the pseudo file name and web prefix path

            ConfigElement pseudoName = urlFileElem.getChild("filename");

            if (pseudoName != null) {
                context.setURLFileName(pseudoName.getValue());
            }
        }

        // Check if locked files should be marked as offline

        ConfigElement offlineFiles = cfg.getChild("offlineFiles");
        if (offlineFiles != null) {
            context.setOfflineFiles(true);
        }

        // Install the node service monitor

        // MER 01/03/2011 - I think one of these is the "wrong way round"

        if (cfg.getChild("disableNodeMonitor") == null) {

            // Create the node monitor
            context.setDisableNodeMonitor(true);
        }

        // Check if oplocks are enabled, if so then enable oplocks in the lock manager

        if (cfg.getChild("disableOplocks") != null) {
            context.setDisableOplocks(true);
        }

        // Register the device context

        registerContext(context);

        // Return the context for this shared filesystem

        return context;
    }

    /**
     * Registers a device context object for this instance
     * of the shared device. The same DeviceInterface implementation may be used for multiple
     * shares.
     * 
     * WARNING: side effect, will commit or roll back current user transaction context.
     * 
     * @param ctx the context
     * @exception DeviceContextException
     */
    //  MER TODO - transaction handling in registerContext needs changing
    @Override
    public void registerContext(DeviceContext ctx) throws DeviceContextException {
        super.registerContext(ctx);

        ContentContext context = (ContentContext) ctx;

        // Wrap the initialization in a transaction

        UserTransaction tx = getTransactionService().getUserTransaction(true);

        try {
            // Use the system user as the authenticated context for the filesystem initialization

            AuthenticationUtil.pushAuthentication();
            AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());

            // Start the transaction

            if (tx != null)
                tx.begin();

            // Get the store
            String storeValue = context.getStoreName();
            StoreRef storeRef = new StoreRef(storeValue);

            // Connect to the repo and ensure that the store exists

            if (!nodeService.exists(storeRef)) {
                throw new DeviceContextException("Store not created prior to application startup: " + storeRef);
            }
            NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef);

            // Get the root path
            String rootPath = context.getRootPath();

            // Find the root node for this device

            List<NodeRef> nodeRefs = searchService.selectNodes(storeRootNodeRef, rootPath, null, namespaceService,
                    false);

            NodeRef rootNodeRef = null;

            if (nodeRefs.size() > 1) {
                throw new DeviceContextException("Multiple possible roots for device: \n" + "   root path: "
                        + rootPath + "\n" + "   results: " + nodeRefs);
            } else if (nodeRefs.size() == 0) {
                // Nothing found

                throw new DeviceContextException("No root found for device: \n" + "   root path: " + rootPath);
            } else {
                // We found a node

                rootNodeRef = nodeRefs.get(0);
            }

            // Check if a relative path has been specified

            String relPath = context.getRelativePath();

            if (relPath != null && relPath.length() > 0) {
                // Find the node and validate that the relative path is to a folder

                NodeRef relPathNode = cifsHelper.getNodeRef(rootNodeRef, relPath);
                if (cifsHelper.isDirectory(relPathNode) == false)
                    throw new DeviceContextException("Relative path is not a folder, " + relPath);

                // Use the relative path node as the root of the filesystem

                rootNodeRef = relPathNode;
            } else {

                // Make sure the default root node is a folder

                if (cifsHelper.isDirectory(rootNodeRef) == false)
                    throw new DeviceContextException("Root node is not a folder type node");
            }

            // Commit the transaction

            // MER 16/03/2010 - Why is this transaction management here?
            tx.commit();
            tx = null;

            // Record the root node ref
            context.setRootNodeRef(rootNodeRef);
        } catch (Exception ex) {
            logger.error("Error during create context", ex);

            // MER BUGBUG Exception swallowed - will result in null pointer errors at best.
            throw new DeviceContextException("unable to register context", ex);
            // MER END
        } finally {
            // Restore authentication context

            AuthenticationUtil.popAuthentication();

            // If there is an active transaction then roll it back

            if (tx != null) {
                try {
                    tx.rollback();
                } catch (Exception ex) {
                    logger.warn("Failed to rollback transaction", ex);
                }
            }
        }

        // Check if locked files should be marked as offline
        if (context.getOfflineFiles()) {
            // Enable marking locked files as offline
            isLockedFilesAsOffline = true;

            // Logging

            logger.info("Locked files will be marked as offline");
        }

        // Enable file state caching

        //        context.enableStateCache(serverConfig, true);

        // Install the node service monitor

        if (!context.getDisableNodeMonitor() && m_nodeMonitorFactory != null) {

            // Create the node monitor

            NodeMonitor nodeMonitor = m_nodeMonitorFactory.createNodeMonitor(context);
            context.setNodeMonitor(nodeMonitor);
        }

        // Check if oplocks are enabled

        if (context.getDisableOplocks() == true)
            logger.warn("Oplock support disabled for filesystem " + ctx.getDeviceName());

        // Start the quota manager, if enabled

        if (context.hasQuotaManager()) {

            try {

                // Start the quota manager

                context.getQuotaManager().startManager(this, context);
                logger.info("Quota manager enabled for filesystem");
            } catch (QuotaManagerException ex) {
                logger.error("Failed to start quota manager", ex);
            }
        }
    }

    /**
     * Check if pseudo file support is enabled
     * 
     * @param context ContentContext
     * @return boolean
     */
    public final boolean hasPseudoFileInterface(ContentContext context) {
        return context.hasPseudoFileInterface();
    }

    /**
     * Return the pseudo file support implementation
     *
     * @param context ContentContext
     * @return PseudoFileInterface
     */
    public final PseudoFileInterface getPseudoFileInterface(ContentContext context) {
        return context.getPseudoFileInterface();
    }

    /**
     * Determine if the disk device is read-only.
     * 
     * @param sess Server session
     * @param ctx Device context
     * @return boolean
     * @exception java.io.IOException If an error occurs.
     */
    public boolean isReadOnly(SrvSession sess, DeviceContext ctx) throws IOException {
        return isReadOnly;
    }

    /**
     * Get the file information for the specified file.
     * 
     * @param session Server session
     * @param tree Tree connection
     * @param path File name/path that information is required for.
     * @return File information if valid, else null
     * @exception java.io.IOException The exception description.
     */
    public FileInfo getFileInformation(SrvSession session, TreeConnection tree, String path) throws IOException {
        if (logger.isDebugEnabled()) {
            logger.debug("getFileInformation:" + path);
        }
        // Start a transaction

        beginReadTransaction(session);

        // Get the device root

        ContentContext ctx = (ContentContext) tree.getContext();
        NodeRef infoParentNodeRef = ctx.getRootNode();

        if (path == null || path.length() == 0)
            path = FileName.DOS_SEPERATOR_STR;

        String infoPath = path;

        try {
            // Check if the path is to a pseudo file

            FileInfo finfo = null;

            if (hasPseudoFileInterface(ctx)) {
                // Make sure the parent folder has a file state, and the path exists

                String[] paths = FileName.splitPath(path);
                FileState fstate = ctx.getStateCache().findFileState(paths[0]);

                if (fstate == null) {
                    NodeRef nodeRef = getNodeForPath(tree, paths[0]);

                    if (nodeRef != null) {
                        // Get the file information for the node

                        finfo = cifsHelper.getFileInformation(nodeRef, isReadOnly, isLockedFilesAsOffline);
                    }

                    // Create the file state

                    fstate = ctx.getStateCache().findFileState(paths[0], true);

                    fstate.setFileStatus(DirectoryExists);
                    fstate.setFilesystemObject(nodeRef);

                    // Add pseudo files to the folder

                    getPseudoFileInterface(ctx).addPseudoFilesToFolder(session, tree, paths[0]);

                    // Debug

                    if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                        logger.debug("Added file state for pseudo files folder (getinfo) - " + paths[0]);
                } else if (fstate.hasPseudoFiles() == false) {
                    // Make sure the file state has the node ref

                    if (fstate.hasFilesystemObject() == false) {
                        // Get the node for the folder path

                        fstate.setFilesystemObject(getNodeForPath(tree, paths[0]));
                    }

                    // Add pseudo files for the parent folder

                    getPseudoFileInterface(ctx).addPseudoFilesToFolder(session, tree, paths[0]);

                    // Debug

                    if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                        logger.debug("Added pseudo files for folder (exists) - " + paths[0]);
                }

                // Get the pseudo file

                PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile(session, tree, path);
                if (pfile != null) {
                    // DEBUG
                    if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                        logger.debug("getInfo using pseudo file info for " + path);

                    FileInfo pseudoFileInfo = pfile.getFileInfo();
                    if (isReadOnly) {
                        int attr = pseudoFileInfo.getFileAttributes();
                        if ((attr & FileAttribute.ReadOnly) == 0) {
                            attr += FileAttribute.ReadOnly;
                            pseudoFileInfo.setFileAttributes(attr);
                        }
                    }
                    return pfile.getFileInfo();
                }
            }

            // Get the node ref for the path, chances are there is a file state in the cache

            NodeRef nodeRef = getNodeForPath(tree, infoPath);

            if (nodeRef != null) {
                // Get the file information for the node

                finfo = cifsHelper.getFileInformation(nodeRef, isReadOnly, isLockedFilesAsOffline);

                // DEBUG

                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                    logger.debug("getInfo using cached noderef for path " + path);
            }

            // If the required node was not in the state cache, the parent folder node might be

            if (finfo == null) {
                String[] paths = FileName.splitPath(path);

                if (paths[0] != null && paths[0].length() > 1) {
                    // Find the node ref for the folder being searched

                    nodeRef = getNodeForPath(tree, paths[0]);

                    if (nodeRef != null) {
                        infoParentNodeRef = nodeRef;
                        infoPath = paths[1];

                        // DEBUG

                        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                            logger.debug("getInfo using cached noderef for parent " + path);
                    }
                }

                // Access the repository to get the file information

                finfo = cifsHelper.getFileInformation(infoParentNodeRef, infoPath, isReadOnly,
                        isLockedFilesAsOffline);

                // DEBUG

                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                    logger.debug("Getting file information: path=" + path + " file info: " + finfo);
            }

            // Set the file id for the file using the relative path

            if (finfo != null) {

                // Set the file id

                long id = DefaultTypeConverter.INSTANCE.convert(Long.class,
                        nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID));
                finfo.setFileId((int) (id & 0xFFFFFFFFL));

                // Copy cached file details, if available

                FileState fstate = getStateForPath(tree, infoPath);
                if (fstate != null) {

                    // Copy cached timestamps

                    //                   if ( fstate.hasAccessDateTime())
                    //                      finfo.setAccessDateTime(fstate.getAccessDateTime());
                    if (fstate.hasChangeDateTime())
                        finfo.setChangeDateTime(fstate.getChangeDateTime());
                    if (fstate.hasModifyDateTime())
                        finfo.setModifyDateTime(fstate.getModifyDateTime());

                    // File allocation size

                    if (fstate.hasAllocationSize() && fstate.getAllocationSize() > finfo.getSize())
                        finfo.setAllocationSize(fstate.getAllocationSize());
                } else {

                    // Create a file state for the file/folder

                    fstate = ctx.getStateCache().findFileState(path, true);
                    if (finfo.isDirectory())
                        fstate.setFileStatus(DirectoryExists);
                    else
                        fstate.setFileStatus(FileExists);
                    fstate.setFilesystemObject(nodeRef);
                }
            }

            // Return the file information

            return finfo;
        } catch (FileNotFoundException e) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                logger.debug("Get file info - file not found, " + path);
            throw e;
        } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                logger.debug("Get file info - access denied, " + path);

            // Convert to a filesystem access denied status

            throw new AccessDeniedException("Get file information " + path);
        } catch (RuntimeException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                logger.debug("Get file info error", ex);

            // Convert to a general I/O exception

            throw new IOException("Get file information " + path);
        }
    }

    /**
     * Start a new search on the filesystem using the specified searchPath that may contain
     * wildcards.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param searchPath File(s) to search for, may include wildcards.
     * @param attributes Attributes of the file(s) to search for, see class SMBFileAttribute.
     * @return SearchContext
     * @exception java.io.FileNotFoundException If the search could not be started.
     */
    public SearchContext startSearch(SrvSession sess, TreeConnection tree, String searchPath, int attributes)
            throws FileNotFoundException {
        // Access the device context
        if (logger.isDebugEnabled()) {
            logger.debug("startSearch: " + searchPath);
        }

        ContentContext ctx = (ContentContext) tree.getContext();

        try {
            String searchFileSpec = searchPath;
            NodeRef searchRootNodeRef = ctx.getRootNode();
            FileState searchFolderState = null;

            // Create the transaction

            beginReadTransaction(sess);

            // If the state table is available see if we can speed up the search using either cached
            // file information or find the folder node to be searched without having to walk the path

            String[] paths = FileName.splitPath(searchPath);

            if (ctx.hasStateCache()) {
                // See if the folder to be searched has a file state, we can avoid having to walk the path

                if (paths[0] != null && paths[0].length() >= 1) {
                    // Find the node ref for the folder being searched

                    NodeRef nodeRef = getNodeForPath(tree, paths[0]);

                    // Get the file state for the folder being searched

                    searchFolderState = getStateForPath(tree, paths[0]);
                    if (searchFolderState == null) {
                        // Create a file state for the folder

                        searchFolderState = ctx.getStateCache().findFileState(paths[0], true);
                    }

                    // Make sure the associated node is set

                    if (searchFolderState.hasFilesystemObject() == false) {
                        // Set the associated node for the folder

                        searchFolderState.setFilesystemObject(nodeRef);
                    }

                    // Add pseudo files to the folder being searched

                    if (hasPseudoFileInterface(ctx))
                        getPseudoFileInterface(ctx).addPseudoFilesToFolder(sess, tree, paths[0]);

                    // Set the search node and file spec

                    if (nodeRef != null) {
                        searchRootNodeRef = nodeRef;
                        searchFileSpec = paths[1];

                        // DEBUG

                        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
                            logger.debug("Search using cached noderef for path " + searchPath);
                    }
                }
            }

            // Convert the all files wildcard

            if (searchFileSpec.equals("*.*"))
                searchFileSpec = "*";

            // Debug

            long startTime = 0L;
            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
                startTime = System.currentTimeMillis();

            // Perform the search

            List<NodeRef> results = cifsHelper.getNodeRefs(searchRootNodeRef, searchFileSpec);

            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH)) {
                long endTime = System.currentTimeMillis();
                if ((endTime - startTime) > 500)
                    logger.debug("Search for searchPath=" + searchPath + ", searchSpec=" + searchFileSpec
                            + ", searchRootNode=" + searchRootNodeRef + " took " + (endTime - startTime)
                            + "ms results=" + results.size());
            }

            // Check if there are any pseudo files for the folder being searched, for CIFS only

            PseudoFileList pseudoList = null;

            if (sess instanceof SMBSrvSession && searchFolderState != null && searchFolderState.hasPseudoFiles()) {
                // If it is a wildcard search use all pseudo files

                if (WildCard.containsWildcards(searchFileSpec)) {
                    // Get the list of pseudo files for the search path

                    pseudoList = searchFolderState.getPseudoFileList();

                    // Check if the wildcard is for all files or a subset

                    if (searchFileSpec.equals("*") == false && pseudoList != null
                            && pseudoList.numberOfFiles() > 0) {
                        // Generate a subset of pseudo files that match the wildcard search pattern

                        WildCard wildCard = new WildCard(searchFileSpec, false);
                        PseudoFileList filterList = null;

                        for (int i = 0; i < pseudoList.numberOfFiles(); i++) {
                            PseudoFile pseudoFile = pseudoList.getFileAt(i);
                            if (wildCard.matchesPattern(pseudoFile.getFileName())) {
                                // Add the pseudo file to the filtered list

                                if (filterList == null)
                                    filterList = new PseudoFileList();
                                filterList.addFile(pseudoFile);
                            }
                        }

                        // Use the filtered pseudo file list, or null if there were no matches

                        pseudoList = filterList;
                    }
                } else if (results == null || results.size() == 0) {
                    // Check if the required file is in the pseudo file list

                    String fname = paths[1];

                    if (fname != null) {
                        // Search for a matching pseudo file

                        PseudoFile pfile = searchFolderState.getPseudoFileList().findFile(fname, true);
                        if (pfile != null) {
                            // Create a file list with the required file

                            pseudoList = new PseudoFileList();
                            pseudoList.addFile(pfile);
                        }
                    }
                }
            }

            // Build the search context to store the results, use the cache lookup search for wildcard searches

            SearchContext searchCtx = null;

            if (searchFileSpec.equals("*")) {
                // Use a cache lookup search context 

                CacheLookupSearchContext cacheContext = new CacheLookupSearchContext(cifsHelper, results,
                        searchFileSpec, pseudoList, paths[0], ctx.getStateCache(), isLockedFilesAsOffline);
                searchCtx = cacheContext;

                // Set the '.' and '..' pseudo file entry details

                if (searchFolderState != null && searchFolderState.hasFilesystemObject()) {
                    // Get the '.' pseudo entry file details

                    FileInfo finfo = cifsHelper.getFileInformation(
                            (NodeRef) searchFolderState.getFilesystemObject(), isReadOnly, isLockedFilesAsOffline);

                    // Blend in any cached timestamps

                    if (searchFolderState != null) {
                        if (searchFolderState.hasAccessDateTime())
                            finfo.setAccessDateTime(searchFolderState.getAccessDateTime());

                        if (searchFolderState.hasChangeDateTime())
                            finfo.setChangeDateTime(searchFolderState.getChangeDateTime());

                        if (searchFolderState.hasModifyDateTime())
                            finfo.setModifyDateTime(searchFolderState.getModifyDateTime());
                    }

                    // Set the '.' pseudo entry details

                    cacheContext.setDotInfo(finfo);

                    // Check if the search folder has a parent, if we are at the root of the filesystem then re-use
                    // the file information

                    if (searchFolderState.getPath().equals(FileName.DOS_SEPERATOR_STR)) {

                        // Searching the root folder, re-use the search folder file information for the '..' pseudo entry

                        FileInfo dotDotInfo = new FileInfo();
                        dotDotInfo.copyFrom(finfo);
                        cacheContext.setDotDotInfo(dotDotInfo);
                    } else {

                        // Get the parent folder path

                        String parentPath = searchFolderState.getPath();
                        if (parentPath.endsWith(FileName.DOS_SEPERATOR_STR) && parentPath.length() > 1)
                            parentPath = parentPath.substring(0, parentPath.length() - 1);

                        int pos = parentPath.lastIndexOf(FileName.DOS_SEPERATOR_STR);
                        if (pos != -1)
                            parentPath = parentPath.substring(0, pos + 1);

                        // Get the file state for the parent path, if available

                        FileState parentState = ctx.getStateCache().findFileState(parentPath);
                        NodeRef parentNode = null;

                        if (parentState != null)
                            parentNode = (NodeRef) parentState.getFilesystemObject();

                        if (parentState == null || parentNode == null)
                            parentNode = getNodeForPath(tree, parentPath);

                        // Get the file information for the parent folder

                        finfo = cifsHelper.getFileInformation(parentNode, isReadOnly, isLockedFilesAsOffline);

                        // Blend in any cached timestamps

                        if (parentState != null) {
                            if (parentState.hasAccessDateTime())
                                finfo.setAccessDateTime(parentState.getAccessDateTime());

                            if (parentState.hasChangeDateTime())
                                finfo.setChangeDateTime(parentState.getChangeDateTime());

                            if (parentState.hasModifyDateTime())
                                finfo.setModifyDateTime(parentState.getModifyDateTime());
                        }

                        // Set the '..' pseudo entry details

                        cacheContext.setDotDotInfo(finfo);
                    }
                }
            } else {
                if (ctx.hasStateCache())
                    searchCtx = new CacheLookupSearchContext(cifsHelper, results, searchFileSpec, pseudoList,
                            paths[0], ctx.getStateCache(), isLockedFilesAsOffline);
                else
                    searchCtx = new ContentSearchContext(cifsHelper, results, searchFileSpec, pseudoList, paths[0],
                            isLockedFilesAsOffline);
            }

            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
                logger.debug("Started search: search path=" + searchPath + " attributes=" + attributes + ", ctx="
                        + searchCtx);

            // Return the search context

            return searchCtx;
        } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
                logger.debug("Start search - access denied, " + searchPath);

            // Convert to a file not found status

            throw new FileNotFoundException("Start search " + searchPath);
        } catch (RuntimeException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
                logger.debug("Start search", ex);

            // Convert to a file not found status

            throw new FileNotFoundException("Start search " + searchPath);
        }
    }

    /**
     * Check if the specified file exists, and whether it is a file or directory.
     * 
     * <p>
     * WARNING: side effect, commit or roll back current user transaction context.  Current transaction becomes read only.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param name the path of the file 
     * @return FileStatus (0: NotExist, 1 : FileExist, 2: DirectoryExists) 
     * @see FileStatus
     */
    public int fileExists(SrvSession sess, TreeConnection tree, String name) {
        ContentContext ctx = (ContentContext) tree.getContext();
        int status = FileStatus.Unknown;
        FileState fstate = null;

        try {
            // Check for a cached file state

            if (ctx.hasStateCache())
                fstate = ctx.getStateCache().findFileState(name, true);

            if (fstate != null && fstate.getFileStatus() != FileUnknown) {
                status = fstate.getFileStatus();
                if (status >= CustomFileStatus)
                    status = FileNotExist;

                // DEBUG

                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                    logger.debug("Cache hit - fileExists() " + name + ", sts=" + status);
            } else {
                // Check if pseudo files are enabled

                if (hasPseudoFileInterface(ctx)) {
                    // Check if the file name is a pseudo file name

                    if (getPseudoFileInterface(ctx).isPseudoFile(sess, tree, name)) {

                        // Make sure the parent folder has a file state, and the path exists

                        String[] paths = FileName.splitPath(name);
                        fstate = ctx.getStateCache().findFileState(paths[0]);

                        if (fstate == null) {

                            // Check if the path exists

                            if (fileExists(sess, tree, paths[0]) == FileStatus.DirectoryExists) {
                                // Create the file state

                                fstate = ctx.getStateCache().findFileState(paths[0], true);

                                fstate.setFileStatus(DirectoryExists);

                                // Get the node for the folder path

                                beginReadTransaction(sess);
                                fstate.setFilesystemObject(getNodeForPath(tree, paths[0]));

                                // Add pseudo files to the folder

                                getPseudoFileInterface(ctx).addPseudoFilesToFolder(sess, tree, paths[0]);

                                // Debug

                                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
                                    logger.debug("Added file state for pseudo files folder (exists) - " + paths[0]);
                            }
                        } else if (fstate.hasPseudoFiles() == false) {
                            // Make sure the file state has the node ref

                            if (fstate.hasFilesystemObject() == false) {
                                // Create the transaction

                                beginReadTransaction(sess);

                                // Get the node for the folder path

                                fstate.setFilesystemObject(getNodeForPath(tree, paths[0]));
                            }

                            // Add pseudo files for the parent folder

                            getPseudoFileInterface(ctx).addPseudoFilesToFolder(sess, tree, paths[0]);

                            // Debug

                            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
                                logger.debug("Added pseudo files for folder (exists) - " + paths[0]);
                        }

                        // Check if the path is to a pseudo file

                        PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile(sess, tree, name);
                        if (pfile != null) {
                            // Indicate that the file exists

                            status = FileStatus.FileExists;
                        } else {
                            // Failed to find pseudo file

                            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
                                logger.debug("Failed to find pseudo file (exists) - " + name);
                        }
                    }
                }

                // If the file is not a pseudo file then search for the file

                if (status == FileStatus.Unknown) {
                    // Create the transaction

                    beginReadTransaction(sess);

                    // Get the file information to check if the file/folder exists

                    FileInfo info = getFileInformation(sess, tree, name);
                    NodeRef nodeRef = getNodeOrNull(name, ctx, fstate);
                    nodeRef = ((null == nodeRef) && (info instanceof ContentFileInfo))
                            ? (((ContentFileInfo) info).getNodeRef())
                            : (nodeRef);

                    if ((null == nodeRef) || !fileFolderService.exists(nodeRef)) {
                        status = FileStatus.NotExist;
                    } else {
                        if (info.isDirectory()) {
                            status = FileStatus.DirectoryExists;
                        } else {
                            status = FileStatus.FileExists;
                        }
                    }

                    // Update the file state status

                    if (fstate != null)
                        fstate.setFileStatus(status);
                }
            }
        } catch (FileNotFoundException e) {
            status = FileStatus.NotExist;
            if (fstate != null)
                fstate.setFileStatus(status);
        } catch (IOException e) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                logger.debug("File exists error, " + name, e);

            status = FileStatus.NotExist;
        }

        // Debug

        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
            logger.debug(
                    "File status determined: name=" + name + " status=" + fileStatusString(fstate.getFileStatus()));

        // Return the file/folder status

        return status;
    }

    /**
     * Open a file or folder
     * 
     * @param sess SrvSession
     * @param tree TreeConnection
     * @param params FileOpenParams
     * @return NetworkFile
     * @exception IOException
     */
    public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException {
        // Create the transaction

        beginReadTransaction(sess);
        ContentContext ctx = (ContentContext) tree.getContext();

        try {
            // Check if pseudo files are enabled

            if (hasPseudoFileInterface(ctx)) {
                // Check if the file name is a pseudo file name

                String path = params.getPath();

                if (getPseudoFileInterface(ctx).isPseudoFile(sess, tree, path)) {

                    // Make sure the parent folder has a file state, and the path exists

                    String[] paths = FileName.splitPath(path);
                    FileState fstate = ctx.getStateCache().findFileState(paths[0]);

                    if (fstate == null) {

                        // Check if the path exists

                        if (fileExists(sess, tree, paths[0]) == FileStatus.DirectoryExists) {
                            // Create the file state and add any pseudo files

                            fstate = ctx.getStateCache().findFileState(paths[0], true);

                            fstate.setFileStatus(DirectoryExists);
                            getPseudoFileInterface(ctx).addPseudoFilesToFolder(sess, tree, paths[0]);

                            // Debug

                            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
                                logger.debug("Added file state for pseudo files folder (open) - " + paths[0]);
                        }
                    } else if (fstate.hasPseudoFiles() == false) {
                        // Add pseudo files for the parent folder

                        getPseudoFileInterface(ctx).addPseudoFilesToFolder(sess, tree, paths[0]);

                        // Debug

                        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
                            logger.debug("Added pseudo files for folder (open) - " + paths[0]);
                    }

                    // Check if the path is to a pseudo file

                    PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile(sess, tree, params.getPath());
                    if (pfile != null) {
                        // Create a network file to access the pseudo file data

                        return pfile.getFile(params.getPath());
                    } else {
                        // Failed to find pseudo file

                        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
                            logger.debug("Failed to find pseudo file (open) - " + params.getPath());
                    }
                }
            }

            // Not a pseudo file, try and open a normal file/folder node

            NodeRef nodeRef = getNodeForPath(tree, params.getPath());

            // Check permissions on the file/folder node
            //
            // Check for read access

            if (params.hasAccessMode(AccessMode.NTRead)
                    && permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.DENIED)
                throw new AccessDeniedException("No read access to " + params.getFullPath());

            // Check for write access

            if (params.hasAccessMode(AccessMode.NTWrite)
                    && permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED)
                throw new AccessDeniedException("No write access to " + params.getFullPath());

            // Check for delete access

            //            if ( params.hasAccessMode(AccessMode.NTDelete) &&
            //                    permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED)
            //                throw new AccessDeniedException("No delete access to " + params.getFullPath());

            // Check if the file has a lock

            String lockTypeStr = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_LOCK_TYPE);

            if (params.hasAccessMode(AccessMode.NTWrite) && lockTypeStr != null)
                throw new AccessDeniedException("File is locked, no write access to " + params.getFullPath());

            //  Check if there is a file state for the file

            FileState fstate = null;

            if (ctx.hasStateCache()) {
                // Check if there is a file state for the file

                fstate = ctx.getStateCache().findFileState(params.getPath());

                if (fstate != null) {
                    // Check if the file exists

                    if (fstate.exists() == false)
                        throw new FileNotFoundException();
                } else {

                    // Create a file state for the path

                    fstate = ctx.getStateCache().findFileState(params.getPath(), true);
                }

                // Check if the current file open allows the required shared access

                boolean nosharing = false;
                String noshrReason = null;

                // TEST

                if (params.getAccessMode() == AccessMode.NTFileGenericExecute
                        && params.getPath().toLowerCase().endsWith(".exe") == false) {

                    // DEBUG

                    if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) {
                        logger.debug("Execute access mode, path" + params.getPath());
                        logger.debug("  Fstate=" + fstate);
                    }

                    throw new AccessDeniedException("Invalid access mode");
                }

                if (fstate.getOpenCount() > 0 && params.isAttributesOnlyAccess() == false) {

                    // Check for impersonation security level from the original process that opened the file

                    if (params.getSecurityLevel() == WinNT.SecurityImpersonation
                            && params.getProcessId() == fstate.getProcessId())
                        nosharing = false;

                    // Check if the caller wants read access, check the sharing mode
                    // Check if the caller wants write access, check if the sharing mode allows write

                    else if (params.isReadOnlyAccess() && (fstate.getSharedAccess() & SharingMode.READ) != 0)
                        nosharing = false;

                    // Check if the caller wants write access, check the sharing mode

                    else if ((params.isReadWriteAccess() || params.isWriteOnlyAccess())
                            && (fstate.getSharedAccess() & SharingMode.WRITE) == 0) {
                        nosharing = true;
                        noshrReason = "Sharing mode disallows write";

                        // DEBUG

                        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                            logger.debug("Sharing mode disallows write access path=" + params.getPath());
                    }

                    // Check if the file has been opened for exclusive access

                    else if (fstate.getSharedAccess() == SharingMode.NOSHARING) {
                        nosharing = true;
                        noshrReason = "Sharing mode exclusive";
                    }

                    // Check if the required sharing mode is allowed by the current file open

                    else if ((fstate.getSharedAccess() & params.getSharedAccess()) != params.getSharedAccess()) {
                        nosharing = true;
                        noshrReason = "Sharing mode mismatch";

                        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                            logger.debug("Local share mode=0x" + Integer.toHexString(fstate.getSharedAccess())
                                    + ", params share mode=0x" + Integer.toHexString(params.getSharedAccess()));
                    }

                    // Check if the caller wants exclusive access to the file

                    else if (params.getSharedAccess() == SharingMode.NOSHARING) {
                        nosharing = true;
                        noshrReason = "Requestor wants exclusive mode";
                    }
                }

                // Check if the file allows shared access

                if (nosharing == true) {
                    if (params.getPath().equals("\\") == false) {

                        // DEBUG

                        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                            logger.debug("Sharing violation path=" + params.getPath() + ", sharing=0x"
                                    + Integer.toHexString(fstate.getSharedAccess()) + ",reason=" + noshrReason);

                        // File is locked by another user

                        throw new FileSharingException("File already open, " + params.getPath());
                    }
                }

                // Update the file sharing mode and process id, if this is the first file open

                fstate.setSharedAccess(params.getSharedAccess());
                fstate.setProcessId(params.getProcessId());

                // DEBUG

                if (logger.isDebugEnabled() && fstate.getOpenCount() == 0 && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                    logger.debug("Path " + params.getPath() + ", sharing=0x"
                            + Integer.toHexString(params.getSharedAccess()) + ", PID=" + params.getProcessId());
            }

            // Check if the node is a link node

            NodeRef linkRef = (NodeRef) nodeService.getProperty(nodeRef, ContentModel.PROP_LINK_DESTINATION);
            AlfrescoNetworkFile netFile = null;

            if (linkRef == null) {
                // Check if the file is already opened by this client/process

                if (tree.openFileCount() > 0 && params.isAttributesOnlyAccess() == false) {

                    // Search the open file table for this session/virtual circuit

                    int idx = 0;

                    while (idx < tree.getFileTableLength() && netFile == null) {

                        // Get the current file from the open file table

                        NetworkFile curFile = tree.findFile(idx);
                        if (curFile != null && curFile instanceof ContentNetworkFile) {

                            // Check if the file is the same path and process id

                            ContentNetworkFile contentFile = (ContentNetworkFile) curFile;
                            if (contentFile.getProcessId() == params.getProcessId()
                                    && contentFile.getFullName().equalsIgnoreCase(params.getFullPath())) {

                                // Check that the access mode is the same

                                if ((params.isReadWriteAccess()
                                        && contentFile.getGrantedAccess() == NetworkFile.READWRITE)
                                        || (params.isReadOnlyAccess()
                                                && contentFile.getGrantedAccess() == NetworkFile.READONLY)) {

                                    // Found a match, re-use the open file

                                    netFile = contentFile;

                                    // Increment the file open count, last file close will actually close the file/stream

                                    contentFile.incrementOpenCount();

                                    // DEBUG

                                    if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                                        logger.debug("Re-use existing file open Path " + params.getPath() + ", PID="
                                                + params.getProcessId() + ", params="
                                                + (params.isReadOnlyAccess() ? "ReadOnly" : "Write") + ", file="
                                                + (contentFile.getGrantedAccess() <= NetworkFile.READONLY
                                                        ? "ReadOnly"
                                                        : "Write"));
                                } else if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                                    logger.debug("Not re-using file path=" + params.getPath() + ", readWrite="
                                            + (params.isReadWriteAccess() ? "true" : "false") + ", readOnly="
                                            + (params.isReadOnlyAccess() ? "true" : "false") + ", grantedAccess="
                                            + contentFile.getGrantedAccessAsString());
                            }
                        }

                        // Update the file table index

                        idx++;
                    }
                }

                // Create the network file, if we could not match an existing file open

                if (netFile == null) {

                    // Create a new network file for the open request

                    netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService,
                            cifsHelper, nodeRef, params.getPath(), params.isReadOnlyAccess(),
                            params.isAttributesOnlyAccess(), sess);
                }
            } else {
                // Get the CIFS server name

                String srvName = null;
                SMBServer cifsServer = (SMBServer) sess.getServer().getConfiguration().findServer("CIFS");

                if (sess instanceof SMBSrvSession) {
                    SMBSrvSession smbSess = (SMBSrvSession) sess;
                    srvName = smbSess.getShareHostName();
                } else if (cifsServer != null) {
                    // Use the CIFS server name in the URL

                    srvName = cifsServer.getServerName();
                } else {
                    // Use the local server name in the URL

                    srvName = InetAddress.getLocalHost().getHostName();
                }

                // Convert the target node to a path, convert to URL format

                String path = getPathForNode(tree, linkRef);
                path = path.replace(FileName.DOS_SEPERATOR, '/');

                // Build the URL file data

                StringBuilder urlStr = new StringBuilder();

                urlStr.append("[InternetShortcut]\r\n");
                urlStr.append("URL=file://");
                urlStr.append(srvName);
                urlStr.append("/");
                urlStr.append(tree.getSharedDevice().getName());
                urlStr.append(path);
                urlStr.append("\r\n");

                // Create the in memory pseudo file for the URL link

                byte[] urlData = urlStr.toString().getBytes();

                // Get the file information for the link node

                FileInfo fInfo = cifsHelper.getFileInformation(nodeRef, isReadOnly, isLockedFilesAsOffline);

                // Set the file size to the actual data length

                fInfo.setFileSize(urlData.length);

                // Create the network file using the in-memory file data

                netFile = new LinkMemoryNetworkFile(fInfo.getFileName(), urlData, fInfo, nodeRef);
                netFile.setFullName(params.getPath());
            }

            // Generate a file id for the file

            if (netFile != null) {
                long id = DefaultTypeConverter.INSTANCE.convert(Long.class,
                        nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID));
                netFile.setFileId((int) (id & 0xFFFFFFFFL));

                // Indicate the file is open

                netFile.setClosed(false);
            }

            // If the file has been opened for overwrite then truncate the file to zero length, this will
            // also prevent the existing content data from being copied to the new version of the file

            if (params.isOverwrite() && netFile != null) {
                // Truncate the file to zero length

                netFile.truncateFile(0L);
            }

            // Create a file state for the open file

            if (ctx.hasStateCache()) {
                if (fstate == null)
                    fstate = ctx.getStateCache().findFileState(params.getPath(), true);

                // Update the file state, cache the node

                if (netFile.getGrantedAccess() > NetworkFile.ATTRIBUTESONLY)
                    fstate.incrementOpenCount();
                fstate.setFilesystemObject(nodeRef);

                // Store the state with the file

                netFile.setFileState(fstate);

                // Set the file access date/time, if available

                //                if ( fstate.hasAccessDateTime())
                //                    netFile.setAccessDate( fstate.getAccessDateTime());

                // Set the live file size, if available

                if (fstate.hasFileSize())
                    netFile.setFileSize(fstate.getFileSize());
            }

            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Opened network file: path=" + params.getPath() + " file open parameters=" + params
                        + " network file=" + netFile);

            // Return the network file

            return netFile;
        } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Open file - access denied, " + params.getFullPath());

            // Convert to a filesystem access denied status

            throw new AccessDeniedException("Open file " + params.getFullPath());
        } catch (RuntimeException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Open file error", ex);

            // Convert to a general I/O exception

            throw new IOException("Open file " + params.getFullPath());
        }
    }

    /**
     * Create a new file on the file system.
     * 
     * <p>
     * WARNING : side effect - closes current transaction context.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param params File create parameters
     * @return NetworkFile
     * @exception java.io.IOException If an error occurs.
     */
    public NetworkFile createFile(SrvSession sess, final TreeConnection tree, final FileOpenParams params)
            throws IOException {
        final ContentContext ctx = (ContentContext) tree.getContext();

        try {

            // Access the repository in a retryable write transaction
            Pair<String, NodeRef> result = doInWriteTransaction(sess, new CallableIO<Pair<String, NodeRef>>() {
                public Pair<String, NodeRef> call() throws IOException {
                    // Get the device root

                    NodeRef deviceRootNodeRef = ctx.getRootNode();

                    String path = params.getPath();
                    String parentPath = null;

                    // If the state table is available then try to find the parent folder node for the new file
                    // to save having to walk the path

                    if (ctx.hasStateCache()) {
                        // See if the parent folder has a file state, we can avoid having to walk the path

                        String[] paths = FileName.splitPath(path);
                        if (paths[0] != null && paths[0].length() > 1) {
                            // Find the node ref for the folder being searched

                            NodeRef nodeRef = getNodeForPath(tree, paths[0]);

                            if (nodeRef != null) {
                                deviceRootNodeRef = nodeRef;
                                path = paths[1];

                                // DEBUG

                                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                                    logger.debug("Create file using cached noderef for path " + paths[0]);
                            }

                            parentPath = paths[0];
                        }
                    }

                    // Create it - the path will be created, if necessary
                    if (logger.isDebugEnabled()) {
                        logger.debug("create new file" + path);
                    }

                    NodeRef nodeRef = null;

                    try {
                        nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, ContentModel.TYPE_CONTENT);
                        nodeService.addAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT, null);
                    } catch (FileExistsException ex) {
                        nodeRef = cifsHelper.getNodeRef(deviceRootNodeRef, path);
                        if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_SOFT_DELETE)) {
                            throw ex;
                        }
                    }

                    return new Pair<String, NodeRef>(parentPath, nodeRef);
                }
            });

            // Get or create the file state for the parent folder
            FileState parentState = null;
            String parentPath = result.getFirst();
            if (parentPath != null) {
                parentState = getStateForPath(tree, parentPath);
                if (parentState == null && ctx.hasStateCache())
                    parentState = ctx.getStateCache().findFileState(parentPath, true);
            }

            NodeRef nodeRef = result.getSecond();
            if (nodeRef != null && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_SOFT_DELETE)) {
                nodeService.removeAspect(nodeRef, ContentModel.ASPECT_SOFT_DELETE);
            }

            // Create the network file

            ContentNetworkFile netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService,
                    cifsHelper, result.getSecond(), params.getPath(), params.isReadOnlyAccess(),
                    params.isAttributesOnlyAccess(), sess);

            // Always allow write access to a newly created file

            netFile.setGrantedAccess(NetworkFile.READWRITE);

            // Set the owner process id for this open file

            netFile.setProcessId(params.getProcessId());

            // Truncate the file so that the content stream is created

            netFile.truncateFile(0L);

            // Indicate the file is open

            netFile.setClosed(false);

            // Generate a file id for the file

            if (netFile != null) {
                long id = DefaultTypeConverter.INSTANCE.convert(Long.class,
                        nodeService.getProperty(netFile.getNodeRef(), ContentModel.PROP_NODE_DBID));
                netFile.setFileId((int) (id & 0xFFFFFFFFL));
            }

            // Add a file state for the new file/folder

            if (ctx.hasStateCache()) {
                FileState fstate = ctx.getStateCache().findFileState(params.getPath(), true);
                if (fstate != null) {
                    // Save the file sharing mode, needs to be done before the open count is incremented

                    fstate.setSharedAccess(params.getSharedAccess());
                    fstate.setProcessId(params.getProcessId());

                    // Indicate that the file is open

                    fstate.setFileStatus(FileExists);
                    fstate.incrementOpenCount();
                    fstate.setFilesystemObject(result.getSecond());

                    // Track the intial allocation size

                    fstate.setAllocationSize(params.getAllocationSize());

                    // Store the file state with the file

                    netFile.setFileState(fstate);

                    // DEBUG

                    if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                        logger.debug("Create file, state=" + fstate);
                }

                // Update the parent folder file state

                if (parentState != null)
                    parentState.updateModifyDateTime();
            }

            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Created file: path=" + params.getPath() + " file open parameters=" + params + " node="
                        + result.getSecond() + " network file=" + netFile);

            // Return the new network file

            return netFile;
        } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Create file - access denied, " + params.getFullPath());

            // Convert to a filesystem access denied status

            throw new AccessDeniedException("Create file " + params.getFullPath());
        } catch (ContentIOException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Create file - content I/O error, " + params.getFullPath());

            // Convert to a filesystem disk full status

            throw new DiskFullException("Create file " + params.getFullPath());
        } catch (RuntimeException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Create file error", ex);

            // Convert to a general I/O exception

            throw new IOException("Create file " + params.getFullPath(), ex);
        }

    }

    /**
     * Create a new directory on this file system.
     *
     * <p>
     * WARNING : side effect - closes current transaction context.
     *
     * @param sess Server session
     * @param tree Tree connection.
     * @param params Directory create parameters
     * @exception java.io.IOException If an error occurs.
     */
    public void createDirectory(SrvSession sess, final TreeConnection tree, final FileOpenParams params)
            throws IOException {
        final ContentContext ctx = (ContentContext) tree.getContext();

        try {
            // Access the repository in a retryable write transaction
            Pair<String, NodeRef> result = doInWriteTransaction(sess, new CallableIO<Pair<String, NodeRef>>() {

                public Pair<String, NodeRef> call() throws IOException {
                    // get the device root

                    NodeRef deviceRootNodeRef = ctx.getRootNode();

                    String path = params.getPath();
                    String parentPath = null;

                    // If the state table is available then try to find the parent folder node for the new folder
                    // to save having to walk the path

                    if (ctx.hasStateCache()) {
                        // See if the parent folder has a file state, we can avoid having to walk the path

                        String[] paths = FileName.splitPath(path);
                        if (paths[0] != null && paths[0].length() > 1) {
                            // Find the node ref for the folder being searched

                            NodeRef nodeRef = getNodeForPath(tree, paths[0]);

                            if (nodeRef != null) {
                                deviceRootNodeRef = nodeRef;
                                path = paths[1];

                                // DEBUG

                                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                                    logger.debug("Create file using cached noderef for path " + paths[0]);
                            }

                            parentPath = paths[0];
                        }
                    }

                    // Create it - the path will be created, if necessary

                    NodeRef nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, ContentModel.TYPE_FOLDER);

                    return new Pair<String, NodeRef>(parentPath, nodeRef);
                }
            });

            // Get or create the file state for the parent folder
            FileState parentState = null;
            String parentPath = result.getFirst();
            if (parentPath != null) {
                parentState = getStateForPath(tree, parentPath);
                if (parentState == null && ctx.hasStateCache())
                    parentState = ctx.getStateCache().findFileState(parentPath, true);
            }

            // Add a file state for the new folder

            if (ctx.hasStateCache()) {
                FileState fstate = ctx.getStateCache().findFileState(params.getPath(), true);
                if (fstate != null) {
                    // Indicate that the file is open

                    fstate.setFileStatus(DirectoryExists);
                    fstate.setFilesystemObject(result.getSecond());

                    // DEBUG

                    if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                        logger.debug("Create folder, state=" + fstate);
                }

                // Update the parent folder file state

                if (parentState != null)
                    parentState.updateModifyDateTime();
            }

            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Created directory: path=" + params.getPath() + " file open params=" + params
                        + " node=" + result.getSecond());
        } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Create directory - access denied, " + params.getFullPath());

            // Convert to a filesystem access denied status

            throw new AccessDeniedException("Create directory " + params.getFullPath());
        } catch (RuntimeException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Create directory error", ex);

            // Convert to a general I/O exception

            throw new IOException("Create directory " + params.getFullPath(), ex);
        }
    }

    /**
     * Delete the directory from the filesystem.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param dir Directory name.
     * @exception java.io.IOException The exception description.
     */
    public void deleteDirectory(SrvSession sess, TreeConnection tree, final String dir) throws IOException {
        // get the device root

        ContentContext ctx = (ContentContext) tree.getContext();
        final NodeRef deviceRootNodeRef = ctx.getRootNode();

        try {
            NodeRef nodeRef = doInWriteTransaction(sess, new CallableIO<NodeRef>() {

                public NodeRef call() throws IOException {
                    // Get the node for the folder                    

                    NodeRef nodeRef = cifsHelper.getNodeRef(deviceRootNodeRef, dir);
                    if (fileFolderService.exists(nodeRef)) {
                        // Check if the folder is empty                        

                        if (cifsHelper.isFolderEmpty(nodeRef)) {
                            // Delete the folder node           

                            fileFolderService.delete(nodeRef);
                            return nodeRef;
                        } else {
                            throw new DirectoryNotEmptyException(dir);
                        }
                    }
                    return null;
                }
            });
            if (nodeRef != null && ctx.hasStateCache()) {
                // Remove the file state

                ctx.getStateCache().removeFileState(dir);

                // Update, or create, a parent folder file state

                String[] paths = FileName.splitPath(dir);
                if (paths[0] != null && paths[0].length() > 1) {
                    // Get the file state for the parent folder

                    FileState parentState = getStateForPath(tree, paths[0]);
                    if (parentState == null && ctx.hasStateCache())
                        parentState = ctx.getStateCache().findFileState(paths[0], true);

                    // Update the modification timestamp

                    parentState.updateModifyDateTime();
                }
            }

            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Deleted directory: directory=" + dir + " node=" + nodeRef);
        } catch (FileNotFoundException e) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Delete directory - file not found, " + dir);
        } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Delete directory - access denied, " + dir);

            // Convert to a filesystem access denied status

            throw new AccessDeniedException("Delete directory " + dir);
        } catch (RuntimeException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Delete directory", ex);

            // Convert to a general I/O exception

            throw new IOException("Delete directory " + dir);
        }
    }

    /**
     * Flush any buffered output for the specified file.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param file Network file context.
     * @exception java.io.IOException The exception description.
     */
    public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) throws IOException {
        // Debug

        ContentContext ctx = (ContentContext) tree.getContext();

        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO))
            logger.debug("Flush file=" + file.getFullName());

        // Flush the file data

        file.flushFile();
    }

    /**
     * Close the file.
     * 
     * @param sess Server session
     * @param tree Tree connection.
     * @param file Network file context.
     * @exception java.io.IOException If an error occurs.
     */
    public void closeFile(SrvSession sess, TreeConnection tree, final NetworkFile file) throws IOException {
        if (logger.isDebugEnabled()) {
            logger.debug("Close file: file" + file);
        }

        // Get the associated file state

        final ContentContext ctx = (ContentContext) tree.getContext();
        FileState toUpdate = null;

        // Check for a content file

        if (file instanceof ContentNetworkFile) {

            // Update the file state

            if (ctx.hasStateCache()) {
                FileState fstate = ctx.getStateCache().findFileState(file.getFullName());
                if (fstate != null) {

                    // If the file open count is now zero then reset the stored sharing mode

                    if (file.getGrantedAccess() > NetworkFile.ATTRIBUTESONLY && fstate.decrementOpenCount() == 0)
                        fstate.setSharedAccess(SharingMode.READWRITE + SharingMode.DELETE);

                    // Check if there is an oplock on the file

                    if (file.hasOpLock()) {

                        // Release the oplock

                        OpLockInterface flIface = (OpLockInterface) this;
                        OpLockManager oplockMgr = flIface.getOpLockManager(sess, tree);

                        oplockMgr.releaseOpLock(file.getOpLock().getPath());

                        //  DEBUG

                        if (logger.isDebugEnabled())
                            logger.debug("Released oplock for closed file, file=" + file.getFullName());
                    }

                    // Check if there is a cached modification timestamp to be written out

                    if (file.hasDeleteOnClose() == false && fstate.hasModifyDateTime()
                            && fstate.hasFilesystemObject() && fstate.isDirectory() == false) {

                        // Update the modification date on the file/folder node
                        toUpdate = fstate;
                    }
                }
            }

            // Decrement the file open count

            ContentNetworkFile contentFile = (ContentNetworkFile) file;

            if (contentFile.decrementOpenCount() > 0) {

                // DEBUG

                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                    logger.debug("Deferred file close, path=" + file.getFullName() + ", openCount="
                            + contentFile.getOpenCount());

                // Defer the file close to the last reference

                return;
            } else if (logger.isDebugEnabled())
                logger.debug("Last reference to file, closing, path=" + file.getFullName() + ", access="
                        + file.getGrantedAccessAsString() + ", fid=" + file.getProtocolId() + ", modified="
                        + contentFile.isModified());
        }

        // Check if there is a quota manager enabled

        long fileSize = 0L;

        if (ctx.hasQuotaManager() && file.hasDeleteOnClose()) {

            // Make sure the content stream has been opened, to get the current file size

            if (file instanceof ContentNetworkFile) {
                ContentNetworkFile contentFile = (ContentNetworkFile) file;
                if (contentFile.hasContent() == false)
                    contentFile.openContent(false, false);

                // Save the current file size

                fileSize = contentFile.getFileSize();
            }
        }

        // Depending on whether the node has the NO_CONTENT aspect, we may have to wipe it out on error
        final CallableIO<Void> errorHandler = new CallableIO<Void>() {
            public Void call() throws IOException {
                if (file instanceof NodeRefNetworkFile) {
                    NodeRef nodeRef = ((NodeRefNetworkFile) file).getNodeRef();
                    if (nodeService.exists(nodeRef)
                            && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT)) {
                        logger.debug("No content - delete");
                        fileFolderService.delete(nodeRef);
                    }
                }

                return null;
            }
        };
        try {
            // Perform repository updates in a retryable write transaction
            final FileState finalFileState = toUpdate;
            Pair<NodeRef, Boolean> result = doInWriteTransaction(sess, new CallableIO<Pair<NodeRef, Boolean>>() {
                public Pair<NodeRef, Boolean> call() throws IOException {
                    // Check if the file is an OpenOffice document and hte truncation flag is set
                    //
                    // Note: Check before the timestamp update

                    if (file instanceof OpenOfficeContentNetworkFile) {
                        OpenOfficeContentNetworkFile ooFile = (OpenOfficeContentNetworkFile) file;
                        if (ooFile.truncatedToZeroLength()) {

                            // Inhibit versioning for this transaction

                            getPolicyFilter().disableBehaviour(ContentModel.ASPECT_VERSIONABLE);

                            // Debug

                            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                                logger.debug("OpenOffice file truncation update only, inhibit versioning, "
                                        + file.getFullName());
                        }
                    }

                    // Update the modification date on the file/folder node
                    if (finalFileState != null && file instanceof ContentNetworkFile) {
                        NodeRef nodeRef = (NodeRef) finalFileState.getFilesystemObject();

                        // Check if the file data has been updated, if not then inhibit versioning for this txn
                        // so the timestamp update does not generate a new file version

                        ContentNetworkFile contentFile = (ContentNetworkFile) file;
                        if (contentFile.isModified() == false
                                && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) {

                            // Stop a new file version being generated

                            getPolicyFilter().disableBehaviour(ContentModel.ASPECT_VERSIONABLE);

                            // Debug

                            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                                logger.debug("Timestamp update only, inhibit versioning, " + file.getFullName());
                        }

                        // Update the modification timestamp

                        getPolicyFilter().disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);

                        if (permissionService.hasPermission((NodeRef) finalFileState.getFilesystemObject(),
                                PermissionService.WRITE_PROPERTIES) == AccessStatus.ALLOWED) {
                            nodeService.setProperty(nodeRef, ContentModel.PROP_MODIFIER,
                                    authService.getCurrentUserName());
                            Date modifyDate = new Date(finalFileState.getModifyDateTime());
                            nodeService.setProperty(nodeRef, ContentModel.PROP_MODIFIED, modifyDate);

                            // Debug

                            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                                logger.debug("Updated modification timestamp, " + file.getFullName() + ", modTime="
                                        + modifyDate);
                        }
                    }

                    // Defer to the network file to close the stream and remove the content

                    file.close();

                    // Remove the node if marked for delete

                    if (file.hasDeleteOnClose()) {
                        logger.debug("File has delete on close");
                        // Check if the file is a noderef based file

                        if (file instanceof NodeRefNetworkFile) {
                            NodeRefNetworkFile nodeNetFile = (NodeRefNetworkFile) file;
                            final NodeRef nodeRef = nodeNetFile.getNodeRef();

                            // We don't know how long the network file has had the reference, so check for existence

                            if (fileFolderService.exists(nodeRef)) {
                                try {
                                    boolean isVersionable = nodeService.hasAspect(nodeRef,
                                            ContentModel.ASPECT_VERSIONABLE);

                                    try {
                                        // Delete the file                                    
                                        FileState fileState = ctx.getStateCache().findFileState(file.getFullName());
                                        if (fileState != null
                                                && fileState.findAttribute(CanDeleteWithoutPerms) != null) {
                                            //Has CanDeleteWithoutPerms attribute, so delete from system user
                                            AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>() {
                                                @Override
                                                public Object doWork() throws Exception {
                                                    logger.debug("delete as system" + nodeRef);
                                                    fileFolderService.delete(nodeRef);
                                                    return null;
                                                }

                                            }, AuthenticationUtil.getSystemUserName());

                                        } else {
                                            logger.debug("delete nodeRef:" + nodeRef);
                                            fileFolderService.delete(nodeRef);
                                        }
                                    } catch (Exception ex) {
                                        logger.debug("on delete on close", ex);
                                        // Propagate retryable errors. Log the rest.
                                        if (RetryingTransactionHelper.extractRetryCause(ex) != null) {
                                            if (ex instanceof RuntimeException) {
                                                throw (RuntimeException) ex;
                                            } else {
                                                throw new AlfrescoRuntimeException(
                                                        "Error during delete on close, " + file.getFullName(), ex);
                                            }
                                        }
                                        if (logger.isWarnEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                                            logger.warn("Error during delete on close, " + file.getFullName(), ex);
                                    }

                                    // Return a node ref to update in the state table
                                    return new Pair<NodeRef, Boolean>(nodeRef, isVersionable);
                                } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) {
                                    // Debug

                                    if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                                        logger.debug("Delete on close - access denied, " + file.getFullName());

                                    // Convert to a filesystem access denied exception

                                    throw new AccessDeniedException("Delete on close " + file.getFullName());
                                }
                            }
                        }
                    }

                    return null;
                }
            });

            if (result != null) {
                // Check if there is a quota manager enabled, release space back to the user quota

                if (ctx.hasQuotaManager())
                    ctx.getQuotaManager().releaseSpace(sess, tree, file.getFileId(), file.getFullName(), fileSize);

                // Set the file state to indicate a delete on close

                if (ctx.hasStateCache()) {
                    if (result.getSecond()) {

                        // Get, or create, the file state

                        FileState fState = ctx.getStateCache().findFileState(file.getFullName(), true);

                        // Indicate that the file was deleted via a delete on close request

                        fState.setFileStatus(DeleteOnClose);

                        // Make sure the file state is cached for a short while, save the noderef details

                        fState.setExpiryTime(System.currentTimeMillis() + FileState.RenameTimeout);
                        fState.setFilesystemObject(result.getFirst());
                    } else {

                        // Remove the file state

                        ctx.getStateCache().removeFileState(file.getFullName());
                    }
                }
            } else if (file.hasDeleteOnClose()
                    && (file instanceof PseudoNetworkFile || file instanceof MemoryNetworkFile)
                    && hasPseudoFileInterface(ctx)) {
                // Delete the pseudo file

                getPseudoFileInterface(ctx).deletePseudoFile(sess, tree, file.getFullName());

            }

            // DEBUG

            if (logger.isDebugEnabled()
                    && (ctx.hasDebug(AlfrescoContext.DBG_FILE) || ctx.hasDebug(AlfrescoContext.DBG_RENAME))) {
                logger.debug("Closed file: network file=" + file + " delete on close=" + file.hasDeleteOnClose());
                if (file.hasDeleteOnClose() == false && file instanceof ContentNetworkFile) {
                    ContentNetworkFile cFile = (ContentNetworkFile) file;
                    logger.debug("  File " + file.getFullName() + ", version="
                            + nodeService.getProperty(cFile.getNodeRef(), ContentModel.PROP_VERSION_LABEL));
                }
            }
        }
        // Make sure we clean up before propagating exceptions
        catch (IOException e) {
            try {
                doInWriteTransaction(sess, errorHandler);
            } catch (Throwable t) {
                logger.error(t.getMessage(), t);
            }
            throw e;

        } catch (RuntimeException e) {
            try {
                doInWriteTransaction(sess, errorHandler);
            } catch (Throwable t) {
                logger.error(t.getMessage(), t);
            }
            throw e;
        } catch (Error e) {
            try {
                doInWriteTransaction(sess, errorHandler);
            } catch (Throwable t) {
                logger.error(t.getMessage(), t);
            }
            throw e;
        }
    }

    /**
     * Delete the specified file.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param name NetworkFile
     * @exception java.io.IOException The exception description.
     */
    public void deleteFile(final SrvSession sess, final TreeConnection tree, final String name) throws IOException {
        // Get the device context

        if (logger.isDebugEnabled()) {
            logger.debug("Delete file - " + name);
        }

        final ContentContext ctx = (ContentContext) tree.getContext();

        try {
            // Check if pseudo files are enabled

            if (hasPseudoFileInterface(ctx)) {
                // Check if the file name is a pseudo file name

                if (getPseudoFileInterface(ctx).isPseudoFile(sess, tree, name)) {

                    // Make sure the parent folder has a file state, and the path exists

                    String[] paths = FileName.splitPath(name);
                    FileState fstate = ctx.getStateCache().findFileState(paths[0]);

                    if (fstate != null) {

                        // Check if the path is to a pseudo file

                        PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile(sess, tree, name);
                        if (pfile != null) {
                            // Delete the pseudo file

                            getPseudoFileInterface(ctx).deletePseudoFile(sess, tree, name);
                            return;
                        }
                    }
                }
            }

            // Check if there is a quota manager enabled, if so then we need to save the current file size

            final QuotaManager quotaMgr = ctx.getQuotaManager();

            // Perform repository updates in a retryable write transaction
            Callable<Void> postTxn = doInWriteTransaction(sess, new CallableIO<Callable<Void>>() {
                public Callable<Void> call() throws IOException {
                    // Get the node and delete it
                    final NodeRef nodeRef = getNodeForPath(tree, name);

                    Callable<Void> result = null;
                    if (fileFolderService.exists(nodeRef)) {
                        // Get the size of the file being deleted

                        final FileInfo fInfo = quotaMgr == null ? null : getFileInformation(sess, tree, name);

                        // Check if the node is versionable

                        final boolean isVersionable = nodeService.hasAspect(nodeRef,
                                ContentModel.ASPECT_VERSIONABLE);

                        if (logger.isDebugEnabled()) {
                            logger.debug("deleted file" + name);
                        }
                        fileFolderService.delete(nodeRef);

                        // Return the operations to perform when the transaction succeeds

                        result = new Callable<Void>() {

                            public Void call() throws Exception {
                                // Remove the file state

                                if (ctx.hasStateCache()) {
                                    // Check if the node is versionable, cache the node details for a short while

                                    if (isVersionable == true) {

                                        // Make sure the file state is cached for a short while, a new file may be
                                        // renamed to the same name
                                        // in which case we can connect the file to the previous version history

                                        FileState delState = ctx.getStateCache().findFileState(name, true);

                                        if (logger.isDebugEnabled()) {
                                            logger.debug("set delete on close" + name);
                                        }
                                        delState.setExpiryTime(
                                                System.currentTimeMillis() + FileState.DeleteTimeout);
                                        delState.setFileStatus(DeleteOnClose);
                                        delState.setFilesystemObject(nodeRef);
                                    } else {

                                        // Remove the file state

                                        ctx.getStateCache().removeFileState(name);
                                    }

                                    // Update, or create, a parent folder file state

                                    String[] paths = FileName.splitPath(name);
                                    if (paths[0] != null && paths[0].length() > 1) {
                                        // Get the file state for the parent folder

                                        FileState parentState = getStateForPath(tree, paths[0]);
                                        if (parentState == null && ctx.hasStateCache())
                                            parentState = ctx.getStateCache().findFileState(paths[0], true);

                                        // Update the modification timestamp

                                        parentState.updateModifyDateTime();
                                    }
                                }

                                // Release the space back to the users quota

                                if (quotaMgr != null)
                                    quotaMgr.releaseSpace(sess, tree, fInfo.getFileId(), name, fInfo.getSize());

                                return null;
                            }
                        };
                    }

                    // Debug

                    if (logger.isDebugEnabled()
                            && (ctx.hasDebug(AlfrescoContext.DBG_FILE) || ctx.hasDebug(AlfrescoContext.DBG_RENAME)))
                        logger.debug("Deleted file: " + name + ", node=" + nodeRef);

                    return result;
                }
            });

            // Perform state updates after the transaction succeeds
            postTxn.call();
        } catch (NodeLockedException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Delete file - access denied (locked)");

            // Convert to a filesystem access denied status

            throw new AccessDeniedException("Delete " + name);
        } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Delete file - access denied");

            // Convert to a filesystem access denied status

            throw new AccessDeniedException("Delete " + name);
        } catch (IOException ex) {
            // Allow I/O Exceptions to pass through
            throw ex;
        } catch (Exception ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                logger.debug("Delete file error", ex);

            // Convert to a general I/O exception

            IOException ioe = new IOException("Delete file " + name);
            ioe.initCause(ex);
            throw ioe;
        }
    }

    /**
     * Rename the specified file.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param oldName java.lang.String
     * @param newName java.lang.String
     * @exception java.io.IOException The exception description.
     */
    public void renameFile(final SrvSession sess, final TreeConnection tree, final String oldName,
            final String newName) throws IOException {
        // Create the transaction (initially read-only)

        beginReadTransaction(sess);

        // Get the device context

        final ContentContext ctx = (ContentContext) tree.getContext();

        // DEBUG

        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
            logger.debug("Rename oldName=" + oldName + ", newName=" + newName);

        try {
            // Get the file/folder to move

            final NodeRef nodeToMoveRef = getNodeForPath(tree, oldName);

            // Check if the node is a link node

            if (nodeToMoveRef != null
                    && nodeService.getProperty(nodeToMoveRef, ContentModel.PROP_LINK_DESTINATION) != null)
                throw new AccessDeniedException("Cannot rename link nodes");

            // Get the new target folder - it must be a folder

            String[] splitPaths = FileName.splitPath(newName);
            String[] oldPaths = FileName.splitPath(oldName);

            final NodeRef targetFolderRef = getNodeForPath(tree, splitPaths[0]);
            final NodeRef sourceFolderRef = getNodeForPath(tree, oldPaths[0]);
            final String name = splitPaths[1];

            // Check if this is a rename within the same folder

            final boolean sameFolder = splitPaths[0].equalsIgnoreCase(oldPaths[0]);

            // Get the file state for the old file, if available

            final FileState oldState = ctx.getStateCache().findFileState(oldName, true);

            // Check if we are renaming a folder, or the rename is to a different folder

            boolean isFolder = cifsHelper.isDirectory(nodeToMoveRef);

            if (isFolder == true || sameFolder == false) {

                // Rename or move the file/folder

                doInWriteTransaction(sess, new CallableIO<Void>() {

                    public Void call() throws IOException {
                        if (sameFolder == true)
                            cifsHelper.rename(nodeToMoveRef, name);
                        else
                            cifsHelper.move(nodeToMoveRef, sourceFolderRef, targetFolderRef, name);
                        return null;
                    }
                });

                // Update the old file state

                if (oldState != null) {
                    // Update the file state index to use the new name

                    ctx.getStateCache().renameFileState(newName, oldState, isFolder);
                }

                // DEBUG

                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                    logger.debug("  Renamed " + (isFolder ? "folder" : "file") + " using "
                            + (sameFolder ? "rename" : "move"));
            } else {

                // Rename a file within the same folder
                //
                // Check if the target file already exists

                final int newExists = fileExists(sess, tree, newName);
                final FileState newState = ctx.getStateCache().findFileState(newName, true);

                List<Runnable> postTxn = doInWriteTransaction(sess, new CallableIO<List<Runnable>>() {

                    public List<Runnable> call() throws IOException {
                        List<Runnable> postTxn = new LinkedList<Runnable>();

                        NodeRef targetNodeRef = null;

                        boolean isFromVersionable = nodeService.hasAspect(nodeToMoveRef,
                                ContentModel.ASPECT_VERSIONABLE);
                        boolean typesCompatible = true;

                        // HACK ALF-3856: Version History lost when Versionable Content renamed via CIFS
                        //                This code will move into the repo layer (or just above it)
                        //                and this complexity removed from here.
                        //          Attempt to detect normal renames.  Hack alert!
                        Pattern renameShufflePattern = ctx.getRenameShufflePattern();
                        boolean renameShuffle = isRenameShuffle(oldName, newName, renameShufflePattern);
                        if (logger.isDebugEnabled()) {
                            logger.debug("Rename file: \n" + "   Old name:      " + oldName + "\n"
                                    + "   New name:      " + newName + "\n" + "   Pattern:       "
                                    + renameShufflePattern.pattern() + "\n" + "   Is shuffle:    " + renameShuffle
                                    + "\n" + "   Source folder: " + sourceFolderRef + "\n" + "   Target folder: "
                                    + targetFolderRef + "\n" + "   Node:          " + nodeToMoveRef + "\n"
                                    + "   Aspects:       " + nodeService.getAspects(nodeToMoveRef));

                        }

                        if (newExists == FileStatus.FileExists) {

                            // Use the existing file as the target node

                            targetNodeRef = getNodeForPath(tree, newName);
                        } else if (renameShuffle) {
                            logger.debug("is rename shuffle");
                            // Check if the target has a renamed or delete-on-close state

                            if (newState.getFileStatus() == FileRenamed) {
                                logger.debug("file status == FileRenamed");

                                // DEBUG

                                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                                    logger.debug("  Using renamed node, " + newState);

                                NodeRef newStateNode = (NodeRef) newState.getFilesystemObject();
                                QName oldType = nodeService.getType(nodeToMoveRef);
                                QName newType = nodeService.getType(newStateNode);
                                if (oldType.equals(newType)) {

                                    // Use the renamed node to clone aspects/state if it is of the correct type

                                    cloneNode(name, newStateNode, nodeToMoveRef, ctx);
                                } else {
                                    logger.debug("not renamed, must create new node");
                                    // Otherwise we must create a node of the correct type
                                    targetNodeRef = cifsHelper.createNode(ctx.getRootNode(), newName, newType);

                                    // Force a copy to this target
                                    typesCompatible = false;

                                    // DEBUG

                                    if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                                        logger.debug("  Created new node for " + newName + " type " + newType
                                                + ", isFromVersionable=false");

                                    // Copy aspects from the original state

                                    cloneNode(name, newStateNode, targetNodeRef, ctx);
                                }

                                //Change state for tmp node to allow delete it without special permission
                                String newStateNodeName = (String) nodeService.getProperty(newStateNode,
                                        ContentModel.PROP_NAME);
                                FileState stateForTmp = ctx.getStateCache().findFileState(
                                        newName.substring(0, newName.lastIndexOf("\\")) + "\\" + newStateNodeName,
                                        true);
                                stateForTmp.addAttribute(CanDeleteWithoutPerms, true);
                                stateForTmp.setFileStatus(FileStatus.FileExists);
                                stateForTmp.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout);
                                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                                    logger.debug("  Set CanDeleteWithoutPerms=true for " + stateForTmp);

                            } else if (newState.getFileStatus() == DeleteOnClose) {
                                logger.debug("file state is delete on close");

                                // DEBUG

                                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                                    logger.debug("  Restoring delete-on-close node, " + newState);

                                // Restore the deleted node so we can relink the new version to the old history/properties

                                NodeRef archivedNode = getNodeArchiveService()
                                        .getArchivedNode((NodeRef) newState.getFilesystemObject());

                                // DEBUG

                                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                                    logger.debug("  Found archived node " + archivedNode);

                                if (archivedNode != null && getNodeService().exists(archivedNode)) {
                                    // Restore the node

                                    targetNodeRef = getNodeService().restoreNode(archivedNode, null, null, null);

                                    // DEBUG

                                    if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                                        logger.debug("  Restored node " + targetNodeRef + ", version=" + nodeService
                                                .getProperty(targetNodeRef, ContentModel.PROP_VERSION_LABEL));

                                    // Check if the deleted file had a linked node, due to a rename

                                    NodeRef linkNode = (NodeRef) newState.findAttribute(AttrLinkNode);

                                    if (linkNode != null && nodeService.exists(linkNode)) {

                                        // Clone aspects from the linked node onto the restored node

                                        cloneNode(name, linkNode, targetNodeRef, ctx);

                                        // DEBUG

                                        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME)) {
                                            logger.debug("  Moved aspects from linked node " + linkNode);

                                            // Check if the node is a working copy

                                            NodeRef mainNodeRef = checkOutCheckInService
                                                    .getCheckedOut(targetNodeRef);
                                            if (mainNodeRef != null) {
                                                // Check if the main document is still locked
                                                LockType lockTyp = lockService.getLockType(mainNodeRef);
                                                logger.debug("  Main node ref lock type = " + lockTyp);
                                            }
                                        }
                                    }
                                }
                            }

                            // Check if the node being renamed is versionable

                            else if (isFromVersionable == true) {
                                logger.debug("from node is versionable");

                                // Create a new node for the target

                                targetNodeRef = cifsHelper.createNode(ctx.getRootNode(), newName,
                                        nodeService.getType(nodeToMoveRef));

                                // DEBUG

                                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                                    logger.debug("  Created new node for " + newName + ", isFromVersionable=true");

                                // Copy aspects from the original file

                                cloneNode(name, nodeToMoveRef, targetNodeRef, ctx);

                                //Change state for tmp node to allow delete it without special permission
                                FileState stateForTmp = ctx.getStateCache().findFileState(newName, true);
                                stateForTmp.addAttribute(CanDeleteWithoutPerms, true);
                                stateForTmp.setFileStatus(FileStatus.FileExists);
                                stateForTmp.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout);
                                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                                    logger.debug("  Set CanDeleteWithoutPerms=true for " + stateForTmp);
                            }
                        }

                        // If the original or target nodes are not versionable and types are compatible then just use a standard rename of the node
                        if (!renameShuffle
                                || (!isFromVersionable && typesCompatible && (targetNodeRef == null || nodeService
                                        .hasAspect(targetNodeRef, ContentModel.ASPECT_VERSIONABLE) == false))) {
                            logger.debug("do simple rename");

                            // Rename the file/folder

                            cifsHelper.rename(nodeToMoveRef, name);

                            postTxn.add(new Runnable() {
                                public void run() {
                                    // Mark the new file as existing

                                    newState.setFileStatus(FileExists);
                                    newState.setFilesystemObject(nodeToMoveRef);
                                    newState.setFileSize(oldState.getFileSize());

                                    // the date is updated to be properly saved when the document is closed, see MNT-214
                                    newState.updateModifyDateTime(oldState.getModifyDateTime());

                                    // Make sure the old file state is cached for a short while, the file may not be open so the
                                    // file state could be expired

                                    oldState.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout);

                                    // Indicate that this is a renamed file state, set the node ref of the file that was renamed

                                    oldState.setFileStatus(FileRenamed);
                                    oldState.setFilesystemObject(nodeToMoveRef);
                                }
                            });

                            // DEBUG

                            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                                logger.debug("  Use standard rename for " + name + "(versionable="
                                        + isFromVersionable + ", targetNodeRef=" + targetNodeRef + ")");
                        } else {

                            // Make sure we have a valid target node

                            if (targetNodeRef == null) {

                                // DEBUG

                                if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                                    logger.debug("  No target node for rename");

                                // Throw an error

                                throw new AccessDeniedException("No target node for file rename");
                            }

                            // Copy content data from the old file to the new file

                            copyContentData(sess, tree, nodeToMoveRef, targetNodeRef, newName);

                            final NodeRef finalTargetNodeRef = targetNodeRef;
                            postTxn.add(new Runnable() {

                                public void run() {
                                    // Mark the new file as existing

                                    newState.setFileStatus(FileExists);
                                    newState.setFilesystemObject(finalTargetNodeRef);
                                    newState.setFileSize(oldState.getFileSize());

                                    // the date is updated to be properly saved when the document is closed, see MNT-214
                                    newState.updateModifyDateTime(oldState.getModifyDateTime());

                                    // Make sure the old file state is cached for a short while, the file may not be open so the
                                    // file state could be expired

                                    oldState.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout);

                                    // Indicate that this is a deleted file state, set the node ref of the file that was renamed

                                    oldState.setFileStatus(DeleteOnClose);
                                    oldState.setFilesystemObject(nodeToMoveRef);

                                    // Link to the new node, a new file may be renamed into place, we need to transfer aspect/locks

                                    oldState.addAttribute(AttrLinkNode, finalTargetNodeRef);

                                    // DEBUG

                                    if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                                        logger.debug("  Cached delete state for " + oldName);
                                }
                            });

                            logger.debug("delete the old file");

                            // Delete the old file
                            if (renameShuffle && isFromVersionable && permissionService.hasPermission(nodeToMoveRef,
                                    PermissionService.EDITOR) == AccessStatus.ALLOWED) {
                                AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>() {
                                    @Override
                                    public Object doWork() throws Exception {
                                        if (logger.isDebugEnabled()) {
                                            logger.debug(
                                                    "Rename shuffle for versioning content is assumed. Deleting "
                                                            + nodeToMoveRef + " as system user");
                                        }
                                        if (renameCSVShufflePattern.matcher(newName.toLowerCase()).matches()) {
                                            Map<QName, Serializable> props = Collections.emptyMap();
                                            nodeService.addAspect(nodeToMoveRef, ContentModel.ASPECT_SOFT_DELETE,
                                                    props);
                                        } else {
                                            nodeService.deleteNode(nodeToMoveRef);
                                        }
                                        return null;
                                    }

                                }, AuthenticationUtil.getSystemUserName());
                            } else {
                                if (logger.isDebugEnabled()) {
                                    logger.debug("Deleting " + nodeToMoveRef + " as user: "
                                            + AuthenticationUtil.getFullyAuthenticatedUser());
                                }
                                nodeService.deleteNode(nodeToMoveRef);
                            }

                        }

                        return postTxn;
                    }
                });

                logger.debug("running post txns");
                // Run the required state-changing logic once the retrying transaction has completed successfully
                for (Runnable runnable : postTxn) {
                    runnable.run();
                }
            }
        } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                logger.debug("Rename file - access denied, " + oldName);

            // Convert to a filesystem access denied status

            throw new AccessDeniedException("Rename file " + oldName);
        } catch (NodeLockedException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                logger.debug("Rename file", ex);

            // Convert to an filesystem access denied exception

            throw new AccessDeniedException("Node locked " + oldName);
        } catch (InvalidNodeRefException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                logger.debug("Rename file - file doesn't exist, " + oldName, ex);

            // Convert to a filesystem access denied status

            throw new FileNotFoundException("File doesn't exist " + oldName);
        } catch (RuntimeException ex) {
            // Unexpected Exception being consumed here - hence the error logging.
            logger.error("Unable to rename file" + oldName, ex);

            // Convert to a general I/O exception

            throw new AccessDeniedException("Rename file " + oldName);
        }
    }

    /**
     * Set file information
     * 
     * @param sess SrvSession
     * @param tree TreeConnection
     * @param name String
     * @param info FileInfo
     * @exception IOException
     */
    public void setFileInformation(SrvSession sess, final TreeConnection tree, final String name,
            final FileInfo info) throws IOException {
        // Get the device context

        final ContentContext ctx = (ContentContext) tree.getContext();

        try {
            // Check if pseudo files are enabled

            if (hasPseudoFileInterface(ctx) && getPseudoFileInterface(ctx).isPseudoFile(sess, tree, name)) {
                // Allow the file information to be changed

                return;
            }

            final FileState fstate = getStateForPath(tree, name);

            doInWriteTransaction(sess, new CallableIO<Pair<Boolean, Boolean>>() {

                public Pair<Boolean, Boolean> call() throws IOException {
                    // Get the file/folder node

                    NodeRef nodeRef = getNodeForPath(tree, name);

                    // Check permissions on the file/folder node

                    if (permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED) {
                        throw new AccessDeniedException("No write access to " + name);
                    }

                    // Inhibit versioning for this transaction

                    getPolicyFilter().disableBehaviour(ContentModel.ASPECT_VERSIONABLE);

                    // Check if the file is being marked for deletion, if so then check if the file is locked

                    if (info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()) {
                        // Check deleting permissions on the node if action of deleting was configured only
                        if ((AccessStatus.DENIED == permissionService.hasPermission(nodeRef,
                                PermissionService.DELETE))
                                && ((null == fstate) || (null == fstate.findAttribute(CanDeleteWithoutPerms)))) {
                            throw new org.alfresco.repo.security.permissions.AccessDeniedException(
                                    "No delete access to " + name);
                        }

                        // Check if the node is locked

                        if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE)) {
                            // Get the lock type, if any

                            String lockTypeStr = (String) nodeService.getProperty(nodeRef,
                                    ContentModel.PROP_LOCK_TYPE);

                            if (lockTypeStr != null)
                                throw new org.alfresco.repo.security.permissions.AccessDeniedException(
                                        "Node locked, cannot mark for delete");
                        }

                        // Get the node for the folder

                        if (fileFolderService.exists(nodeRef)) {
                            // Check if it is a folder that is being deleted, make sure it is empty

                            boolean isFolder = true;

                            if (fstate != null)
                                isFolder = fstate.isDirectory();
                            else {
                                ContentFileInfo cInfo = cifsHelper.getFileInformation(nodeRef, isReadOnly,
                                        isLockedFilesAsOffline);
                                if (cInfo != null && cInfo.isDirectory() == false)
                                    isFolder = false;
                            }

                            // Check if the folder is empty

                            if (isFolder == true && cifsHelper.isFolderEmpty(nodeRef) == false)
                                throw new DirectoryNotEmptyException(name);
                        }

                        // DEBUG

                        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                            logger.debug("Set deleteOnClose=true file=" + name);
                    }

                    // Set the creation and modified date/time
                    Map<QName, Serializable> auditableProps = new HashMap<QName, Serializable>(5);

                    if (info.hasSetFlag(FileInfo.SetCreationDate)) {
                        // Set the creation date on the file/folder node
                        Date createDate = new Date(info.getCreationDateTime());
                        auditableProps.put(ContentModel.PROP_CREATED, createDate);
                    }
                    if (info.hasSetFlag(FileInfo.SetModifyDate)) {

                        // Set the modification date on the file/folder node
                        Date modifyDate = new Date(info.getModifyDateTime());
                        auditableProps.put(ContentModel.PROP_MODIFIED, modifyDate);
                    }

                    // Did we have any cm:auditable properties?
                    if (auditableProps.size() > 0) {
                        getPolicyFilter().disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
                        nodeService.addProperties(nodeRef, auditableProps);

                        // DEBUG
                        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                            logger.debug("Set auditable props: " + auditableProps + " file=" + name);
                    }

                    return null;
                }
            });
            // Check if the file is being marked for deletion, if so then check if the file is locked

            // Update the change date/time
            if (fstate != null) {
                if (info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()
                        || info.hasSetFlag(FileInfo.SetCreationDate)) {

                    fstate.updateChangeDateTime();
                }

                // Set the modification date/time

                if (info.hasSetFlag(FileInfo.SetModifyDate)) {
                    // Update the change date/time, clear the cached modification date/time
                    fstate.updateChangeDateTime();
                    Date modifyDate = new Date(info.getModifyDateTime());
                    fstate.updateModifyDateTime(modifyDate.getTime());
                }
            }
        } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                logger.debug("Set file information - access denied, " + name, ex);

            // Convert to a filesystem access denied status

            throw new AccessDeniedException("Set file information " + name);
        } catch (RuntimeException ex) {
            // Debug

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                logger.debug("Open file error", ex);

            // Convert to a general I/O exception

            throw new IOException("Set file information " + name);
        }
    }

    /**
     * Truncate a file to the specified size
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param file Network file details
     * @param size New file length
     * @exception java.io.IOException The exception description.
     */
    public void truncateFile(SrvSession sess, TreeConnection tree, NetworkFile file, long size) throws IOException {
        //  Keep track of the allocation/release size in case the file resize fails

        ContentContext ctx = (ContentContext) tree.getContext();

        long allocSize = 0L;
        long releaseSize = 0L;

        //  Check if there is a quota manager

        QuotaManager quotaMgr = ctx.getQuotaManager();

        if (ctx.hasQuotaManager()) {

            // Check if the file content has been opened, we need the content to be opened to get the
            // current file size

            if (file instanceof ContentNetworkFile) {
                ContentNetworkFile contentFile = (ContentNetworkFile) file;
                if (contentFile.hasContent() == false)
                    contentFile.openContent(false, false);
            } else
                throw new IOException("Invalid file class type, " + file.getClass().getName());

            //  Determine if the new file size will release space or require space allocating

            if (size > file.getFileSize()) {

                //  Calculate the space to be allocated

                allocSize = size - file.getFileSize();

                //  Allocate space to extend the file

                quotaMgr.allocateSpace(sess, tree, file, allocSize);
            } else {

                //  Calculate the space to be released as the file is to be truncated, release the space if
                //  the file truncation is successful

                releaseSize = file.getFileSize() - size;
            }
        }

        //  Check if this is a file extend, update the cached allocation size if necessary

        if (file instanceof ContentNetworkFile) {

            // Get the cached state for the file

            ContentNetworkFile contentFile = (ContentNetworkFile) file;
            FileState fstate = contentFile.getFileState();
            if (fstate != null && size > fstate.getAllocationSize())
                fstate.setAllocationSize(size);
        }

        //  Set the file length

        try {
            file.truncateFile(size);
        } catch (IOException ex) {

            //  Check if we allocated space to the file

            if (allocSize > 0 && quotaMgr != null)
                quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, allocSize);

            //  Rethrow the exception

            throw ex;
        }

        //  Check if space has been released by the file resizing

        if (releaseSize > 0 && quotaMgr != null)
            quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, releaseSize);

        // Debug

        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO))
            logger.debug("Truncated file: network file=" + file + " size=" + size);
    }

    /**
     * Read a block of data from the specified file.
     * 
     * @param sess Session details
     * @param tree Tree connection
     * @param file Network file
     * @param buffer Buffer to return data to
     * @param bufferPosition Starting position in the return buffer
     * @param size Maximum size of data to return
     * @param fileOffset File offset to read data
     * @return Number of bytes read
     * @exception java.io.IOException The exception description.
     */
    public int readFile(SrvSession sess, TreeConnection tree, NetworkFile file, byte[] buffer, int bufferPosition,
            int size, long fileOffset) throws IOException {
        // Check if the file is a directory

        if (file.isDirectory())
            throw new AccessDeniedException();

        // If the content channel is not open for the file then start a transaction

        if (file instanceof ContentNetworkFile) {
            ContentNetworkFile contentFile = (ContentNetworkFile) file;

            if (contentFile.hasContent() == false)
                beginReadTransaction(sess);
        }

        // Read a block of data from the file

        int count = file.readFile(buffer, size, bufferPosition, fileOffset);

        if (count == -1) {
            // Read count of -1 indicates a read past the end of file

            count = 0;
        }

        // Debug

        ContentContext ctx = (ContentContext) tree.getContext();

        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO))
            logger.debug("Read bytes from file: network file=" + file + " buffer size=" + buffer.length
                    + " buffer pos=" + bufferPosition + " size=" + size + " file offset=" + fileOffset
                    + " bytes read=" + count);

        return count;
    }

    /**
     * Seek to the specified file position.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param file Network file.
     * @param pos Position to seek to.
     * @param typ Seek type.
     * @return New file position, relative to the start of file.
     */
    public long seekFile(SrvSession sess, TreeConnection tree, NetworkFile file, long pos, int typ)
            throws IOException {
        // Check if the file is a directory

        if (file.isDirectory())
            throw new AccessDeniedException();

        // If the content channel is not open for the file then start a transaction

        ContentNetworkFile contentFile = (ContentNetworkFile) file;

        if (contentFile.hasContent() == false)
            beginReadTransaction(sess);

        // Set the file position

        return file.seekFile(pos, typ);
    }

    /**
     * Write a block of data to the file.
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param file Network file details
     * @param buffer byte[] Data to be written
     * @param bufferOffset Offset within the buffer that the data starts
     * @param size int Data length
     * @param fileOffset Position within the file that the data is to be written.
     * @return Number of bytes actually written
     * @exception java.io.IOException The exception description.
     */
    public int writeFile(SrvSession sess, TreeConnection tree, NetworkFile file, byte[] buffer, int bufferOffset,
            int size, long fileOffset) throws IOException {
        // If the content channel is not open for the file then start a transaction

        if (file instanceof ContentNetworkFile) {
            ContentNetworkFile contentFile = (ContentNetworkFile) file;

            if (contentFile.hasContent() == false)
                beginReadTransaction(sess);
        }

        //  Check if there is a quota manager

        ContentContext ctx = (ContentContext) tree.getContext();
        QuotaManager quotaMgr = ctx.getQuotaManager();
        long curSize = file.getFileSize();

        if (quotaMgr != null) {

            //  Check if the file requires extending

            long extendSize = 0L;
            long endOfWrite = fileOffset + size;

            if (endOfWrite > curSize) {

                //  Calculate the amount the file must be extended

                extendSize = endOfWrite - file.getFileSize();

                //  Allocate space for the file extend

                quotaMgr.allocateSpace(sess, tree, file, extendSize);
            }
        }

        // Write to the file

        file.writeFile(buffer, size, bufferOffset, fileOffset);

        // Check if the file size was reduced by the write, may have been extended previously

        if (quotaMgr != null) {

            // Check if the file size reduced

            if (file.getFileSize() < curSize) {

                // Release space that was freed by the write

                quotaMgr.releaseSpace(sess, tree, file.getFileId(), file.getFullName(),
                        curSize - file.getFileSize());
            }
        }

        // Debug

        if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO))
            logger.debug("Wrote bytes to file: network file=" + file + " buffer size=" + buffer.length + " size="
                    + size + " file offset=" + fileOffset);

        return size;
    }

    /**
     * Get the node for the specified path
     * 
     * @param tree TreeConnection
     * @param path String
     * @return NodeRef
     * @exception FileNotFoundException
     */
    public NodeRef getNodeForPath(TreeConnection tree, String path) throws FileNotFoundException {
        // Check if there is a cached state for the path

        ContentContext ctx = (ContentContext) tree.getContext();

        NodeRef result = null;
        if (ctx.hasStateCache()) {
            // Try and get the node ref from an in memory file state
            FileState fstate = ctx.getStateCache().findFileState(path);
            if (null != (result = getNodeOrNull(path, ctx, fstate))) {
                return result;
            }
        }

        // Search the repository for the node

        return cifsHelper.getNodeRef(ctx.getRootNode(), path);
    }

    /**
     * Determines current existence state of the file object from the cache.
     * Updates state of the object in the cache according to existence state: increasing expiration time if
     * object exists or removing object from the cache if it does not exist
     * 
     * @param path - {@link String} value which contains relative path to the file object
     * @param ctx - {@link ContentContext} instance of the current {@link TreeConnection}
     * @param fstate - {@link FileState} instance which represents current state of the cached object
     * @return {@link NodeRef} instance of existent file object or <code>null</code> if object is not exist
     */
    private NodeRef getNodeOrNull(String path, ContentContext ctx, FileState fstate) {
        if ((null != fstate) && fstate.hasFilesystemObject() && fstate.exists()) {
            // Check that the node exists
            if (fileFolderService.exists((NodeRef) fstate.getFilesystemObject())) {
                // Bump the file states expiry time
                fstate.setExpiryTime(System.currentTimeMillis() + FileState.DefTimeout);

                // Return the cached noderef
                return (NodeRef) fstate.getFilesystemObject();
            } else {
                ctx.getStateCache().removeFileState(path);
            }
        }

        return null;
    }

    /**
     * Convert a node into a share relative path
     */
    public String getPathForNode(TreeConnection tree, NodeRef nodeRef) throws FileNotFoundException {
        // Convert the target node to a path

        ContentContext ctx = (ContentContext) tree.getContext();
        List<org.alfresco.service.cmr.model.FileInfo> linkPaths = null;

        try {
            linkPaths = fileFolderService.getNamePath(ctx.getRootNode(), nodeRef);
        } catch (org.alfresco.service.cmr.model.FileNotFoundException ex) {
            throw new FileNotFoundException();
        }

        // Build the share relative path to the node

        StringBuilder pathStr = new StringBuilder();

        for (org.alfresco.service.cmr.model.FileInfo fInfo : linkPaths) {
            pathStr.append(FileName.DOS_SEPERATOR);
            pathStr.append(fInfo.getName());
        }

        // Return the share relative path

        return pathStr.toString();
    }

    /**
     * Get the file state for the specified path
     * 
     * @param tree TreeConnection
     * @param path String
     * @return FileState
     * @exception FileNotFoundException
     */
    public FileState getStateForPath(TreeConnection tree, String path) throws FileNotFoundException {
        // Check if there is a cached state for the path

        ContentContext ctx = (ContentContext) tree.getContext();
        FileState fstate = null;

        if (ctx.hasStateCache()) {
            // Get the file state for a file/folder

            fstate = ctx.getStateCache().findFileState(path);
        }

        // Return the file state

        return fstate;
    }

    /**
     * Connection opened to this disk device
     * 
     * @param sess Server session
     * @param tree Tree connection
     */
    public void treeClosed(SrvSession sess, TreeConnection tree) {
        // Nothing to do
    }

    /**
     * Connection closed to this device
     * 
     * @param sess Server session
     * @param tree Tree connection
     */
    public void treeOpened(SrvSession sess, TreeConnection tree) {
        // Nothing to do
    }

    /**
     * Return the lock manager used by this filesystem
     * 
     * @param sess SrvSession
     * @param tree TreeConnection
     * @return LockManager
     */
    public LockManager getLockManager(SrvSession sess, TreeConnection tree) {
        ContentContext ctx = (ContentContext) tree.getContext();
        return ctx.getLockManager();
    }

    /**
     * Return the oplock manager implementation associated with this virtual filesystem
     * 
     * @param sess SrvSession
     * @param tree TreeConnection
     * @return OpLockManager
     */
    public OpLockManager getOpLockManager(SrvSession sess, TreeConnection tree) {
        ContentContext ctx = (ContentContext) tree.getContext();
        return ctx.getLockManager();
    }

    /**
     * Enable/disable oplock support
     * 
     * @param sess SrvSession
     * @param tree TreeConnection
     * @return boolean
     */
    public boolean isOpLocksEnabled(SrvSession sess, TreeConnection tree) {

        // Check if oplocks are enabled

        ContentContext ctx = (ContentContext) tree.getContext();
        return ctx.getDisableOplocks() ? false : true;
    }

    /**
     * Copy content data from file to file
     * 
     * @param sess SrvSession
     * @param tree TreeConnection
     * @param fromNode NodeRef
     * @param toNode NodeRef
     * @param newName String
     */
    private void copyContentData(SrvSession sess, TreeConnection tree, NodeRef fromNode, NodeRef toNode,
            String newName) {
        ContentData content = (ContentData) nodeService.getProperty(fromNode, ContentModel.PROP_CONTENT);
        if (newName != null)
            content = ContentData.setMimetype(content, mimetypeService.guessMimetype(newName));
        nodeService.setProperty(toNode, ContentModel.PROP_CONTENT, content);
    }

    /**
     * Clone/move aspects/properties between nodes
     * 
     * @param newName new name of the file
     * @param fromNode NodeRef the node to copy from
     * @param toNode NodeRef the node to copy to
     * @param ctx Filesystem Context
     */
    private void cloneNodeAspects(String newName, NodeRef fromNode, NodeRef toNode, ContentContext ctx) {
        // We need to remove various aspects/properties from the original file, and move them to the new file
        //
        // Check for the lockable aspect

        if (nodeService.hasAspect(fromNode, ContentModel.ASPECT_LOCKABLE)) {

            // Remove the lockable aspect from the old working copy, add it to the new file

            nodeService.removeAspect(fromNode, ContentModel.ASPECT_LOCKABLE);
            nodeService.addAspect(toNode, ContentModel.ASPECT_LOCKABLE, null);

            // DEBUG

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                logger.debug("  Moved aspect " + ContentModel.ASPECT_LOCKABLE + " to new document");
        }

        // Check for the working copy aspect

        if (nodeService.hasAspect(fromNode, ContentModel.ASPECT_WORKING_COPY)) {

            // Add the working copy aspect to the new file

            Map<QName, Serializable> workingCopyProperties = new HashMap<QName, Serializable>(1);
            workingCopyProperties.put(ContentModel.PROP_WORKING_COPY_OWNER,
                    nodeService.getProperty(fromNode, ContentModel.PROP_WORKING_COPY_OWNER));

            nodeService.addAspect(toNode, ContentModel.ASPECT_WORKING_COPY, workingCopyProperties);

            // Remove the working copy aspect from old working copy file 

            nodeService.removeAspect(fromNode, ContentModel.ASPECT_WORKING_COPY);

            // DEBUG

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                logger.debug("  Moved aspect " + ContentModel.ASPECT_WORKING_COPY + " to new document");
        }

        // Check for the copied from aspect

        if (nodeService.hasAspect(fromNode, ContentModel.ASPECT_COPIEDFROM)) {

            // Add the copied from aspect to the new file
            List<AssociationRef> assocs = nodeService.getSourceAssocs(fromNode, ContentModel.ASSOC_ORIGINAL);
            if (assocs.size() > 0) {
                AssociationRef assoc = assocs.get(0);
                NodeRef originalNodeRef = assoc.getTargetRef();
                nodeService.createAssociation(toNode, originalNodeRef, ContentModel.ASSOC_ORIGINAL);
            }

            // Remove the copied from aspect from old working copy file 

            nodeService.removeAspect(fromNode, ContentModel.ASPECT_COPIEDFROM);

            // DEBUG

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                logger.debug("  Moved aspect " + ContentModel.ASPECT_COPIEDFROM + " to new document");

            //            // Check if the original node is locked
            //            
            //            if ( lockService.getLockType( copiedFromNode) == null) {
            //                
            //                // Add the lock back onto the original file
            //                
            //                lockService.lock( copiedFromNode, LockType.READ_ONLY_LOCK);
            //
            //                // DEBUG
            //                
            //                if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
            //                    logger.debug("  Re-locked copied from node " + copiedFromNode);
            //            }
        }

        // Copy over all aspects from non-system namespaces (we will copy their properties later)

        for (QName aspectName : nodeService.getAspects(fromNode)) {
            if (!_excludedNamespaces.contains(aspectName.getNamespaceURI())) {
                nodeService.addAspect(toNode, aspectName, null);
            }
        }

        // Copy over all other properties from non system namespaces
        Map<QName, Serializable> fromProps = nodeService.getProperties(fromNode);
        for (Map.Entry<QName, Serializable> entry : fromProps.entrySet()) {
            QName propName = entry.getKey();
            if (!_excludedNamespaces.contains(propName.getNamespaceURI())) {
                nodeService.setProperty(toNode, propName, entry.getValue());
            }
        }

        // Check if the new file name is a temporary file, remove any versionable aspect from it

        String newNameNorm = newName.toLowerCase();

        if (newNameNorm.endsWith(".tmp") || newNameNorm.endsWith(".temp")) {

            // Remove the versionable aspect

            if (nodeService.hasAspect(toNode, ContentModel.ASPECT_VERSIONABLE))
                nodeService.removeAspect(toNode, ContentModel.ASPECT_VERSIONABLE);

            // Add the temporary aspect, also prevents versioning

            nodeService.addAspect(toNode, ContentModel.ASPECT_TEMPORARY, null);

            // DEBUG

            if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
                logger.debug("  Removed versionable aspect from temp file");
        }

        // Copy over various properties

        for (QName propName : _copyProperties) {
            Serializable nodeProp = nodeService.getProperty(fromNode, propName);
            if (nodeProp != null)
                nodeService.setProperty(toNode, propName, nodeProp);
        }

    }

    /**
     * Clone node
     * 
     * @param newName the new name of the node
     * @param fromNode the node to copy from
     * @param toNode the node to copy to
     * @param ctx
     */
    private void cloneNode(String newName, NodeRef fromNode, NodeRef toNode, ContentContext ctx) {
        if (logger.isDebugEnabled()) {
            logger.debug("clone node from fromNode:" + fromNode + "toNode:" + toNode);
        }
        cloneNodeAspects(newName, fromNode, toNode, ctx);

        // copy over the node creator and owner properties
        // need to disable the auditable aspect first to prevent default audit behaviour
        policyBehaviourFilter.disableBehaviour(ContentModel.ASPECT_AUDITABLE);
        try {
            nodeService.setProperty(toNode, ContentModel.PROP_CREATOR,
                    nodeService.getProperty(fromNode, ContentModel.PROP_CREATOR));
            ownableService.setOwner(toNode, ownableService.getOwner(fromNode));
        } finally {
            policyBehaviourFilter.enableBehaviour(ContentModel.ASPECT_AUDITABLE);
        }

        Set<AccessPermission> permissions = permissionService.getAllSetPermissions(fromNode);
        boolean inheritParentPermissions = permissionService.getInheritParentPermissions(fromNode);
        permissionService.deletePermissions(fromNode);

        permissionService.setInheritParentPermissions(toNode, inheritParentPermissions);
        for (AccessPermission permission : permissions) {
            permissionService.setPermission(toNode, permission.getAuthority(), permission.getPermission(),
                    (permission.getAccessStatus() == AccessStatus.ALLOWED));
        }

        // Need to take a new guess at the mimetype based upon the new file name.
        ContentData content = (ContentData) nodeService.getProperty(toNode, ContentModel.PROP_CONTENT);

        // Take a guess at the mimetype (if it has not been set by something already)
        if (content != null
                && (content.getMimetype() == null || content.getMimetype().equals(MimetypeMap.MIMETYPE_BINARY))) {
            String mimetype = mimetypeService.guessMimetype(newName);
            if (logger.isDebugEnabled()) {
                logger.debug("set new mimetype to:" + mimetype);
            }
            ContentData replacement = ContentData.setMimetype(content, mimetype);
            nodeService.setProperty(toNode, ContentModel.PROP_CONTENT, replacement);
        }

        // Extract metadata pending change for ALF-5082
        Action action = getActionService().createAction(ContentMetadataExtracter.EXECUTOR_NAME);
        if (action != null) {
            getActionService().executeAction(action, toNode);
        }
    }

    /**
     * Return the file state status as a string
     * 
     * @param sts int
     * @return String
     */
    private final String fileStatusString(int sts) {
        String fstatus = "Unknown";

        switch (sts) {
        case FileUnknown:
            fstatus = "Unknown";
            break;
        case FileNotExist:
            fstatus = "NotExist";
            break;
        case FileExists:
            fstatus = "FileExists";
            break;
        case DirectoryExists:
            fstatus = "DirectoryExists";
            break;
        case FileRenamed:
            fstatus = "FileRenamed";
            break;
        case DeleteOnClose:
            fstatus = "DeleteOnClose";
            break;
        }

        return fstatus;
    }

    private boolean isRenameShuffle(String oldFilename, String newFilename, Pattern renameShufflePattern) {
        boolean renameShuffle = false;
        String oldNameLower = oldFilename.toLowerCase();
        String newNameLower = newFilename.toLowerCase();
        renameShuffle = renameShuffle || renameShufflePattern.matcher(oldNameLower).matches();
        renameShuffle = renameShuffle || renameShufflePattern.matcher(newNameLower).matches();

        return renameShuffle;
    }

    /**
     * Get the disk information for this shared disk device.
     *
     * @param ctx       DiskDeviceContext
     * @param diskDev   SrvDiskInfo
     * @exception IOException
     */
    public void getDiskInformation(DiskDeviceContext ctx, SrvDiskInfo diskDev) throws IOException {

        // Set the block size and blocks per allocation unit

        diskDev.setBlockSize(DiskBlockSize);
        diskDev.setBlocksPerAllocationUnit(DiskBlocksPerUnit);

        // Get the free and total disk size in bytes from the content store

        long freeSpace = contentService.getStoreFreeSpace();
        long totalSpace = contentService.getStoreTotalSpace();

        if (totalSpace == -1L) {

            // Use a fixed value for the total space, content store does not support size information

            totalSpace = DiskSizeDefault;
            freeSpace = DiskFreeDefault;
        }

        // Convert the total/free space values to allocation units

        diskDev.setTotalUnits(totalSpace / DiskAllocationUnit);
        diskDev.setFreeUnits(freeSpace / DiskAllocationUnit);
    }

    public void setActionService(ActionService actionService) {
        this.actionService = actionService;
    }

    public ActionService getActionService() {
        return actionService;
    }
}