gt:             if ( ctx.hasStateCache())
context: /*
   * Copyright (C) 2005-2010 Alfresco Software Limited.
   *
   * This file is part of Alfresco
   *
   * 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/>.
   */
  package org.alfresco.filesys.repo;
  
  import java.io.FileNotFoundException;
  import java.io.IOException;
  import java.io.Serializable;
  import java.net.InetAddress;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.TreeSet;
  import java.util.concurrent.Callable;
  import java.util.regex.Pattern;
  
  import javax.transaction.UserTransaction;
  
  import org.alfresco.error.AlfrescoRuntimeException;
  import org.alfresco.filesys.alfresco.AlfrescoContext;
  import org.alfresco.filesys.alfresco.AlfrescoNetworkFile;
  import org.alfresco.filesys.alfresco.AlfrescoTxDiskDriver;
  import org.alfresco.jlan.server.SrvSession;
  import org.alfresco.jlan.server.core.DeviceContext;
  import org.alfresco.jlan.server.core.DeviceContextException;
  import org.alfresco.jlan.server.filesys.AccessDeniedException;
  import org.alfresco.jlan.server.filesys.AccessMode;
  import org.alfresco.jlan.server.filesys.DirectoryNotEmptyException;
  import org.alfresco.jlan.server.filesys.DiskDeviceContext;
  import org.alfresco.jlan.server.filesys.DiskFullException;
  import org.alfresco.jlan.server.filesys.DiskInterface;
  import org.alfresco.jlan.server.filesys.DiskSizeInterface;
  import org.alfresco.jlan.server.filesys.FileAttribute;
  import org.alfresco.jlan.server.filesys.FileExistsException;
  import org.alfresco.jlan.server.filesys.FileInfo;
  import org.alfresco.jlan.server.filesys.FileName;
  import org.alfresco.jlan.server.filesys.FileOpenParams;
  import org.alfresco.jlan.server.filesys.FileSharingException;
  import org.alfresco.jlan.server.filesys.FileStatus;
  import org.alfresco.jlan.server.filesys.NetworkFile;
  import org.alfresco.jlan.server.filesys.SearchContext;
  import org.alfresco.jlan.server.filesys.SrvDiskInfo;
  import org.alfresco.jlan.server.filesys.TreeConnection;
  import org.alfresco.jlan.server.filesys.cache.FileState;
  import org.alfresco.jlan.server.filesys.pseudo.MemoryNetworkFile;
  import org.alfresco.jlan.server.filesys.pseudo.PseudoFile;
  import org.alfresco.jlan.server.filesys.pseudo.PseudoFileInterface;
  import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList;
  import org.alfresco.jlan.server.filesys.pseudo.PseudoNetworkFile;
  import org.alfresco.jlan.server.filesys.quota.QuotaManager;
  import org.alfresco.jlan.server.filesys.quota.QuotaManagerException;
  import org.alfresco.jlan.server.locking.FileLockingInterface;
  import org.alfresco.jlan.server.locking.LockManager;
  import org.alfresco.jlan.server.locking.OpLockInterface;
  import org.alfresco.jlan.server.locking.OpLockManager;
  import org.alfresco.jlan.smb.SharingMode;
  import org.alfresco.jlan.smb.WinNT;
  import org.alfresco.jlan.smb.server.SMBServer;
  import org.alfresco.jlan.smb.server.SMBSrvSession;
  import org.alfresco.jlan.util.MemorySize;
  import org.alfresco.jlan.util.WildCard;
  import org.alfresco.model.ContentModel;
  import org.alfresco.repo.action.executer.ContentMetadataExtracter;
  import org.alfresco.repo.admin.SysAdminParams;
  import org.alfresco.repo.content.MimetypeMap;
  import org.alfresco.repo.node.archive.NodeArchiveService;
  import org.alfresco.repo.policy.BehaviourFilter;
  import org.alfresco.repo.security.authentication.AuthenticationContext;
  import org.alfresco.repo.security.authentication.AuthenticationUtil;
  import org.alfresco.repo.transaction.RetryingTransactionHelper;
  import org.alfresco.service.cmr.action.Action;
  import org.alfresco.service.cmr.action.ActionService;
  import org.alfresco.service.cmr.coci.CheckOutCheckInService;
  import org.alfresco.service.cmr.dictionary.DictionaryService;
  import org.alfresco.service.cmr.lock.LockService;
  import org.alfresco.service.cmr.lock.LockType;
  import org.alfresco.service.cmr.lock.NodeLockedException;
  import org.alfresco.service.cmr.model.FileFolderService;
  import org.alfresco.service.cmr.repository.AssociationRef;
  import org.alfresco.service.cmr.repository.ContentData;
  import org.alfresco.service.cmr.repository.ContentIOException;
  import org.alfresco.service.cmr.repository.ContentService;
  import org.alfresco.service.cmr.repository.InvalidNodeRefException;
  import org.alfresco.service.cmr.repository.MimetypeService;
  import org.alfresco.service.cmr.repository.NodeRef;
  import org.alfresco.service.cmr.repository.NodeService;
  import org.alfresco.service.cmr.repository.StoreRef;
  import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
  import org.alfresco.service.cmr.search.SearchService;
  import org.alfresco.service.cmr.security.AccessPermission;
  import org.alfresco.service.cmr.security.AccessStatus;
  import org.alfresco.service.cmr.security.AuthenticationService;
  import org.alfresco.service.cmr.security.OwnableService;
  import org.alfresco.service.cmr.security.PermissionService;
  import org.alfresco.service.namespace.NamespaceService;
  import org.alfresco.service.namespace.QName;
  import org.alfresco.util.Pair;
  import org.alfresco.util.PropertyCheck;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  import org.springframework.extensions.config.ConfigElement;
  
  /**
   * Content repository filesystem driver class
   * 
   * <p>Provides a filesystem interface for various protocols such as SMB/CIFS and FTP.
   * 
   * @author gkspencer
   */
  public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInterface, FileLockingInterface, OpLockInterface, DiskSizeInterface
  {
      // Logging
      private static final Log logger = LogFactory.getLog(ContentDiskDriver.class);
      
      // Configuration key names
      
      private static final String KEY_STORE = "store";
      private static final String KEY_ROOT_PATH = "rootPath";
      private static final String KEY_RELATIVE_PATH = "relativePath";
      
      // File status values used in the file state cache
      
      public static final int FileUnknown     = FileStatus.Unknown;
      public static final int FileNotExist    = FileStatus.NotExist;
      public static final int FileExists      = FileStatus.FileExists;
      public static final int DirectoryExists = FileStatus.DirectoryExists;
      
      public static final int CustomFileStatus= FileStatus.MaxStatus + 1;
      public static final int FileRenamed     = CustomFileStatus;
      public static final int DeleteOnClose   = CustomFileStatus + 1;
      
      // File state attributes
      
      public static final String AttrLinkNode = "ContentLinkNode";
      public static final String CanDeleteWithoutPerms = "CanDeleteWithoutPerms";
          
      // List of content properties to copy during rename
      
      private static QName[] _copyProperties = { ContentModel.PROP_AUTHOR, ContentModel.PROP_TITLE, ContentModel.PROP_DESCRIPTION };
  
      // List of property namespaces to exclude from copy during rename
      
      private static Set<String> _excludedNamespaces = new TreeSet<String>(Arrays.asList(new String[]
      {
          NamespaceService.CONTENT_MODEL_1_0_URI, NamespaceService.SYSTEM_MODEL_1_0_URI
      }));
      
      // Disk sizing contants
      
      private static final int DiskBlockSize			= 512;	// bytes per block
      private static final long DiskAllocationUnit	= 32 * MemorySize.KILOBYTE;
      private static final long DiskBlocksPerUnit		= DiskAllocationUnit / DiskBlockSize;
      
      // Disk size returned in the content store does not support free/total size
      
      protected static final long DiskSizeDefault		= 1 * MemorySize.TERABYTE;
      protected static final long DiskFreeDefault		= DiskSizeDefault / 2;
  
      private boolean isReadOnly;
      private boolean isLockedFilesAsOffline;
      // pattern for detect CSV files.
      private Pattern renameCSVShufflePattern = Pattern.compile(".*[a-f0-9]{8}+$");
      // Services and helpers
      
      private CifsHelper cifsHelper;
      private NamespaceService namespaceService;
      private NodeService nodeService;
      private CheckOutCheckInService checkOutCheckInService;
      private SearchService searchService;
      private ContentService contentService;
      private MimetypeService mimetypeService;
      private PermissionService permissionService;
      private FileFolderService fileFolderService;
      private NodeArchiveService nodeArchiveService;
      private LockService lockService;
      private DictionaryService dictionaryService;
      private OwnableService ownableService;
      private ActionService actionService;
      
      private AuthenticationContext authContext;
      private AuthenticationService authService;
      private SysAdminParams sysAdminParams;
  
      private BehaviourFilter policyBehaviourFilter;
      private NodeMonitorFactory m_nodeMonitorFactory;
      
      
      /**
       * Class constructor
       * 
       * @param serviceRegistry 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
       * 
       * @param NodeArchiveService
       */
      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
       * @param serverConfig ServerConfigurationBean
       * @exception DeviceContextException
       */
      //  MER TODO - transaction handling in registerContext needs changing
      @Override
      public void registerContext(DeviceContext ctx) throws DeviceContextException
      {
          super.registerContext(ctx);
  
          ContentContext context = (ContentContext)ctx;
  
          // Wrap the initialization in a transaction
          
          UserTransaction tx = getTransactionService().getUserTransaction(true);
  
          try
          {
              // Use the system user as the authenticated context for the filesystem initialization
              
              AuthenticationUtil.pushAuthentication();
              AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());
              
              // Start the transaction
              
              if ( tx != null)
                  tx.begin();
              
              // Get the store
              String storeValue = context.getStoreName();
              StoreRef storeRef = new StoreRef(storeValue);
              
              // Connect to the repo and ensure that the store exists
              
              if (! nodeService.exists(storeRef))
              {
                  throw new DeviceContextException("Store not created prior to application startup: " + storeRef);
              }
              NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef);
              
              // Get the root path
              String rootPath = context.getRootPath();
              
              // Find the root node for this device
              
              List<NodeRef> nodeRefs = searchService.selectNodes(storeRootNodeRef, rootPath, null, namespaceService, false);
              
              NodeRef rootNodeRef = null;
              
              if (nodeRefs.size() > 1)
              {
                  throw new DeviceContextException("Multiple possible roots for device: \n" +
                          "   root path: " + rootPath + "\n" +
                          "   results: " + nodeRefs);
              }
              else if (nodeRefs.size() == 0)
              {
                  // Nothing found
                  
                  throw new DeviceContextException("No root found for device: \n" +
                          "   root path: " + rootPath);
              }
              else
              {
                  // We found a node
                  
                  rootNodeRef = nodeRefs.get(0);
              }
  
              // Check if a relative path has been specified
              
              String relPath = context.getRelativePath();
              
              if ( relPath != null && relPath.length() > 0)
              {
                  // Find the node and validate that the relative path is to a folder
                  
                  NodeRef relPathNode = cifsHelper.getNodeRef( rootNodeRef, relPath);
                  if ( cifsHelper.isDirectory( relPathNode) == false)
                      throw new DeviceContextException("Relative path is not a folder, " + relPath);
                  
                  // Use the relative path node as the root of the filesystem
                  
                  rootNodeRef = relPathNode;
              }
              else {
                  
                  // Make sure the default root node is a folder
                  
                  if ( cifsHelper.isDirectory( rootNodeRef) == false)
                      throw new DeviceContextException("Root node is not a folder type node");
              }
              
              // Commit the transaction
              
              // MER 16/03/2010 - Why is this transaction management here?
              tx.commit();
              tx = null;
  
              // Record the root node ref
              context.setRootNodeRef(rootNodeRef);
          }
          catch (Exception ex)
          {
              logger.error("Error during create context", ex);
              
              // MER BUGBUG Exception swallowed - will result in null pointer errors at best.
              throw new DeviceContextException("unable to register context", ex);
              // MER END
          }
          finally
          {
              // Restore authentication context
              
              AuthenticationUtil.popAuthentication();
              
              // If there is an active transaction then roll it back
              
              if ( tx != null)
              {
                  try
                  {
                      tx.rollback();
                  }
                  catch (Exception ex)
                  {
                      logger.warn("Failed to rollback transaction", ex);
                  }
              }
          }
  
          // Check if locked files should be marked as offline
          if ( context.getOfflineFiles() )
          {
              // Enable marking locked files as offline
              isLockedFilesAsOffline = true;
              
              // Logging
              
              logger.info("Locked files will be marked as offline");
          }
                  
          // Enable file state caching
          
  //        context.enableStateCache(serverConfig, true);
          
          // Install the node service monitor
          
          if ( !context.getDisableNodeMonitor() && m_nodeMonitorFactory != null) {
              
              // Create the node monitor
  
              NodeMonitor nodeMonitor = m_nodeMonitorFactory.createNodeMonitor(context);
              context.setNodeMonitor( nodeMonitor);
          }
          
          // Check if oplocks are enabled
          
          if ( context.getDisableOplocks() == true)
          	logger.warn("Oplock support disabled for filesystem " + ctx.getDeviceName());
          
          // Start the quota manager, if enabled
          
          if ( context.hasQuotaManager()) {
              
              try {
  
                  // Start the quota manager
                  
                  context.getQuotaManager().startManager( this, context);
                  logger.info("Quota manager enabled for filesystem");
              }
              catch ( QuotaManagerException ex) {
                  logger.error("Failed to start quota manager", ex);
              }
          }
      }
  
      /**
       * Check if pseudo file support is enabled
       * 
       * @param context ContentContext
       * @return boolean
       */
      public final boolean hasPseudoFileInterface(ContentContext context)
      {
      	return context.hasPseudoFileInterface();
      }
      
      /**
       * Return the pseudo file support implementation
       *
       * @param context ContentContext
       * @return PseudoFileInterface
       */
      public final PseudoFileInterface getPseudoFileInterface(ContentContext context)
      {
          return context.getPseudoFileInterface();
      }
      
      /**
       * Determine if the disk device is read-only.
       * 
       * @param sess Server session
       * @param ctx Device context
       * @return boolean
       * @exception java.io.IOException If an error occurs.
       */
      public boolean isReadOnly(SrvSession sess, DeviceContext ctx) throws IOException
      {
          return isReadOnly;
      }
      
      /**
       * Get the file information for the specified file.
       * 
       * @param sess Server session
       * @param tree Tree connection
       * @param name File name/path that information is required for.
       * @return File information if valid, else null
       * @exception java.io.IOException The exception description.
       */
      public FileInfo getFileInformation(SrvSession session, TreeConnection tree, String path) throws IOException
      {
          if(logger.isDebugEnabled())
          {
              logger.debug("getFileInformation:" + path);
          }
      	// Start a transaction
      	
      	beginReadTransaction( session);
          
          // Get the device root
          
          ContentContext ctx = (ContentContext) tree.getContext();
          NodeRef infoParentNodeRef = ctx.getRootNode();
          
          if ( path == null || path.length() == 0)
              path = FileName.DOS_SEPERATOR_STR;
          
          String infoPath = path;
          
          try
          {
              // Check if the path is to a pseudo file
  
              FileInfo finfo = null;
              
              if ( hasPseudoFileInterface(ctx))
              {
              	// Make sure the parent folder has a file state, and the path exists
          		
                  String[] paths = FileName.splitPath( path);
                  FileState fstate = ctx.getStateCache().findFileState( paths[0]);
                  
                  if ( fstate == null)
                  {
                  	NodeRef nodeRef = getNodeForPath(tree, paths[0]);
                          
                      if ( nodeRef != null)
                      {
                          // Get the file information for the node
                              
                          finfo = cifsHelper.getFileInformation(nodeRef, isReadOnly, isLockedFilesAsOffline);
                      }
                          
                		// Create the file state
                  		
                 		fstate = ctx.getStateCache().findFileState( paths[0], true);
                  		
                 		fstate.setFileStatus( DirectoryExists);
     	                fstate.setFilesystemObject( nodeRef);
                  		
                 		// Add pseudo files to the folder
                  		
                 		getPseudoFileInterface( ctx).addPseudoFilesToFolder( session, tree, paths[0]);
                  		
                 		// Debug
                  		
                		if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                 			logger.debug( "Added file state for pseudo files folder (getinfo) - " + paths[0]);
                  }
                  else if ( fstate.hasPseudoFiles() == false)
                  {
              		// Make sure the file state has the node ref
              		
              		if ( fstate.hasFilesystemObject() == false)
              		{
      	                // Get the node for the folder path
      	                
      	                fstate.setFilesystemObject( getNodeForPath( tree, paths[0]));
              		}
              		
                  	// Add pseudo files for the parent folder
                  	
              		getPseudoFileInterface( ctx).addPseudoFilesToFolder( session, tree, paths[0]);
              		
              		// Debug
              		
              		if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
              			logger.debug( "Added pseudo files for folder (exists) - " + paths[0]);
                  }
              	
              	
                  // Get the pseudo file
                  
                  PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( session, tree, path);
                  if ( pfile != null)
                  {
                      // DEBUG
                      if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                          logger.debug("getInfo using pseudo file info for " + path);
                      
                      FileInfo pseudoFileInfo = pfile.getFileInfo();
                      if (isReadOnly)
                      {
                          int attr = pseudoFileInfo.getFileAttributes();
                          if (( attr & FileAttribute.ReadOnly) == 0)
                          {
                              attr += FileAttribute.ReadOnly;
                              pseudoFileInfo.setFileAttributes(attr);
                          }
                      }
                      return pfile.getFileInfo();
                  }
              }
              
              // Get the node ref for the path, chances are there is a file state in the cache
              
              NodeRef nodeRef = getNodeForPath(tree, infoPath);
              
              if ( nodeRef != null)
              {
                  // Get the file information for the node
                  
                  finfo = cifsHelper.getFileInformation(nodeRef, isReadOnly, isLockedFilesAsOffline);
  
                  // DEBUG
                  
                  if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                      logger.debug("getInfo using cached noderef for path " + path);
              }
              
              // If the required node was not in the state cache, the parent folder node might be
              
              
              if ( finfo == null)
              {
                  String[] paths = FileName.splitPath( path);
                  
                  if ( paths[0] != null && paths[0].length() > 1)
                  {
                      // Find the node ref for the folder being searched
                      
                      nodeRef = getNodeForPath(tree, paths[0]);
                      
                      if ( nodeRef != null)
                      {
                          infoParentNodeRef = nodeRef;
                          infoPath          = paths[1];
                          
                          // DEBUG
                          
                          if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                              logger.debug("getInfo using cached noderef for parent " + path);
                      }
                  }
              
                  // Access the repository to get the file information
                  
                  finfo = cifsHelper.getFileInformation(infoParentNodeRef, infoPath, isReadOnly, isLockedFilesAsOffline);
                  
                  // DEBUG
                  
                  if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                      logger.debug("Getting file information: path=" + path + " file info: " + finfo);
              }
  
              // Set the file id for the file using the relative path
              
              if ( finfo != null) {
              	
              	// Set the file id
    
                  long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID));
                  finfo.setFileId((int) (id & 0xFFFFFFFFL));
  
              	// Copy cached file details, if available
              	
                  FileState fstate = getStateForPath(tree, infoPath);
                  if ( fstate != null) {
                      
                      // Copy cached timestamps
                      
  //                	if ( fstate.hasAccessDateTime())
  //                		finfo.setAccessDateTime(fstate.getAccessDateTime());
                  	if ( fstate.hasChangeDateTime())
                  		finfo.setChangeDateTime(fstate.getChangeDateTime());
                  	if ( fstate.hasModifyDateTime())
                  		finfo.setModifyDateTime(fstate.getModifyDateTime());
                  	
                  	// File allocation size
                  	
                  	if ( fstate.hasAllocationSize() && fstate.getAllocationSize() > finfo.getSize())
                  	    finfo.setAllocationSize( fstate.getAllocationSize());
                  }
                  else {
                  	
                  	// Create a file state for the file/folder
                  	
                  	fstate = ctx.getStateCache().findFileState( path, true);
                  	if ( finfo.isDirectory())
                  	    fstate.setFileStatus( DirectoryExists);
                  	else
                  	    fstate.setFileStatus( FileExists);
                  	fstate.setFilesystemObject( nodeRef);
                  }
              }
              
              // Return the file information
              
              return finfo;
          }
          catch (FileNotFoundException e)
          {
              // Debug
          	
              if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                  logger.debug("Get file info - file not found, " + path);
              throw e;
          }
          catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
          {
              // Debug
              
              if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                  logger.debug("Get file info - access denied, " + path);
              
              // Convert to a filesystem access denied status
              
              throw new AccessDeniedException("Get file information " + path);
          }
          catch (RuntimeException ex)
          {
              // Debug
              
              if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                  logger.debug("Get file info error", ex);
              
              // Convert to a general I/O exception
              
              throw new IOException("Get file information " + path);
          }
      }
  
      /**
       * Start a new search on the filesystem using the specified searchPath that may contain
       * wildcards.
       * 
       * @param sess Server session
       * @param tree Tree connection
       * @param searchPath File(s) to search for, may include wildcards.
       * @param attrib Attributes of the file(s) to search for, see class SMBFileAttribute.
       * @return SearchContext
       * @exception java.io.FileNotFoundException If the search could not be started.
       */
      public SearchContext startSearch(SrvSession sess, TreeConnection tree, String searchPath, int attributes) throws FileNotFoundException
      {
          // Access the device context
          if(logger.isDebugEnabled())
          {
              logger.debug("startSearch: "+ searchPath);
          }
          
          ContentContext ctx = (ContentContext) tree.getContext();
  
          try
          {
              String searchFileSpec = searchPath;
              NodeRef searchRootNodeRef = ctx.getRootNode();
              FileState searchFolderState = null;
              
              // Create the transaction
              
              beginReadTransaction( sess);
              
              // If the state table is available see if we can speed up the search using either cached
              // file information or find the folder node to be searched without having to walk the path
  
              String[] paths = FileName.splitPath(searchPath);
              
              if ( ctx.hasStateCache())
              {
                  // See if the folder to be searched has a file state, we can avoid having to walk the path
                  
                  if ( paths[0] != null && paths[0].length() >= 1)
                  {
                      // Find the node ref for the folder being searched
                      
                      NodeRef nodeRef = getNodeForPath(tree, paths[0]);
                      
                      // Get the file state for the folder being searched
                      
                      searchFolderState = getStateForPath(tree, paths[0]);
                      if ( searchFolderState == null)
                      {
                          // Create a file state for the folder
  
                          searchFolderState = ctx.getStateCache().findFileState( paths[0], true);
                      }
                      
                      // Make sure the associated node is set
                      
                      if ( searchFolderState.hasFilesystemObject() == false)
                      {
                          // Set the associated node for the folder
                          
                          searchFolderState.setFilesystemObject( nodeRef);
                      }
                      
                      // Add pseudo files to the folder being searched
  
                      if ( hasPseudoFileInterface(ctx))
                          getPseudoFileInterface(ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
  
                      // Set the search node and file spec
                      
                      if ( nodeRef != null)
                      {
                          searchRootNodeRef = nodeRef;
                          searchFileSpec    = paths[1];
                          
                          // DEBUG
                          
                          if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
                              logger.debug("Search using cached noderef for path " + searchPath);
                      }
                  }
              }
              
              // Convert the all files wildcard
              
              if ( searchFileSpec.equals( "*.*"))
              	searchFileSpec = "*";
              
              // Debug
              
              long startTime = 0L;
              if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
              	startTime = System.currentTimeMillis();
              
              // Perform the search
              
              List<NodeRef> results = cifsHelper.getNodeRefs(searchRootNodeRef, searchFileSpec);
  
              // Debug
              
              if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH)) {
              	long endTime = System.currentTimeMillis();
              	if (( endTime - startTime) > 500)
              		logger.debug("Search for searchPath=" + searchPath + ", searchSpec=" + searchFileSpec + ", searchRootNode=" + searchRootNodeRef + " took "
              				     + ( endTime - startTime) + "ms results=" + results.size());
              }
              
              // Check if there are any pseudo files for the folder being searched, for CIFS only
              
              PseudoFileList pseudoList = null;
              
              if ( sess instanceof SMBSrvSession && searchFolderState != null && searchFolderState.hasPseudoFiles())
              {
                  // If it is a wildcard search use all pseudo files
                  
                  if ( WildCard.containsWildcards(searchFileSpec))
                  {
                      // Get the list of pseudo files for the search path
                      
                      pseudoList = searchFolderState.getPseudoFileList();
                      
                      // Check if the wildcard is for all files or a subset
                     
                      if ( searchFileSpec.equals( "*") == false && pseudoList != null && pseudoList.numberOfFiles() > 0)
                      {
                          // Generate a subset of pseudo files that match the wildcard search pattern
                          
                          WildCard wildCard = new WildCard( searchFileSpec, false);
                          PseudoFileList filterList = null;
                          
                          for ( int i = 0; i < pseudoList.numberOfFiles(); i++)
                          {
                              PseudoFile pseudoFile = pseudoList.getFileAt( i);
                              if ( wildCard.matchesPattern( pseudoFile.getFileName()))
                              {
                                  // Add the pseudo file to the filtered list
                                  
                                  if ( filterList == null)
                                      filterList = new PseudoFileList();
                                  filterList.addFile( pseudoFile);
                              }
                          }
                          
                          // Use the filtered pseudo file list, or null if there were no matches
                          
                          pseudoList = filterList;
                      }
                  }
                  else if ( results == null || results.size() == 0)
                  {
                      // Check if the required file is in the pseudo file list
                      
                      String fname = paths[1];
                      
                      if ( fname != null)
                      {
                          // Search for a matching pseudo file
                          
                          PseudoFile pfile = searchFolderState.getPseudoFileList().findFile( fname, true);
                          if ( pfile != null)
                          {
                              // Create a file list with the required file
                              
                              pseudoList = new PseudoFileList();
                              pseudoList.addFile( pfile);
                          }
                      }
                  }
              }
              
              // Build the search context to store the results, use the cache lookup search for wildcard searches
              
              SearchContext searchCtx = null;
              
              if ( searchFileSpec.equals( "*"))
              {
              	// Use a cache lookup search context 
                                                                                                                                                                                 
              	CacheLookupSearchContext cacheContext = new CacheLookupSearchContext(cifsHelper, results, searchFileSpec, pseudoList, paths[0], ctx.getStateCache(), isLockedFilesAsOffline);
              	searchCtx = cacheContext;
              	
              	// Set the '.' and '..' pseudo file entry details
              	
              	if ( searchFolderState != null && searchFolderState.hasFilesystemObject())
              	{
              		// Get the '.' pseudo entry file details
              	
              		FileInfo finfo = cifsHelper.getFileInformation((NodeRef) searchFolderState.getFilesystemObject(), isReadOnly, isLockedFilesAsOffline);
              		
              		// Blend in any cached timestamps
              		
              		if ( searchFolderState != null) {
              			if ( searchFolderState.hasAccessDateTime())
              				finfo.setAccessDateTime( searchFolderState.getAccessDateTime());
              			
              			if ( searchFolderState.hasChangeDateTime())
              				finfo.setChangeDateTime( searchFolderState.getChangeDateTime());
              			
              			if ( searchFolderState.hasModifyDateTime())
              				finfo.setModifyDateTime( searchFolderState.getModifyDateTime());
              		}
              		
              		// Set the '.' pseudo entry details
              		
              		cacheContext.setDotInfo( finfo);
              		
              		// Check if the search folder has a parent, if we are at the root of the filesystem then re-use
              		// the file information
              		
              		if ( searchFolderState.getPath().equals( FileName.DOS_SEPERATOR_STR)) {
              			
              			// Searching the root folder, re-use the search folder file information for the '..' pseudo entry
              			
              			FileInfo dotDotInfo = new FileInfo();
              			dotDotInfo.copyFrom( finfo);
              			cacheContext.setDotDotInfo( dotDotInfo);
              		}
              		else {
              			
              			// Get the parent folder path
              			
              			String parentPath = searchFolderState.getPath();
              			if ( parentPath.endsWith( FileName.DOS_SEPERATOR_STR) && parentPath.length() > 1)
              				parentPath = parentPath.substring(0, parentPath.length() - 1);
              			
              			int pos = parentPath.lastIndexOf( FileName.DOS_SEPERATOR_STR);
              			if ( pos != -1)
              				parentPath = parentPath.substring(0, pos + 1);
              			
              			// Get the file state for the parent path, if available
              			
              			FileState parentState = ctx.getStateCache().findFileState( parentPath);
              			NodeRef parentNode = null;
              			
              			if ( parentState != null)
              				parentNode = (NodeRef) parentState.getFilesystemObject();
              			
              			if ( parentState == null || parentNode == null)
              				parentNode = getNodeForPath( tree, parentPath);
  
              			// Get the file information for the parent folder
              			
              			finfo = cifsHelper.getFileInformation( parentNode, isReadOnly, isLockedFilesAsOffline);
              			
              			// Blend in any cached timestamps
              			
                  		if ( parentState != null) {
                  			if ( parentState.hasAccessDateTime())
                  				finfo.setAccessDateTime( parentState.getAccessDateTime());
                  			
                  			if ( parentState.hasChangeDateTime())
                  				finfo.setChangeDateTime( parentState.getChangeDateTime());
                  			
                  			if ( parentState.hasModifyDateTime())
                  				finfo.setModifyDateTime( parentState.getModifyDateTime());
                  		}
  
                  		// Set the '..' pseudo entry details
                  		
              			cacheContext.setDotDotInfo( finfo);
              		}
             		}
              }
              else {
              	if ( ctx.hasStateCache())
                  	searchCtx = new CacheLookupSearchContext(cifsHelper, results, searchFileSpec, pseudoList, paths[0], ctx.getStateCache(), isLockedFilesAsOffline);
              	else
                  	searchCtx = new ContentSearchContext(cifsHelper, results, searchFileSpec, pseudoList, paths[0], isLockedFilesAsOffline);
              }
              
              // Debug
              
              if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
                  logger.debug("Started search: search path=" + searchPath + " attributes=" + attributes + ", ctx=" + searchCtx);
              
              // Return the search context
              
              return searchCtx;
          }
          catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
          {
              // Debug
              
              if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
                  logger.debug("Start search - access denied, " + searchPath);
              
              // Convert to a file not found status
              
              throw new FileNotFoundException("Start search " + searchPath);
          }
          catch (RuntimeException ex)
          {
              // Debug
              
              if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
                  logger.debug("Start search", ex);
              
              // Convert to a file not found status
              
              throw new FileNotFoundException("Start search " + searchPath);
          }
      }
  
      /**
       * Check if the specified file exists, and whether it is a file or directory.
       * 
       * <p>
       * WARNING: side effect, commit or roll back current user transaction context.  Current transaction becomes read only.
       * 
       * @param sess Server session
       * @param tree Tree connection
       * @param name the path of the file 
       * @return FileStatus (0: NotExist, 1 : FileExist, 2: DirectoryExists) 
       * @see FileStatus
       */
      public int fileExists(SrvSession sess, TreeConnection tree, String name)
      {
          ContentContext ctx = (ContentContext) tree.getContext();
          int status = FileStatus.Unknown;
          FileState fstate = null;
          
          try
          {
              // Check for a cached file state
              
              if ( ctx.hasStateCache())
                  fstate = ctx.getStateCache().findFileState(name, true);
              
              if ( fstate != null && fstate.getFileStatus() != FileUnknown)
              {
                  status = fstate.getFileStatus();
                  if ( status >= CustomFileStatus)
                      status = FileNotExist;
  
                  // DEBUG
                  
                  if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
                      logger.debug("Cache hit - fileExists() " + name + ", sts=" + status);
              }
              else
              {
                  // Check if pseudo files are enabled
  
                  if ( hasPseudoFileInterface(ctx))
                  {
                  	// Check if the file name is a pseudo file name
                  	
                  	if ( getPseudoFileInterface( ctx).isPseudoFile(sess, tree, name)) {
                  		
      	            	// Make sure the parent folder has a file state, and the path exists
                  		
      	                String[] paths = FileName.splitPath( name);
      	                fstate = ctx.getStateCache().findFileState( paths[0]);
      	                
      	                if ( fstate == null) {
  
      	                	// Check if the path exists
      	                	
      	                	if ( fileExists( sess, tree, paths[0]) == FileStatus.DirectoryExists)
      	                	{
      	                		// Create the file state
      	                		
      	                		fstate = ctx.getStateCache().findFileState( paths[0], true);
      	                		
      	                		fstate.setFileStatus( DirectoryExists);
      	                		
     	        	                // Get the node for the folder path
      	        	                
      	                		beginReadTransaction( sess);
     	        	                fstate.setFilesystemObject( getNodeForPath( tree, paths[0]));
      	                		
      	                		// Add pseudo files to the folder
      	                		
      	                		getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
      	                		
      	                		// Debug
      	                		
      	                		if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
      	                			logger.debug( "Added file state for pseudo files folder (exists) - " + paths[0]);
      	                	}
      	                }
      	                else if ( fstate.hasPseudoFiles() == false)
      	                {
  	                		// Make sure the file state has the node ref
  	                		
  	                		if ( fstate.hasFilesystemObject() == false)
  	                		{
  	        	            	// Create the transaction
  	        	                
  	                			beginReadTransaction( sess);
  	        	            
  	        	                // Get the node for the folder path
  	        	                
  	        	                fstate.setFilesystemObject( getNodeForPath( tree, paths[0]));
  	                		}
  	                		
      	                	// Add pseudo files for the parent folder
      	                	
                      		getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
                      		
                      		// Debug
                      		
                      		if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
                      			logger.debug( "Added pseudo files for folder (exists) - " + paths[0]);
      	                }
      	            	
      	                // Check if the path is to a pseudo file
      	                
      	                PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, name);
      	                if ( pfile != null)
      	                {
      	                    // Indicate that the file exists
      	                    
      	                    status = FileStatus.FileExists;
      	                }
      	                else
      	                {
      	                	// Failed to find pseudo file
      	                	
      	                	if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
      	                		logger.debug( "Failed to find pseudo file (exists) - " + name);
      	                }
                  	}
                  }
  
                  // If the file is not a pseudo file then search for the file
                  
                  if ( status == FileStatus.Unknown) 
                  {
  	            	// Create the transaction
  	                
                  	beginReadTransaction( sess);
  	                
  	                // Get the file information to check if the file/folder exists
  	                
                      FileInfo info = getFileInformation(sess, tree, name);
                      NodeRef nodeRef = getNodeOrNull(name, ctx, fstate);
                      nodeRef = ((null == nodeRef) && (info instanceof ContentFileInfo)) ? (((ContentFileInfo) info).getNodeRef()) : (nodeRef);
  
                      if ((null == nodeRef) || !fileFolderService.exists(nodeRef))
                      {
                          status = FileStatus.NotExist;
                      }
                      else
                      {
  		                if (info.isDirectory())
  		                {
  		                    status = FileStatus.DirectoryExists;
  		                }
  		                else
  		                {
  		                    status = FileStatus.FileExists;
  		                }
  	                }
  
  	                // Update the file state status
  
  	                if ( fstate != null)
  	                    fstate.setFileStatus( status);
                  }
              }
          }
          catch (FileNotFoundException e)
          {
              status = FileStatus.NotExist;
              if ( fstate != null)
                  fstate.setFileStatus( status);
          }
          catch (IOException e)
          {
              // Debug
  
          	if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
          		logger.debug("File exists error, " + name, e);
              
              status = FileStatus.NotExist;
          }
  
          // Debug
          
          if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
              logger.debug("File status determined: name=" + name + " status=" + fileStatusString(fstate.getFileStatus()));
          
          // Return the file/folder status
          
          return status;
      }
      
      /**
       * Open a file or folder
       * 
       * @param sess SrvSession
       * @param tree TreeConnection
       * @param params FileOpenParams
       * @return NetworkFile
       * @exception IOException
       */
      public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params ) throws IOException
      {
          // Create the transaction
          
      	beginReadTransaction( sess);
          ContentContext ctx = (ContentContext) tree.getContext();
          
          try
          {
              // Check if pseudo files are enabled
              
              if ( hasPseudoFileInterface(ctx))
              {
              	// Check if the file name is a pseudo file name
              	
              	String path = params.getPath();
  
              	if ( getPseudoFileInterface( ctx).isPseudoFile(sess, tree, path)) {
              		
  	            	// Make sure the parent folder has a file state, and the path exists
  	
  	                String[] paths = FileName.splitPath( path);
  	                FileState fstate = ctx.getStateCache().findFileState( paths[0]);
  	                
  	                if ( fstate == null) {
  
  	                	// Check if the path exists
  	                	
  	                	if ( fileExists( sess, tree, paths[0]) == FileStatus.DirectoryExists)
  	                	{
  	                		// Create the file state and add any pseudo files
  	                		
  	                		fstate = ctx.getStateCache().findFileState( paths[0], true);
  	                		
  	                		fstate.setFileStatus( DirectoryExists);
  	                		getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
  	                		
  	                		// Debug
  	                		
  	                		if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
  	                			logger.debug( "Added file state for pseudo files folder (open) - " + paths[0]);
  	                	}
  	                }
  	                else if ( fstate.hasPseudoFiles() == false)
  	                {
  	                	// Add pseudo files for the parent folder
  	                	
                  		getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
                  		
                  		// Debug
                  		
                  		if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
                  			logger.debug( "Added pseudo files for folder (open) - " + paths[0]);
  	                }
  	            	
  	                // Check if the path is to a pseudo file
  	                
  	                PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, params.getPath());
  	                if ( pfile != null)
  	                {
  	                    // Create a network file to access the pseudo file data
  	                    
  	                    return pfile.getFile( params.getPath());
  	                }
  	                else
  	                {
  	                	// Failed to find pseudo file
  	                	
  	                	if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
  	                		logger.debug( "Failed to find pseudo file (open) - " + params.getPath());
  	                }
              	}
              }
              
              // Not a pseudo file, try and open a normal file/folder node
              
              NodeRef nodeRef = getNodeForPath(tree, params.getPath());
              
              // Check permissions on the file/folder node
              //
              // Check for read access
              
              if ( params.hasAccessMode(AccessMode.NTRead) &&
                      permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.DENIED)
                  throw new AccessDeniedException("No read access to " + params.getFullPath());
                  
              // Check for write access
              
              if ( params.hasAccessMode(AccessMode.NTWrite) &&
                      permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED)
                  throw new AccessDeniedException("No write access to " + params.getFullPath());
              
              // Check for delete access
              
  //            if ( params.hasAccessMode(AccessMode.NTDelete) &&
  //                    permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED)
  //                throw new AccessDeniedException("No delete access to " + params.getFullPath());
  
              // Check if the file has a lock
              
              String lockTypeStr = (String) nodeService.getProperty( nodeRef, ContentModel.PROP_LOCK_TYPE);
              
              if ( params.hasAccessMode(AccessMode.NTWrite) && lockTypeStr != null)
                  throw new AccessDeniedException("File is locked, no write access to " + params.getFullPath());
              
              //  Check if there is a file state for the file
  
              FileState fstate = null;
              
              if ( ctx.hasStateCache())
              {
                  // Check if there is a file state for the file
  
                  fstate = ctx.getStateCache().findFileState( params.getPath());
              
                  if ( fstate != null)
                  {                
                      // Check if the file exists
                      
                      if ( fstate.exists() == false)
                          throw new FileNotFoundException();
                  }
                  else {
                  	
                  	// Create a file state for the path
                  	
                      fstate = ctx.getStateCache().findFileState( params.getPath(), true);
                  }                	
                      
              	// Check if the current file open allows the required shared access
              	
              	boolean nosharing = false;
              	String noshrReason = null;
              	
              	// TEST
              	
          		if ( params.getAccessMode() == AccessMode.NTFileGenericExecute && params.getPath().toLowerCase().endsWith( ".exe") == false) {
          			
              		// DEBUG
              		
              		if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) {
              			logger.debug( "Execute access mode, path" + params.getPath());
              			logger.debug( "  Fstate=" + fstate);
              		}
          		
                      throw new AccessDeniedException("Invalid access mode");
                  }
                  
                  if ( fstate.getOpenCount() > 0 && params.isAttributesOnlyAccess() == false) {
                      
      				// Check for impersonation security level from the original process that opened the file
      				
      				if ( params.getSecurityLevel() == WinNT.SecurityImpersonation && params.getProcessId() == fstate.getProcessId())
      					nosharing = false;
      	
      				// Check if the caller wants read access, check the sharing mode
      				// Check if the caller wants write access, check if the sharing mode allows write
      				
      		    	else if ( params.isReadOnlyAccess() && (fstate.getSharedAccess() & SharingMode.READ) != 0)
      		    		nosharing = false;
      				
      				// Check if the caller wants write access, check the sharing mode
      				
      		    	else if (( params.isReadWriteAccess() || params.isWriteOnlyAccess()) && (fstate.getSharedAccess() & SharingMode.WRITE) == 0)
      		    	{
      					nosharing = true;
      					noshrReason = "Sharing mode disallows write";
      					
      		    		// DEBUG
      		    		
                          if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
      		    			logger.debug("Sharing mode disallows write access path=" + params.getPath());
      		    	}
      		    	
      				// Check if the file has been opened for exclusive access
      				
      				else if ( fstate.getSharedAccess() == SharingMode.NOSHARING) {
      					nosharing = true;
      					noshrReason = "Sharing mode exclusive";
      				}
      				
      				// Check if the required sharing mode is allowed by the current file open
      				
      				else if ((fstate.getSharedAccess() & params.getSharedAccess()) != params.getSharedAccess()) {
      					nosharing = true;
      					noshrReason = "Sharing mode mismatch";
      					
                          if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
      		    			logger.debug("Local share mode=0x" + Integer.toHexString(fstate.getSharedAccess()) + ", params share mode=0x" + Integer.toHexString(params.getSharedAccess()));
      				}
      				
      				// Check if the caller wants exclusive access to the file
      				
      		    	else if ( params.getSharedAccess() == SharingMode.NOSHARING) {
      		    		nosharing = true;
      		    		noshrReason = "Requestor wants exclusive mode";
      		    	}
                  }
                  
                  // Check if the file allows shared access
                  
                  if ( nosharing == true)
                  {
                      if ( params.getPath().equals( "\\") == false) {
                          
                          // DEBUG
                          
                          if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                              logger.debug("Sharing violation path=" + params.getPath() + ", sharing=0x" + Integer.toHexString(fstate.getSharedAccess()) + ",reason=" + noshrReason);
                      
                          // File is locked by another user
                          
                          throw new FileSharingException("File already open, " + params.getPath());
                      }
                  }
                  
                  // Update the file sharing mode and process id, if this is the first file open
                  
                  fstate.setSharedAccess( params.getSharedAccess());
                  fstate.setProcessId( params.getProcessId());
                  
                  // DEBUG
                  
                  if ( logger.isDebugEnabled() && fstate.getOpenCount() == 0 && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                      logger.debug("Path " + params.getPath() + ", sharing=0x" + Integer.toHexString(params.getSharedAccess()) + ", PID=" + params.getProcessId());
              }
              
              // Check if the node is a link node
              
              NodeRef linkRef = (NodeRef) nodeService.getProperty(nodeRef, ContentModel.PROP_LINK_DESTINATION);
              AlfrescoNetworkFile netFile = null;
              
              if ( linkRef == null)
              {
                  // Check if the file is already opened by this client/process
                  
                  if ( tree.openFileCount() > 0 && params.isAttributesOnlyAccess() == false) {
                  
                      // Search the open file table for this session/virtual circuit
                      
                      int idx = 0;
                      
                      while ( idx < tree.getFileTableLength() && netFile == null) {
                          
                          // Get the current file from the open file table
                          
                          NetworkFile curFile = tree.findFile( idx);
                          if ( curFile != null && curFile instanceof ContentNetworkFile) {
                              
                              // Check if the file is the same path and process id
                              
                              ContentNetworkFile contentFile = (ContentNetworkFile) curFile;
                              if ( contentFile.getProcessId() == params.getProcessId() &&
                                      contentFile.getFullName().equalsIgnoreCase( params.getFullPath())) {
                                  
                                  // Check that the access mode is the same
                                  
                                  if (( params.isReadWriteAccess() && contentFile.getGrantedAccess() == NetworkFile.READWRITE) ||
                                          ( params.isReadOnlyAccess() && contentFile.getGrantedAccess() == NetworkFile.READONLY)) {
                                      
                                      // Found a match, re-use the open file
                                      
                                      netFile = contentFile;
                                      
                                      // Increment the file open count, last file close will actually close the file/stream
                                      
                                      contentFile.incrementOpenCount();
      
                                      // DEBUG
                                      
                                      if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                                          logger.debug("Re-use existing file open Path " + params.getPath() + ", PID=" + params.getProcessId() + ", params=" +
                                                  ( params.isReadOnlyAccess() ? "ReadOnly" : "Write") + ", file=" + 
                                                  ( contentFile.getGrantedAccess() <= NetworkFile.READONLY ? "ReadOnly" : "Write"));
                                  }
                                  else if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                                      logger.debug("Not re-using file path=" + params.getPath() + ", readWrite=" + (params.isReadWriteAccess() ? "true" : "false") +
                                              ", readOnly=" + (params.isReadOnlyAccess() ? "true" : "false") +
                                              ", grantedAccess=" + contentFile.getGrantedAccessAsString());
                              }
                          }
                          
                          // Update the file table index
                          
                          idx++;
                      }
                  }
                  
                  // Create the network file, if we could not match an existing file open
                  
                  if ( netFile == null) {
                      
                      // Create a new network file for the open request
                  
                      netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params.getPath(), params.isReadOnlyAccess(), params.isAttributesOnlyAccess(), sess);
                  }
              }
              else
              {
                  // Get the CIFS server name
                  
                  String srvName = null;
                  SMBServer cifsServer = (SMBServer) sess.getServer().getConfiguration().findServer( "CIFS");
                  
                  if(sess instanceof SMBSrvSession)
                  {
                      SMBSrvSession smbSess = (SMBSrvSession)sess;
                      srvName = smbSess.getShareHostName();
                  }
                  else if ( cifsServer != null)
                  {
                      // Use the CIFS server name in the URL
                  
                      srvName = cifsServer.getServerName();
                  }
                  else
                  {
                      // Use the local server name in the URL
                    
                      srvName = InetAddress.getLocalHost().getHostName();
                  }
                  
                  // Convert the target node to a path, convert to URL format
                  
                  String path = getPathForNode( tree, linkRef);
                  path = path.replace( FileName.DOS_SEPERATOR, '/');
                  
                  // Build the URL file data
                  
                  StringBuilder urlStr = new StringBuilder();
              
                  urlStr.append("[InternetShortcut]\r\n");
                  urlStr.append("URL=file://");
                  urlStr.append( srvName);
                  urlStr.append("/");
                  urlStr.append( tree.getSharedDevice().getName());
                  urlStr.append( path);
                  urlStr.append("\r\n");
      
                  // Create the in memory pseudo file for the URL link
                  
                  byte[] urlData = urlStr.toString().getBytes();
                  
                  // Get the file information for the link node
                  
                  FileInfo fInfo = cifsHelper.getFileInformation( nodeRef, isReadOnly, isLockedFilesAsOffline);
  
                  // Set the file size to the actual data length
                  
                  fInfo.setFileSize( urlData.length);
                  
                  // Create the network file using the in-memory file data
                  
                  netFile = new LinkMemoryNetworkFile( fInfo.getFileName(), urlData, fInfo, nodeRef);
                  netFile.setFullName( params.getPath());
              }
              
              // Generate a file id for the file
              
              if ( netFile != null) {
                  long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID));
                  netFile.setFileId(( int) ( id & 0xFFFFFFFFL));
                  
                  // Indicate the file is open
                  
                  netFile.setClosed( false);
              }
              
              // If the file has been opened for overwrite then truncate the file to zero length, this will
              // also prevent the existing content data from being copied to the new version of the file
              
              if ( params.isOverwrite() && netFile != null)
              {
                  // Truncate the file to zero length
                  
                  netFile.truncateFile( 0L);
              }
              
              // Create a file state for the open file
              
              if ( ctx.hasStateCache())
              {
                  if ( fstate  == null)
                      fstate = ctx.getStateCache().findFileState(params.getPath(), true);
              
                  // Update the file state, cache the node
                  
                  if ( netFile.getGrantedAccess() > NetworkFile.ATTRIBUTESONLY)
                  	fstate.incrementOpenCount();
                  fstate.setFilesystemObject(nodeRef);
                  
                  // Store the state with the file
                  
                  netFile.setFileState( fstate);
                  
                  // Set the file access date/time, if available
                  
  //                if ( fstate.hasAccessDateTime())
  //                    netFile.setAccessDate( fstate.getAccessDateTime());
                  
                  
                 // Set the live file size, if available
                  
                 if ( fstate.hasFileSize())
              	   netFile.setFileSize( fstate.getFileSize());
              }
              
              // Debug
              
              if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                  logger.debug("Opened network file: path=" + params.getPath() + " file open parameters=" + params + " network file=" + netFile);
  
              // Return the network file
              
              return netFile;
          }
          catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
          {
              // Debug
              
              if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                  logger.debug("Open file - access denied, " + params.getFullPath());
              
              // Convert to a filesystem access denied status
              
              throw new AccessDeniedException("Open file " + params.getFullPath());
          }
          catch (RuntimeException ex)
          {
              // Debug
              
              if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                  logger.debug("Open file error", ex);
              
              // Convert to a general I/O exception
              
              throw new IOException("Open file " + params.getFullPath());
          }
      }
      
      /**
       * Create a new file on the file system.
       * 
       * <p>
       * WARNING : side effect - closes current transaction context.
       * 
       * @param sess Server session
       * @param tree Tree connection
       * @param params File create parameters
       * @return NetworkFile
       * @exception java.io.IOException If an error occurs.
       */
      public NetworkFile createFile(SrvSession sess, final TreeConnection tree, final FileOpenParams params) throws IOException
      {
          final ContentContext ctx = (ContentContext) tree.getContext();
          
          try
          {
  
              // Access the repository in a retryable write transaction
              Pair<String, NodeRef> result = doInWriteTransaction(sess, new CallableIO<Pair<String, NodeRef>>(){
                  public Pair<String, NodeRef> call() throws IOException
                  {
                      // Get the device root
  
                      NodeRef deviceRootNodeRef = ctx.getRootNode();
                      
                      String path = params.getPath();
                      String parentPath = null;
  
                      // If the state table is available then try to find the parent folder node for the new file
                      // to save having to walk the path
  
                      if (ctx.hasStateCache())
                      {
                          // See if the parent folder has a file state, we can avoid having to walk the path
                          
                          String[] paths = FileName.splitPath(path);
                          if ( paths[0] != null && paths[0].length() > 1)
                          {
                              // Find the node ref for the folder being searched
                              
                              NodeRef nodeRef = getNodeForPath(tree, paths[0]);
                              
                              if ( nodeRef != null)
                              {
                                  deviceRootNodeRef = nodeRef;
                                  path              = paths[1];
                                  
                                  // DEBUG
                                  
                                  if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
                                      logger.debug("Create file using cached noderef for path " + paths[0]);
                              }
                              
                              parentPath = paths[0];
                          }
                      }
                      
                      // Create it - the path will be created, if necessary
                      if(logger.isDebugEnabled())
                      {
                          logger.debug("create new file" + path);
                      }
  
  
                      NodeRef nodeRef = null;
  
                      try
                      {
                          nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, ContentModel.TYPE_CONTENT);
                          nodeService.addAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT, null);
                      }
                      catch ( FileExistsException ex)
                      {
                          nodeRef = cifsHelper.getNodeRef(deviceRootNodeRef, path);
                          if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_SOFT_DELETE))
                          {
                              throw ex;
                          }
                      }
                      
                      return new Pair<String, NodeRef>(parentPath, nodeRef);
                  }});
  
              // Get or create the file state for the parent folder
              FileState parentState = null;
              String parentPath = result.getFirst();
              if (parentPath != null)
              {
                  parentState = getStateForPath(tree, parentPath);
                  if (parentState == null && ctx.hasStateCache())
                      parentState = ctx.getStateCache().findFileState(parentPath, true);
              }
              
              NodeRef nodeRef = result.getSecond();
              if (nodeRef != null && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_SOFT_DELETE))
              {
                  nodeService.removeAspect(nodeRef, ContentModel.ASPECT_SOFT_DELETE);
              }
    
              // Create the network file
              
              ContentNetworkFile netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, result.getSecond(), params.getPath(), params.isReadOnlyAccess(), params.isAttributesOnlyAccess(), sess);
              
              // Always allow write access to a newly created file
              
              netFile.setGrantedAccess(NetworkFile.READWRITE);
              
              // Set the owner process id for this open file
              
              netFile.setProcessId( params.getProcessId());
              
              // Truncate the file so that the content stream is created
              
              netFile.truncateFile( 0L);
  
              // Indicate the file is open
              
              netFile.setClosed( false);
              
              // Generate a file id for the file
              
              if ( netFile != null) {
                  long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(netFile.getNodeRef(), ContentModel.PROP_NODE_DBID));
                  netFile.setFileId((int) (id & 0xFFFFFFFFL));
              }
              
              // Add a file state for the new file/folder
              
  