@Override
 public void run() {
   for (Map.Entry<String, ConfigFileInfo> entry : watchedFileMap.entrySet()) {
     String filePath = entry.getKey();
     ConfigFileInfo configFileInfo = entry.getValue();
     try {
       File file = new File(filePath);
       long lastModified = file.lastModified();
       Preconditions.checkArgument(lastModified > 0L);
       if (lastModified != configFileInfo.lastModifiedTimestampMillis) {
         configFileInfo.lastModifiedTimestampMillis = lastModified;
         ByteSource byteSource = Files.asByteSource(file);
         HashCode newContentHash = byteSource.hash(HASH_FUNCTION);
         if (!newContentHash.equals(configFileInfo.contentHash)) {
           configFileInfo.contentHash = newContentHash;
           LOG.info("File {} was modified at {}, notifying watchers.", filePath, lastModified);
           byte[] newContents = byteSource.read();
           for (Function<byte[], Void> watchers : configFileInfo.changeWatchers) {
             try {
               watchers.apply(newContents);
             } catch (Exception e) {
               LOG.error(
                   "Exception in watcher callback for {}, ignoring. New file contents were: {}",
                   filePath,
                   new String(newContents, Charsets.UTF_8),
                   e);
             }
           }
         } else {
           LOG.info(
               "File {} was modified at {} but content hash is unchanged.",
               filePath,
               lastModified);
         }
       } else {
         LOG.debug("File {} not modified since {}", filePath, lastModified);
       }
     } catch (Exception e) {
       // We catch and log exceptions related to the update of any specific file, but
       // move on so others aren't affected. Issues can happen for example if the watcher
       // races with an external file replace operation; in that case, the next run should
       // pick up the update.
       // TODO: Consider adding a metric to track this so we can alert on failures.
       LOG.error("Config update check failed for {}", filePath, e);
     }
   }
 }
  /**
   * Adds a watch on the specified file. The file must exist, otherwise a FileNotFoundException is
   * returned. If the file is deleted after a watch is established, the watcher will log errors but
   * continue to monitor it, and resume watching if it is recreated.
   *
   * @param filePath path to the file to watch.
   * @param onUpdate function to call when a change is detected to the file. The entire contents of
   *     the file will be passed in to the function. Note that onUpdate will be called once before
   *     this call completes, which facilities initial load of data. This callback is executed
   *     synchronously on the watcher thread - it is important that the function be non-blocking.
   */
  public synchronized void addWatch(String filePath, Function<byte[], Void> onUpdate)
      throws IOException {
    MorePreconditions.checkNotBlank(filePath);
    Preconditions.checkNotNull(onUpdate);

    // Read the file and make the initial onUpdate call.
    File file = new File(filePath);
    ByteSource byteSource = Files.asByteSource(file);
    onUpdate.apply(byteSource.read());

    // Add the file to our map if it isn't already there, and register the new change watcher.
    ConfigFileInfo configFileInfo = watchedFileMap.get(filePath);
    if (configFileInfo == null) {
      configFileInfo = new ConfigFileInfo(file.lastModified(), byteSource.hash(HASH_FUNCTION));
      watchedFileMap.put(filePath, configFileInfo);
    }
    configFileInfo.changeWatchers.add(onUpdate);
  }
  @Override
  public Blob getBlob(final String container, final String key) {
    BlobBuilder builder = blobBuilders.get();
    builder.name(key);
    File file = getFileForBlobKey(container, key);
    ByteSource byteSource;

    if (getDirectoryBlobSuffix(key) != null) {
      logger.debug("%s - %s is a directory", container, key);
      byteSource = ByteSource.empty();
    } else {
      byteSource = Files.asByteSource(file);
    }
    try {
      String cacheControl = null;
      String contentDisposition = null;
      String contentEncoding = null;
      String contentLanguage = null;
      String contentType = null;
      HashCode hashCode = null;
      Date expires = null;
      ImmutableMap.Builder<String, String> userMetadata = ImmutableMap.builder();

      UserDefinedFileAttributeView view = getUserDefinedFileAttributeView(file.toPath());
      if (view != null) {
        Set<String> attributes = ImmutableSet.copyOf(view.list());

        cacheControl = readStringAttributeIfPresent(view, attributes, XATTR_CACHE_CONTROL);
        contentDisposition =
            readStringAttributeIfPresent(view, attributes, XATTR_CONTENT_DISPOSITION);
        contentEncoding = readStringAttributeIfPresent(view, attributes, XATTR_CONTENT_ENCODING);
        contentLanguage = readStringAttributeIfPresent(view, attributes, XATTR_CONTENT_LANGUAGE);
        contentType = readStringAttributeIfPresent(view, attributes, XATTR_CONTENT_TYPE);
        if (contentType == null && autoDetectContentType) {
          contentType = probeContentType(file.toPath());
        }
        if (attributes.contains(XATTR_CONTENT_MD5)) {
          ByteBuffer buf = ByteBuffer.allocate(view.size(XATTR_CONTENT_MD5));
          view.read(XATTR_CONTENT_MD5, buf);
          hashCode = HashCode.fromBytes(buf.array());
        }
        if (attributes.contains(XATTR_EXPIRES)) {
          ByteBuffer buf = ByteBuffer.allocate(view.size(XATTR_EXPIRES));
          view.read(XATTR_EXPIRES, buf);
          buf.flip();
          expires = new Date(buf.asLongBuffer().get());
        }
        for (String attribute : attributes) {
          if (!attribute.startsWith(XATTR_USER_METADATA_PREFIX)) {
            continue;
          }
          String value = readStringAttributeIfPresent(view, attributes, attribute);
          userMetadata.put(attribute.substring(XATTR_USER_METADATA_PREFIX.length()), value);
        }

        builder
            .payload(byteSource)
            .cacheControl(cacheControl)
            .contentDisposition(contentDisposition)
            .contentEncoding(contentEncoding)
            .contentLanguage(contentLanguage)
            .contentLength(byteSource.size())
            .contentMD5(hashCode)
            .contentType(contentType)
            .expires(expires)
            .userMetadata(userMetadata.build());
      } else {
        builder
            .payload(byteSource)
            .contentLength(byteSource.size())
            .contentMD5(byteSource.hash(Hashing.md5()).asBytes());
      }
    } catch (IOException e) {
      throw Throwables.propagate(e);
    }
    Blob blob = builder.build();
    blob.getMetadata().setContainer(container);
    blob.getMetadata().setLastModified(new Date(file.lastModified()));
    blob.getMetadata().setSize(file.length());
    if (blob.getPayload().getContentMetadata().getContentMD5() != null)
      blob.getMetadata()
          .setETag(
              base16().lowerCase().encode(blob.getPayload().getContentMetadata().getContentMD5()));
    return blob;
  }