/*
 * #%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.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);
            }
        }
    }
    
    /**
     * 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;

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

                    // 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());
            }

            // 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, 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, paths[0], ctx.getStateCache(), isLockedFilesAsOffline);
            	else
                	searchCtx = new ContentSearchContext(cifsHelper, results, searchFileSpec, 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
            {
                // 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
        {
            // 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 the 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());
                    }
                }
            }
            
            // 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 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
        {
            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;
    }
}
