@Override
  public synchronized void mkdir(SyndicateFSPath path) throws IOException {
    if (path == null) {
      LOG.error("path is null");
      throw new IllegalArgumentException("path is null");
    }

    SyndicateFSPath absPath = getAbsolutePath(path);
    SyndicateFSFileStatus status = getFileStatus(absPath.getParent());
    if (status == null) {
      LOG.error("parent directory does not exist : " + absPath.getPath());
      throw new IOException("parent directory does not exist : " + absPath.getPath());
    }

    Future<ClientResponse> makeDirFuture = this.client.makeDir(absPath.getPath(), 0744);
    if (makeDirFuture != null) {
      try {
        this.client.processMakeDir(makeDirFuture);
      } catch (Exception ex) {
        LOG.error("exception occurred", ex);
        throw new IOException(ex);
      }
    } else {
      throw new IOException("Can not create a REST client");
    }
  }
  @Override
  public SyndicateFSInputStream getFileInputStream(SyndicateFSPath path)
      throws FileNotFoundException, IOException {
    if (path == null) {
      LOG.error("path is null");
      throw new IllegalArgumentException("path is null");
    }

    SyndicateFSPath absPath = getAbsolutePath(path);
    SyndicateFSFileStatus status = getFileStatus(absPath);
    if (status == null) {
      LOG.error("Can not open the file to read : " + absPath.getPath());
      throw new FileNotFoundException("Can not open the file to read : " + absPath.getPath());
    }

    SyndicateFSFileHandle handle = getFileHandle(status, true);
    if (handle == null) {
      LOG.error("Can not open the file to read : " + absPath.getPath());
      throw new IOException("Can not open the file to read : " + absPath.getPath());
    }

    SyndicateFSInputStream is = new SyndicateFSInputStream(handle);
    this.openInputStream.add(is);
    return is;
  }
  @Override
  public synchronized String getExtendedAttr(SyndicateFSPath path, String name) throws IOException {
    if (path == null) {
      LOG.error("path is null");
      throw new IllegalArgumentException("path is null");
    }

    SyndicateFSPath absPath = getAbsolutePath(path);
    SyndicateFSFileStatus status = getFileStatus(absPath);
    if (status == null) {
      LOG.error("file not exist");
      throw new IOException("file not exist : " + path.getPath());
    }

    Future<ClientResponse> getXattrFuture = this.client.getXattr(absPath.getPath(), name);
    if (getXattrFuture != null) {
      try {
        Xattr processGetXattr = this.client.processGetXattr(getXattrFuture);
        return processGetXattr.getValue();
      } catch (Exception ex) {
        LOG.error("exception occurred", ex);
        throw new IOException(ex);
      }
    } else {
      throw new IOException("Can not create a REST client");
    }
  }
  @Override
  public synchronized String[] listExtendedAttrs(SyndicateFSPath path) throws IOException {
    if (path == null) {
      LOG.error("path is null");
      throw new IllegalArgumentException("path is null");
    }

    SyndicateFSPath absPath = getAbsolutePath(path);
    SyndicateFSFileStatus status = getFileStatus(absPath);
    if (status == null) {
      LOG.error("file not exist");
      throw new IOException("file not exist : " + path.getPath());
    }

    Future<ClientResponse> listXattrFuture = this.client.listXattr(absPath.getPath());
    if (listXattrFuture != null) {
      try {
        XattrKeyList processListXattr = this.client.processListXattr(listXattrFuture);
        return processListXattr.getKeys().toArray(new String[0]);
      } catch (Exception ex) {
        LOG.error("exception occurred", ex);
        throw new IOException(ex);
      }
    } else {
      throw new IOException("Can not create a REST client");
    }
  }
  private SyndicateFSFileStatus getFileStatus(SyndicateFSPath abspath) throws IOException {
    if (abspath == null) {
      LOG.error("Can not get FileStatus from null abspath");
      throw new IllegalArgumentException("Can not get FileStatus from null abspath");
    }

    // check memory cache
    SyndicateFSFileStatus cached_status = this.filestatusCache.get(abspath);

    if (cached_status != null && !cached_status.isDirty()) {
      return cached_status;
    }

    // not in memory cache
    StatRaw statRaw = null;
    try {
      Future<ClientResponse> statFuture = this.client.getStat(abspath.getPath());
      if (statFuture != null) {
        statRaw = this.client.processGetStat(statFuture);
      } else {
        throw new IOException("Can not create a REST client");
      }
    } catch (FileNotFoundException ex) {
      // silent
      return null;
    } catch (Exception ex) {
      LOG.error("exception occurred", ex);
      throw new IOException(ex);
    }

    SyndicateFSFileStatus status = new SyndicateFSFileStatus(this, abspath, statRaw);
    this.filestatusCache.put(abspath, status);

    return status;
  }
  @Override
  public synchronized boolean delete(SyndicateFSPath path)
      throws FileNotFoundException, IOException {
    if (path == null) {
      LOG.error("path is null");
      throw new IllegalArgumentException("path is null");
    }

    SyndicateFSPath absPath = getAbsolutePath(path);
    SyndicateFSFileStatus status = getFileStatus(absPath);
    if (status == null) {
      LOG.error("file not exist");
      throw new FileNotFoundException("file not exist : " + path.getPath());
    }

    if (status.isFile()) {
      Future<ClientResponse> unlinkFuture = this.client.unlink(absPath.getPath());
      if (unlinkFuture != null) {
        try {
          this.client.processUnlink(unlinkFuture);
        } catch (RestfulException ex) {
          LOG.error("exception occurred", ex);
          throw new IOException(ex);
        }
      } else {
        throw new IOException("Can not create a REST client");
      }
    } else if (status.isDirectory()) {
      Future<ClientResponse> removeDirFuture = this.client.removeDir(absPath.getPath());
      if (removeDirFuture != null) {
        try {
          this.client.processRemoveDir(removeDirFuture);
        } catch (RestfulException ex) {
          LOG.error("exception occurred", ex);
          throw new IOException(ex);
        }
      } else {
        throw new IOException("Can not create a REST client");
      }
    } else {
      LOG.error("Can not delete from unknown status");
      throw new IOException("Can not delete from unknown status");
    }

    this.filestatusCache.remove(absPath);
    return true;
  }
  private synchronized SyndicateFSFileHandle createNewFile(SyndicateFSPath abspath)
      throws IOException {
    if (abspath == null) {
      LOG.error("abspath is null");
      throw new IllegalArgumentException("abspath is null");
    }

    if (abspath.getParent() != null) {
      SyndicateFSFileStatus parent = getFileStatus(abspath.getParent());
      if (parent == null) {
        LOG.error("Parent directory does not exist");
        throw new IOException("Parent directory does not exist");
      }

      if (!parent.isDirectory()) {
        LOG.error("Parent directory does not exist");
        throw new IOException("Parent directory does not exist");
      }
    }

    LOG.info("creating a file - " + abspath.getPath());

    Future<ClientResponse> openFuture = this.client.open(abspath.getPath(), "w");
    if (openFuture != null) {
      try {
        FileDescriptor fi = this.client.processOpen(openFuture);
        SyndicateFSFileStatus status = new SyndicateFSFileStatus(this, abspath);
        return new SyndicateFSFileHandle(this, status, fi, false);
      } catch (Exception ex) {
        LOG.error("exception occurred", ex);
        throw new IOException(ex);
      }
    } else {
      throw new IOException("Can not create a REST client");
    }
  }
  @Override
  public SyndicateFSOutputStream getFileOutputStream(SyndicateFSPath path) throws IOException {
    if (path == null) {
      LOG.error("path is null");
      throw new IllegalArgumentException("path is null");
    }

    SyndicateFSPath absPath = getAbsolutePath(path);
    SyndicateFSFileStatus status = getFileStatus(absPath);

    if (status != null) {
      if (!status.isFile()) {
        LOG.error("Can not open the file to write (is directory) : " + absPath.getPath());
        throw new IOException(
            "Can not open the file to write (is directory) : " + absPath.getPath());
      }

      SyndicateFSFileHandle handle = getFileHandle(status, false);
      if (handle == null) {
        LOG.error("Can not open the file to write : " + absPath.getPath());
        throw new IOException("Can not open the file to write : " + absPath.getPath());
      }

      SyndicateFSOutputStream os = new SyndicateFSOutputStream(handle);
      this.openOutputStream.add(os);
      return os;
    } else {
      // create new file
      SyndicateFSFileHandle handle = createNewFile(absPath);
      if (handle == null) {
        LOG.error("Can not create a file to write : " + absPath.getPath());
        throw new IOException("Can not create a file to write : " + absPath.getPath());
      }

      SyndicateFSOutputStream os = new SyndicateFSOutputStream(handle);
      this.openOutputStream.add(os);
      return os;
    }
  }
  @Override
  public synchronized String[] readDirectoryEntries(SyndicateFSPath path)
      throws FileNotFoundException, IOException {
    if (path == null) {
      LOG.error("path is null");
      throw new IllegalArgumentException("path is null");
    }

    SyndicateFSPath absPath = getAbsolutePath(path);
    SyndicateFSFileStatus status = getFileStatus(absPath);
    if (status == null) {
      LOG.error("directory does not exist : " + absPath.getPath());
      throw new FileNotFoundException("directory does not exist : " + absPath.getPath());
    }

    List<String> entries = new ArrayList<String>();
    Future<ClientResponse> readDirFuture = this.client.listDir(absPath.getPath());
    if (readDirFuture != null) {
      try {
        DirectoryEntries processReadDir = this.client.processListDir(readDirFuture);

        // remove duplicates
        Map<String, StatRaw> entryTable = new HashMap<String, StatRaw>();

        // need to remove duplicates
        int entry_cnt = 0;
        for (StatRaw statRaw : processReadDir.getEntries()) {
          entry_cnt++;
          if (entry_cnt <= 2) {
            // ignore . and ..
            continue;
          }

          StatRaw eStatRaw = entryTable.get(statRaw.getName());
          if (eStatRaw == null) {
            // first
            entryTable.put(statRaw.getName(), statRaw);
          } else {
            if (eStatRaw.getVersion() <= statRaw.getVersion()) {
              // replace
              entryTable.remove(statRaw.getName());
              entryTable.put(statRaw.getName(), statRaw);
            }
          }
        }

        entries.addAll(entryTable.keySet());

        // put to memory cache
        for (StatRaw statRaw : entryTable.values()) {
          SyndicateFSPath entryPath = new SyndicateFSPath(absPath, statRaw.getName());
          this.filestatusCache.remove(entryPath);
          SyndicateFSFileStatus entryStatus = new SyndicateFSFileStatus(this, absPath, statRaw);
          this.filestatusCache.put(entryPath, entryStatus);
        }
      } catch (Exception ex) {
        LOG.error("exception occurred", ex);
        throw new IOException(ex);
      }
    } else {
      throw new IOException("Can not create a REST client");
    }

    return entries.toArray(new String[0]);
  }
  @Override
  public synchronized void rename(SyndicateFSPath path, SyndicateFSPath newpath)
      throws FileNotFoundException, IOException {
    if (path == null) {
      LOG.error("path is null");
      throw new IllegalArgumentException("path is null");
    }
    if (newpath == null) {
      LOG.error("newpath is null");
      throw new IllegalArgumentException("newpath is null");
    }

    SyndicateFSPath absPath = getAbsolutePath(path);
    SyndicateFSPath absNewPath = getAbsolutePath(newpath);

    SyndicateFSFileStatus status = getFileStatus(absPath);
    SyndicateFSFileStatus newStatus = getFileStatus(absNewPath);
    SyndicateFSFileStatus newStatusParent = getFileStatus(absNewPath.getParent());

    if (status == null) {
      LOG.error("source file does not exist : " + path.getPath());
      throw new FileNotFoundException("source file does not exist : " + path.getPath());
    }

    if (newStatus != null) {
      LOG.error("target file already exists : " + newpath.getPath());
      throw new IOException("target file already exists : " + newpath.getPath());
    }

    if (newStatusParent == null) {
      LOG.error("parent directory of target file does not exist : " + newpath.getPath());
      throw new IOException(
          "parent directory of target file does not exist : " + newpath.getPath());
    }

    Future<ClientResponse> renameFuture =
        this.client.rename(absPath.getPath(), absNewPath.getPath());
    if (renameFuture != null) {
      try {
        this.client.processRename(renameFuture);
      } catch (Exception ex) {
        LOG.error("exception occurred", ex);
        throw new IOException(ex);
      }
    } else {
      throw new IOException("Can not create a REST client");
    }

    this.filestatusCache.remove(absPath);
  }