/**
   * ファイルデータをストレージに保存します.
   *
   * @param metadata ファイルデータを含むurlTreeメタデータオブジェクト
   * @param ctx urlTreeコンテキストオブジェクト
   */
  @Override
  public void save(UrlTreeMetaData<InputStream> metadata, UrlTreeContext ctx)
      throws BadContentException {

    String localFileName = metadata.getAbsolutePath();

    logger.debug("saving " + localFileName);
    Path f = this.generateFileObj(localFileName);

    InputStream b = metadata.getData();
    // nullの場合は書き換えない。
    if (b == null) {
      return;
    }
    try {
      Path parent = f.getParent();

      // 書き込む場所がなければ親ディレクトリを作成
      if (Files.notExists(parent)) {
        Files.createDirectories(parent);
      }
      Files.copy(b, f, StandardCopyOption.REPLACE_EXISTING);
    } catch (IOException e) {
      throw new GenericResourceException(e);
    }
  }
  /**
   * キー情報から実ファイルデータを取得し、それらからメタデータを生成して返します.
   *
   * @param key ファイルデータのキー情報
   * @return キーから生成したメタデータ
   */
  @Override
  public UrlTreeMetaData<InputStream> generateMetaDataFromReal(String key, UrlTreeContext ctx)
      throws BadContentException {
    if (!this.canLoad(key, ctx)) {
      throw new IllegalArgumentException(
          "Cannot Load it. check before can it load with canLoad(key): " + key);
    }
    Path p = this.generateFileObj(key);
    File f = p.toFile();
    long lastModified = f.lastModified();

    logger.trace("[デバッグ]ファイル名とファイルのlastModified: " + p.toString() + ":" + lastModified);

    UrlTreeMetaData<InputStream> md = new UrlTreeMetaData<>();
    md.setDirectory(Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS));
    md.setFilename(key);
    md.setOwnerId(ctx.getUserName());
    md.setGroupId(ctx.getPrimaryGroup());
    md.setCreatedTime(lastModified);
    md.setUpdatedTime(lastModified);
    md.setPermission(ctx.getDefaultPermission());
    String contentType = new MimetypesFileTypeMap().getContentType(p.getFileName().toString());
    md.setContentType(contentType);

    return md;
  }
  /**
   * ファイルデータをストレージからロードします.
   *
   * @param metadata urlTreeメタデータオブジェクト
   * @param ctx urlTreeコンテキストオブジェクト
   * @return ロードされたファイルデータ
   */
  @Override
  public InputStream load(UrlTreeMetaData<InputStream> metadata, UrlTreeContext ctx)
      throws BadContentException, TargetNotFoundException {
    Path f = this.generateFileObj(metadata.getAbsolutePath());

    if (!Files.exists(f) || !Files.isReadable(f)) {
      throw new TargetNotFoundException("cannot read real file");
    }

    InputStream contents;
    try {
      contents = Files.newInputStream(f);
    } catch (IOException e) {
      throw new GenericResourceException(e);
    }

    String contentType = new MimetypesFileTypeMap().getContentType(f.getFileName().toString());
    metadata.setContentType(contentType);

    return contents;
  }
  /**
   * ディレクトリデータをストレージに保存(ディレクトリを作成)します.
   *
   * @param metadata ディレクトリデータを含むurlTreeメタデータオブジェクト
   * @param ctx urlTreeコンテキストオブジェクト
   */
  @Override
  public void mkdir(UrlTreeMetaData<InputStream> metadata, UrlTreeContext ctx)
      throws BadContentException, FileAlreadyExistsException {
    Path f = generateFileObj(metadata.getAbsolutePath());
    if (Files.exists(f)) {
      throw new FileAlreadyExistsException("file exists");
    }

    try {
      Files.createDirectories(f);
    } catch (IOException e) {
      logger.error("mkdir failure", e);
      throw new RuntimeException(e);
    }
  }
 /**
  * ディレクトリデータをストレージから削除します.
  *
  * @param metadata ディレクトリデータを含むurlTreeメタデータオブジェクト
  * @param ctx urlTreeコンテキストオブジェクト
  */
 @Override
 public void delete(UrlTreeMetaData<InputStream> metadata, UrlTreeContext ctx)
     throws BadContentException {
   Path f = generateFileObj(metadata.getAbsolutePath());
   logger.debug("delete: " + f.getFileName());
   try {
     boolean deleted = Files.deleteIfExists(f);
     if (!deleted) {
       throw new IOException("file not exists");
     }
   } catch (IOException e) {
     // TargetNotFoundまたはBadContentでもよいか
     throw new GenericResourceException("cannot delete file", e);
   }
 }
  /**
   * ディレクトリデータをストレージから削除します.
   *
   * @param metadata ディレクトリデータを含むurlTreeメタデータオブジェクト
   * @param ctx urlTreeコンテキストオブジェクト
   */
  @Override
  public void rmdir(UrlTreeMetaData<InputStream> metadata, UrlTreeContext ctx)
      throws BadContentException, FileNotFoundException {
    Path f = generateFileObj(metadata.getAbsolutePath());
    logger.debug("called rmdir: " + f.getFileName());
    if (!Files.exists(f)) {
      throw new FileNotFoundException("file not exists");
    }

    try {
      Files.delete(f);
    } catch (IOException e) {
      logger.error("rmdir failure", e);
      throw new RuntimeException(e);
    }
  }
  @Override
  public void move(UrlTreeMetaData<InputStream> metadata, String dstDir, UrlTreeContext ctx)
      throws BadContentException {

    String srcPathName = metadata.getAbsolutePath();
    Path srcPath = this.generateFileObj(srcPathName);
    Path dstPath = this.generateFileObj(dstDir);

    logger.debug("move: " + srcPath.toAbsolutePath() + " to " + dstPath.toAbsolutePath());

    try {
      Files.move(
          srcPath,
          dstPath.resolve(srcPath.getFileName()),
          StandardCopyOption.ATOMIC_MOVE,
          StandardCopyOption.REPLACE_EXISTING);
    } catch (IOException e) {
      throw new GenericResourceException("cannot copy file", e);
    }
  }