@Override
  public void closeFile(SrvSession sess, TreeConnection tree, NetworkFile param)
      throws IOException {
    ContentContext tctx = (ContentContext) tree.getContext();

    try {
      diskInterface.closeFile(sess, tree, param);

      if (tctx.hasStateCache()) {
        FileStateCache cache = tctx.getStateCache();
        FileState fstate = cache.findFileState(param.getFullName(), true);

        if (fstate != null && param.getAccessToken() != null) {
          FileAccessToken token = param.getAccessToken();
          if (logger.isDebugEnabled() && token != null) {
            logger.debug("close file release lock token:" + token);
          }
          cache.releaseFileAccess(fstate, token);
        }
      }
    } catch (IOException ie) {
      if (logger.isDebugEnabled()) {
        logger.debug("close file exception caught", ie);
      }
      throw ie;
    }
  }
  /**
   * Re-create, or attach, a file request to the file state.
   *
   * @param fid int
   * @param tempPath String
   * @param virtPath String
   * @param sts int
   * @return FileState
   */
  protected final FileState createFileStateForRequest(
      int fid, String tempPath, String virtPath, int sts) {

    // Find, or create, the file state for the file/directory

    FileState state = m_stateCache.findFileState(virtPath, true);

    synchronized (state) {

      // Prevent the file state from expiring whilst the request is queued against it

      state.setExpiryTime(FileState.NoTimeout);

      // Indicate that the file exists, set the unique file id

      state.setFileStatus(FileStatus.FileExists);
      state.setFileId(fid);

      // Check if the file segment has been attached to the file state

      FileSegmentInfo fileSegInfo = (FileSegmentInfo) state.findAttribute(DBFileSegmentInfo);
      FileSegment fileSeg = null;

      if (fileSegInfo == null) {

        // Create a new file segment

        fileSegInfo = new FileSegmentInfo();
        fileSegInfo.setTemporaryFile(tempPath);

        fileSeg = new FileSegment(fileSegInfo, true);
        fileSeg.setStatus(sts, true);

        // Add the segment to the file state cache

        state.addAttribute(DBFileSegmentInfo, fileSegInfo);
      } else {

        // Make sure the file segment indicates its part of a queued request

        fileSeg = new FileSegment(fileSegInfo, true);
        fileSeg.setStatus(sts, true);
      }
    }

    // Return the file state

    return state;
  }
  /**
   * Delete the specified file data
   *
   * @param fname String
   * @param fid int
   * @param stid int
   * @exception IOException
   */
  public void deleteFile(String fname, int fid, int stid) throws IOException {

    // Delete the file data from the database

    try {

      // Find the associated file state

      FileState fstate = m_stateCache.findFileState(fname, false);

      if (fstate != null) {

        // Get the file segment details

        FileSegmentInfo fileSegInfo = (FileSegmentInfo) fstate.removeAttribute(DBFileSegmentInfo);
        if (fileSegInfo != null) {
          try {

            // Change the file segment status

            fileSegInfo.setQueued(false);
            fileSegInfo.setStatus(FileSegmentInfo.Initial);

            // Delete the temporary file

            fileSegInfo.deleteTemporaryFile();
          } catch (Exception ex) {

            // DEBUG

            if (Debug.EnableInfo && hasDebug())
              Debug.println(
                  "## ObjIdLoader failed to delete temp file " + fileSegInfo.getTemporaryFile());
          }
        }
      }
    } catch (Exception ex) {

      // DEBUG

      if (Debug.EnableInfo && hasDebug())
        Debug.println("## ObjIdLoader deleteFile() error, " + ex.toString());
    }
  }
  /**
   * Find the file segment for the specified virtual path
   *
   * @param virtPath String
   * @return FileSegment
   */
  protected final FileSegment findFileSegmentForPath(String virtPath) {

    // Get the file state for the virtual path

    FileState fstate = m_stateCache.findFileState(virtPath, false);
    if (fstate == null) return null;

    // Get the file segment

    FileSegmentInfo segInfo = null;
    FileSegment fileSeg = null;

    synchronized (fstate) {

      // Get the associated file segment

      segInfo = (FileSegmentInfo) fstate.findAttribute(DBFileSegmentInfo);
      fileSeg = new FileSegment(segInfo, true);
    }

    // Return the file segment

    return fileSeg;
  }
  @Override
  public NetworkFile createFile(SrvSession sess, TreeConnection tree, FileOpenParams params)
      throws IOException {
    ContentContext tctx = (ContentContext) tree.getContext();

    FileAccessToken token = null;

    if (tctx.hasStateCache()) {
      FileStateCache cache = tctx.getStateCache();
      FileState fstate = tctx.getStateCache().findFileState(params.getPath(), true);
      token = cache.grantFileAccess(params, fstate, FileStatus.NotExist);
      if (logger.isDebugEnabled()) {
        logger.debug("create file created lock token:" + token);
      }
    }

    try {
      NetworkFile newFile = diskInterface.createFile(sess, tree, params);

      if (tctx.hasStateCache()) {
        FileState fstate = tctx.getStateCache().findFileState(params.getPath(), true);
        fstate.setProcessId(params.getProcessId());
        fstate.setSharedAccess(params.getSharedAccess());

        // Indicate that the file is open
        fstate.setFileStatus(
            newFile.isDirectory() ? FileStatus.DirectoryExists : FileStatus.FileExists);
        fstate.setAllocationSize(params.getAllocationSize());

        if (newFile instanceof NodeRefNetworkFile) {
          NodeRefNetworkFile x = (NodeRefNetworkFile) newFile;
          x.setFileState(fstate);
        }

        if (newFile instanceof TempNetworkFile) {
          TempNetworkFile x = (TempNetworkFile) newFile;
          x.setFileState(fstate);
        }
      }

      if (newFile instanceof NodeRefNetworkFile) {
        NodeRefNetworkFile x = (NodeRefNetworkFile) newFile;
        x.setProcessId(params.getProcessId());
        x.setAccessToken(token);
      }

      if (newFile instanceof TempNetworkFile) {
        TempNetworkFile x = (TempNetworkFile) newFile;
        x.setAccessToken(token);
      }

      return newFile;

    } catch (IOException ie) {
      if (logger.isDebugEnabled()) {
        logger.debug("create file exception caught", ie);
      }
      if (tctx.hasStateCache() && token != null) {
        FileStateCache cache = tctx.getStateCache();
        FileState fstate = tctx.getStateCache().findFileState(params.getPath(), false);
        if (fstate != null && token != null) {
          if (logger.isDebugEnabled()) {
            logger.debug("create file release lock token:" + token);
          }
          cache.releaseFileAccess(fstate, token);
        }
      }
      throw ie;
    }
  }
  @Override
  public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params)
      throws IOException {
    ContentContext tctx = (ContentContext) tree.getContext();
    String path = params.getPath();

    FileAccessToken token = null;

    if (tctx.hasStateCache()) {
      if (!params.isDirectory()) {
        FileStateCache cache = tctx.getStateCache();
        FileState fstate = tctx.getStateCache().findFileState(params.getPath(), true);
        token = cache.grantFileAccess(params, fstate, FileStatus.Unknown);
        if (logger.isDebugEnabled()) {
          logger.debug("open file created lock token:" + token);
        }
      }
    }

    try {
      NetworkFile openFile = diskInterface.openFile(sess, tree, params);

      if (openFile instanceof ContentNetworkFile) {
        ContentNetworkFile x = (ContentNetworkFile) openFile;
        x.setProcessId(params.getProcessId());
        x.setAccessToken(token);
        if (tctx.hasStateCache()) {
          FileState fstate = tctx.getStateCache().findFileState(path, true);
          x.setFileState(fstate);
          fstate.setProcessId(params.getProcessId());
          fstate.setSharedAccess(params.getSharedAccess());
          fstate.setFileStatus(FileStatus.FileExists);
        }
      }

      if (openFile instanceof TempNetworkFile) {
        TempNetworkFile x = (TempNetworkFile) openFile;
        x.setAccessToken(token);
        // x.setProcessId( params.getProcessId());
        if (tctx.hasStateCache()) {
          FileState fstate = tctx.getStateCache().findFileState(path, true);
          x.setFileState(fstate);
          fstate.setFileStatus(FileStatus.FileExists);
          fstate.setProcessId(params.getProcessId());
          fstate.setSharedAccess(params.getSharedAccess());
        }
      }

      if (openFile instanceof AlfrescoFolder) {
        AlfrescoFolder x = (AlfrescoFolder) openFile;
        // x.setProcessId( param.getProcessId());
        if (tctx.hasStateCache()) {
          FileState fstate = tctx.getStateCache().findFileState(path, true);
          x.setFileState(fstate);
          fstate.setFileStatus(FileStatus.DirectoryExists);
          fstate.setProcessId(params.getProcessId());
          fstate.setSharedAccess(params.getSharedAccess());
        }
      }

      return openFile;
    } catch (IOException ie) {
      if (logger.isDebugEnabled()) {
        logger.debug("open file exception caught", ie);
      }
      if (tctx.hasStateCache() && token != null) {
        FileStateCache cache = tctx.getStateCache();
        FileState fstate = tctx.getStateCache().findFileState(params.getPath(), false);
        if (fstate != null) {
          if (logger.isDebugEnabled()) {
            logger.debug("open file release lock token:" + token);
          }
          cache.releaseFileAccess(fstate, token);
        }
      }
      throw ie;
    }
  }
  /**
   * Close the network file
   *
   * @param sess SrvSession
   * @param netFile NetworkFile
   * @exception IOException
   */
  public void closeFile(SrvSession sess, NetworkFile netFile) throws IOException {

    // Close the cached network file

    if (netFile instanceof CachedNetworkFile) {

      // Get the cached network file

      CachedNetworkFile cacheFile = (CachedNetworkFile) netFile;
      cacheFile.closeFile();

      // Get the file segment details

      FileSegment fileSeg = cacheFile.getFileSegment();

      // Check if the file data has been updated, if so then queue a file save

      if (fileSeg.isUpdated() && netFile.hasDeleteOnClose() == false) {

        // Set the modified date/time and file size for the file

        File tempFile = new File(fileSeg.getTemporaryFile());

        netFile.setModifyDate(tempFile.lastModified());
        netFile.setFileSize(tempFile.length());

        // Queue a file save request to save the data back to the repository, if not already queued

        if (fileSeg.isSaveQueued() == false) {

          // Create a file save request for the updated file segment

          SingleFileRequest fileReq =
              new SingleFileRequest(
                  FileRequest.SAVE,
                  cacheFile.getFileId(),
                  cacheFile.getStreamId(),
                  fileSeg.getInfo(),
                  netFile.getFullName(),
                  cacheFile.getFileState());

          // Check if there are any attributes to be added to the file request

          if (hasRequiredAttributes() && sess != null) {

            // Check if the user name is required

            if (m_reqAttributes.containsString(FileRequest.AttrUserName)) {

              // Add the user name attribute

              ClientInfo cInfo = sess.getClientInformation();
              String userName = "";

              if (cInfo != null && cInfo.getUserName() != null) userName = cInfo.getUserName();

              fileReq.addAttribute(new NameValue(FileRequest.AttrUserName, userName));
            }

            // Check if the protocol type is required

            if (m_reqAttributes.containsString(FileRequest.AttrProtocol)) {

              // Add the protocol type attribute

              fileReq.addAttribute(new NameValue(FileRequest.AttrProtocol, sess.getProtocolName()));
            }
          }

          // Set the file segment status

          fileSeg.setStatus(FileSegmentInfo.SaveWait, true);

          // Queue the file save request

          queueFileRequest(fileReq);
        } else if (Debug.EnableInfo && hasDebug()) {

          // DEBUG

          Debug.println("## FileLoader Save already queued for " + fileSeg);
        }
      }

      // Update the cache timeout for the temporary file if there are no references to the file. If
      // the file was
      // opened for sequential access only it will be expired quicker.

      else if (cacheFile.getFileState().getOpenCount() == 0) {

        // If the file was opened for sequential access only then we can delete it from the
        // temporary area
        // sooner

        long tmo = System.currentTimeMillis();

        if (cacheFile.isSequentialOnly()) tmo += SequentialFileExpire;
        else tmo += m_stateCache.getFileStateExpireInterval();

        // Set the file state expiry, the local file data will be deleted when the file state
        // expires (if there
        // are still no references to the file).

        cacheFile.getFileState().setExpiryTime(tmo);
      }

      // If the database is not online and the file is marked for delete then queue a delete file
      // request to do
      // the
      // delete when the database is back online

      if (m_dbCtx.isAvailable() == false && netFile.hasDeleteOnClose()) {

        // Queue an offline delete request for the file

        DeleteFileRequest deleteReq =
            new DeleteFileRequest(
                cacheFile.getFileId(),
                cacheFile.getStreamId(),
                fileSeg.getTemporaryFile(),
                cacheFile.getFullNameStream(),
                cacheFile.getFileState());
        m_dbCtx.addOfflineFileDelete(deleteReq);

        // DEBUG

        if (Debug.EnableInfo && hasDebug())
          Debug.println("## FileLoader queued offline delete, " + deleteReq);
      }
    }
  }
  /**
   * Create a network file for the specified file
   *
   * @param params FileOpenParams
   * @param fid int
   * @param stid int
   * @param did int
   * @param create boolean
   * @param dir boolean
   * @exception IOException
   * @exception FileNotFoundException
   */
  public NetworkFile openFile(
      FileOpenParams params, int fid, int stid, int did, boolean create, boolean dir)
      throws IOException, FileNotFoundException {

    // Split the file name to get the name only

    String[] paths = FileName.splitPath(params.getPath());
    String name = paths[1];

    if (params.isStream()) name = name + params.getStreamName();

    // Find, or create, the file state for the file/directory

    FileState fstate = m_stateCache.findFileState(params.getFullPath(), true);
    fstate.setExpiryTime(System.currentTimeMillis() + m_stateCache.getFileStateExpireInterval());

    // Check if the file is a directory

    DBNetworkFile netFile = null;

    if (dir == false) {

      // Create the network file and associated file segment

      CachedNetworkFile cacheFile = createNetworkFile(fstate, params, name, fid, stid, did);
      netFile = cacheFile;

      // Check if the file is being opened for sequential access and the data has not yet been
      // loaded

      FileSegment fileSeg = cacheFile.getFileSegment();

      if (create == true || params.isOverwrite() == true) {

        // Indicate that the file data is available, this is a new file or the existing file is
        // being
        // overwritten so there is no data to load.

        fileSeg.setStatus(FileSegmentInfo.Available);
      } else if (params.isSequentialAccessOnly() && fileSeg.isDataLoading() == false) {

        synchronized (cacheFile.getFileState()) {

          // Create the temporary file

          cacheFile.openFile(create);
          cacheFile.closeFile();

          // Queue a file data load request

          if (fileSeg.isDataLoading() == false)
            queueFileRequest(
                new SingleFileRequest(
                    FileRequest.LOAD,
                    cacheFile.getFileId(),
                    cacheFile.getStreamId(),
                    fileSeg.getInfo(),
                    cacheFile.getFullName(),
                    fstate));
        }

        // DEBUG

        if (Debug.EnableInfo && hasDebug())
          Debug.println(
              "ObjIdLoader Queued file load, SEQUENTIAL access, file=" + cacheFile.getFullName());
      }
    } else {

      // Create a placeholder network file for the directory

      netFile = new DirectoryNetworkFile(name, fid, did, m_stateCache.getFileStateProxy(fstate));

      // Debug

      if (Debug.EnableInfo && hasDebug())
        Debug.println("ObjIdLoader.openFile() DIR name=" + name + ", state=" + fstate);
    }

    // Return the network file

    return netFile;
  }
  /**
   * Create a file segment to load/save the file data
   *
   * @param state FileState
   * @param params FileOpenParams
   * @param fname String
   * @param fid int
   * @param stid int
   * @param did int
   * @return CachedNetworkFile
   * @exception IOException
   */
  private final CachedNetworkFile createNetworkFile(
      FileState state, FileOpenParams params, String fname, int fid, int stid, int did)
      throws IOException {

    // The file state is used to synchronize the creation of the file segment as there may be other
    // sessions opening the file at the same time. We have to be careful that only one thread
    // creates the
    // file segment.

    FileSegment fileSeg = null;
    CachedNetworkFile netFile = null;

    synchronized (state) {

      // Check if the file segment has been attached to the file state

      FileSegmentInfo fileSegInfo = (FileSegmentInfo) state.findAttribute(DBFileSegmentInfo);
      if (fileSegInfo == null) {

        // Check if we need to create a new temporary sub-drectory

        if (m_tempCount++ >= m_tempMax) createNewTempDirectory();

        // Create a unique temporary file name

        StringBuffer tempName = new StringBuffer();
        tempName.append(getTempFilePrefix());
        tempName.append(fid);

        if (stid > 0) {
          tempName.append("_");
          tempName.append(stid);

          // DEBUG

          if (Debug.EnableInfo && hasDebug()) Debug.println("## Temp file for stream ##");
        }

        tempName.append(".tmp");

        // Create a new file segment

        fileSegInfo = new FileSegmentInfo();
        fileSeg =
            FileSegment.createSegment(
                fileSegInfo, tempName.toString(), m_curTempDir, params.isReadOnlyAccess() == false);

        // Add the segment to the file state cache

        state.addAttribute(DBFileSegmentInfo, fileSegInfo);

        // Check if the file is zero length, if so then set the file segment state to indicate it is
        // available

        DBFileInfo finfo = (DBFileInfo) state.findAttribute(FileState.FileInformation);
        if (finfo != null && finfo.getSize() == 0) fileSeg.setStatus(FileSegmentInfo.Available);
      } else {

        // Create the file segment to map to the existing temporary file

        fileSeg = new FileSegment(fileSegInfo, params.isReadOnlyAccess() == false);

        // Check if the temporary file exists, if not then create it

        File tempFile = new File(fileSeg.getTemporaryFile());
        if (tempFile.exists() == false) {

          // Create the temporary file

          tempFile.createNewFile();

          // Reset the file segment state to indicate a file load is required

          fileSeg.setStatus(FileSegmentInfo.Initial);
        }
      }

      // Create the new network file

      netFile =
          new CachedNetworkFile(
              fname, fid, stid, did, m_stateCache.getFileStateProxy(state), fileSeg, this);

      netFile.setGrantedAccess(
          params.isReadOnlyAccess() ? NetworkFile.READONLY : NetworkFile.READWRITE);
      netFile.setSequentialOnly(params.isSequentialAccessOnly());
      netFile.setAttributes(params.getAttributes());
      netFile.setFullName(params.getPath());

      if (stid != 0) netFile.setStreamName(params.getStreamName());
    }

    // Return the network file

    return netFile;
  }
  /**
   * File state has expired. The listener can control whether the file state is removed from the
   * cache, or not.
   *
   * @param state FileState
   * @return true to remove the file state from the cache, or false to leave the file state in the
   *     cache
   */
  public boolean fileStateExpired(FileState state) {

    // Check if the file state has an associated file segment

    FileSegmentInfo segInfo = (FileSegmentInfo) state.findAttribute(DBFileSegmentInfo);
    boolean expire = true;

    if (segInfo != null) {

      // Check if the file has a request queued

      if (segInfo.isQueued() == false) {

        try {

          // Delete the temporary file and reset the segment status so that the data may be loaded
          // again
          // if required.

          if (segInfo.hasStatus() != FileSegmentInfo.Initial) {

            // Delete the temporary file

            try {
              segInfo.deleteTemporaryFile();
            } catch (IOException ex) {

              // DEBUG

              if (Debug.EnableError) {
                Debug.println("Delete temp file error: " + ex.toString());
                File tempFile = new File(segInfo.getTemporaryFile());
                Debug.println(
                    "  TempFile file="
                        + tempFile.getAbsolutePath()
                        + ", exists="
                        + tempFile.exists());
                Debug.println("  FileState state=" + state);
                Debug.println("  FileSegmentInfo segInfo=" + segInfo);
                Debug.println("  StateCache size=" + m_stateCache.numberOfStates());
              }
            }

            // Remove the file segment, reset the file segment back to the initial state

            state.removeAttribute(DBFileSegmentInfo);
            segInfo.setStatus(FileSegmentInfo.Initial);

            // Reset the file state to indicate file data load required

            state.setDataStatus(FileState.FILE_LOADWAIT);

            // Check if the temporary file sub-directory is now empty, and it is not the current
            // temporary
            // sub-directory

            if (segInfo.getTemporaryFile().startsWith(m_curTempName) == false) {

              // Check if the sub-directory is empty

              File tempFile = new File(segInfo.getTemporaryFile());
              File subDir = tempFile.getParentFile();
              String[] files = subDir.list();
              if (files == null || files.length == 0) {

                // Delete the sub-directory

                subDir.delete();

                // DEBUG

                if (Debug.EnableInfo && hasDebug())
                  Debug.println(
                      "$$ Deleted temporary directory "
                          + subDir.getPath()
                          + ", curTempName="
                          + m_curTempName
                          + ", tempFile="
                          + segInfo.getTemporaryFile());
              }
            }

            // Indicate that the file state should not be deleted

            expire = false;

            // Debug

            if (Debug.EnableInfo && hasDebug())
              Debug.println(
                  "$$ Deleted temporary file " + segInfo.getTemporaryFile() + " [EXPIRED] $$");
          }

          // If the file state is not to be deleted reset the file state expiration timer

          if (expire == false)
            state.setExpiryTime(
                System.currentTimeMillis() + m_stateCache.getFileStateExpireInterval());
        } catch (Exception ex) {

          // DEBUG

          if (Debug.EnableError) {
            Debug.println("$$  " + ex.toString());
            Debug.println("  state=" + state);
          }
        }
      } else {

        // DEBUG

        if (hasDebug()) {
          File tempFile = new File(segInfo.getTemporaryFile());
          if (tempFile.exists() == false) {
            Debug.println("== Skipped file state, queued " + state);
            Debug.println("   File seg=" + segInfo);
          }
        }

        // File state is queued, do not expire

        expire = false;
      }
    } else if (state.isDirectory()) {

      // Nothing to do when it's a directory, just allow it to expire

      expire = true;
    } else if (Debug.EnableInfo && hasDebug()) Debug.println("$$ Expiring state=" + state);

    // Return true if the file state can be expired

    return expire;
  }
  /**
   * Start the file loader
   *
   * @param ctx DeviceContext
   */
  public void startLoader(DeviceContext ctx) {

    // Get the file state cache from the context

    m_stateCache = getContext().getStateCache();

    // Add the file loader as a file state listener so that we can cleanup temporary data files

    m_stateCache.addStateListener(this);

    // Get the database interface

    DBQueueInterface dbQueue = null;

    if (getContext().getDBInterface() instanceof DBQueueInterface)
      dbQueue = (DBQueueInterface) getContext().getDBInterface();
    else throw new RuntimeException("Database interface does not implement queue interface");

    // Perform a queue cleanup before starting the thread pool. This will check the temporary cache
    // area and delete
    // files that are not part of a queued save/transaction save request.

    FileRequestQueue recoveredQueue = null;

    try {

      // Cleanup the temporary cache area and queue

      recoveredQueue =
          dbQueue.performQueueCleanup(m_tempDir, TempDirPrefix, TempFilePrefix, JarFilePrefix);

      // DEBUG

      if (recoveredQueue != null && Debug.EnableInfo && hasDebug())
        Debug.println(
            "[DBLoader] Cleanup recovered "
                + recoveredQueue.numberOfRequests()
                + " pending save files");
    } catch (DBException ex) {

      // DEBUG

      if (Debug.EnableError && hasDebug()) Debug.println(ex);
    }

    // Check if there are any file save requests pending in the queue database

    FileRequestQueue saveQueue = new FileRequestQueue();

    try {
      dbQueue.loadFileRequests(1, FileRequest.SAVE, saveQueue, 1);
    } catch (DBException ex) {
    }

    // Create the background load/save thread pool

    m_backgroundLoader = new BackgroundLoadSave("DBLdr", dbQueue, m_stateCache, this);

    m_backgroundLoader.setReadWorkers(m_readWorkers);
    m_backgroundLoader.setWriteWorkers(m_writeWorkers);

    m_backgroundLoader.setDebug(m_threadDebug);

    // Start the file loader threads, start the request loading if there are pending file save
    // requests

    m_backgroundLoader.startThreads(saveQueue.numberOfRequests());

    // Queue the recovered file save requests

    if (recoveredQueue != null) {

      // Queue the file save requests

      while (recoveredQueue.numberOfRequests() > 0)
        queueFileRequest(recoveredQueue.removeRequestNoWait());
    }
  }