All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.snowflake.client.core.FileCacheManager Maven / Gradle / Ivy

/*
 * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
 */

package net.snowflake.client.core;

import static net.snowflake.client.jdbc.SnowflakeUtil.systemGetEnv;
import static net.snowflake.client.jdbc.SnowflakeUtil.systemGetProperty;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Date;
import net.snowflake.client.log.SFLogger;
import net.snowflake.client.log.SFLoggerFactory;

class FileCacheManager {
  private static final SFLogger LOGGER = SFLoggerFactory.getLogger(FileCacheManager.class);

  /** Object mapper for JSON encoding and decoding */
  private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper();

  private static final Charset DEFAULT_FILE_ENCODING = StandardCharsets.UTF_8;

  private String cacheDirectorySystemProperty;
  private String cacheDirectoryEnvironmentVariable;
  private String baseCacheFileName;
  private long cacheExpirationInMilliseconds;
  private long cacheFileLockExpirationInMilliseconds;

  private File cacheFile;
  private File cacheLockFile;

  private File cacheDir;

  private FileCacheManager() {}

  static FileCacheManager builder() {
    return new FileCacheManager();
  }

  FileCacheManager setCacheDirectorySystemProperty(String cacheDirectorySystemProperty) {
    this.cacheDirectorySystemProperty = cacheDirectorySystemProperty;
    return this;
  }

  FileCacheManager setCacheDirectoryEnvironmentVariable(String cacheDirectoryEnvironmentVariable) {
    this.cacheDirectoryEnvironmentVariable = cacheDirectoryEnvironmentVariable;
    return this;
  }

  FileCacheManager setBaseCacheFileName(String baseCacheFileName) {
    this.baseCacheFileName = baseCacheFileName;
    return this;
  }

  FileCacheManager setCacheExpirationInSeconds(long cacheExpirationInSeconds) {
    // converting from seconds to milliseconds
    this.cacheExpirationInMilliseconds = cacheExpirationInSeconds * 1000;
    return this;
  }

  FileCacheManager setCacheFileLockExpirationInSeconds(long cacheFileLockExpirationInSeconds) {
    this.cacheFileLockExpirationInMilliseconds = cacheFileLockExpirationInSeconds * 1000;
    return this;
  }

  /**
   * Override the cache file.
   *
   * @param newCacheFile a file object to override the default one.
   */
  void overrideCacheFile(File newCacheFile) {
    this.cacheFile = newCacheFile;
    this.cacheDir = newCacheFile.getParentFile();
    this.baseCacheFileName = newCacheFile.getName();
  }

  FileCacheManager build() {
    // try to get cacheDir from system property or environment variable
    String cacheDirPath =
        this.cacheDirectorySystemProperty != null
            ? systemGetProperty(this.cacheDirectorySystemProperty)
            : null;
    if (cacheDirPath == null) {
      try {
        cacheDirPath =
            this.cacheDirectoryEnvironmentVariable != null
                ? systemGetEnv(this.cacheDirectoryEnvironmentVariable)
                : null;
      } catch (Throwable ex) {
        LOGGER.debug(
            "Cannot get environment variable for cache directory, " + "skip using cache", false);
        // In Boomi cloud, System.getenv is not allowed due to policy,
        // so we catch the exception and skip cache completely
        return this;
      }
    }

    if (cacheDirPath != null) {
      this.cacheDir = new File(cacheDirPath);
    } else {
      // use user home directory to store the cache file
      String homeDir = systemGetProperty("user.home");
      if (homeDir == null) {
        // use tmp dir if not exists.
        homeDir = systemGetProperty("java.io.tmpdir");
      } else {
        // Checking if home directory is writable.
        File homeFile = new File(homeDir);
        if (!homeFile.canWrite()) {
          LOGGER.debug("Home directory not writeable, using tmpdir", false);
          homeDir = systemGetProperty("java.io.tmpdir");
        }
      }
      if (homeDir == null) {
        // if still home directory is null, no cache dir is set.
        return this;
      }
      if (Constants.getOS() == Constants.OS.WINDOWS) {
        this.cacheDir =
            new File(
                new File(new File(new File(homeDir, "AppData"), "Local"), "Snowflake"), "Caches");
      } else if (Constants.getOS() == Constants.OS.MAC) {
        this.cacheDir = new File(new File(new File(homeDir, "Library"), "Caches"), "Snowflake");
      } else {
        this.cacheDir = new File(new File(homeDir, ".cache"), "snowflake");
      }
    }

    if (!this.cacheDir.mkdirs() && !this.cacheDir.exists()) {
      LOGGER.debug(
          "Cannot create the cache directory {}. Giving up.", this.cacheDir.getAbsolutePath());
      return this;
    }
    LOGGER.debug("Verified Directory {}", this.cacheDir.getAbsolutePath());

    File cacheFileTmp = new File(this.cacheDir, this.baseCacheFileName).getAbsoluteFile();
    try {
      // create an empty file if not exists and return true.
      // If exists. the method returns false.
      // In this particular case, it doesn't matter as long as the file is
      // writable.
      if (cacheFileTmp.createNewFile()) {
        LOGGER.debug("Successfully created a cache file {}", cacheFileTmp);
      } else {
        LOGGER.debug("Cache file already exists {}", cacheFileTmp);
      }
      this.cacheFile = cacheFileTmp.getCanonicalFile();
      this.cacheLockFile =
          new File(this.cacheFile.getParentFile(), this.baseCacheFileName + ".lck");
    } catch (IOException | SecurityException ex) {
      LOGGER.info("Failed to touch the cache file. Ignored. {}", cacheFileTmp.getAbsoluteFile());
    }
    return this;
  }

  /** Reads the cache file. */
  JsonNode readCacheFile() {
    if (cacheFile == null || !this.checkCacheLockFile()) {
      // no cache or the cache is not valid.
      return null;
    }
    try {
      if (!cacheFile.exists()) {
        LOGGER.debug("Cache file doesn't exists. File: {}", cacheFile);
        return null;
      }

      try (Reader reader =
          new InputStreamReader(new FileInputStream(cacheFile), DEFAULT_FILE_ENCODING)) {
        return OBJECT_MAPPER.readTree(reader);
      }
    } catch (IOException ex) {
      LOGGER.debug("Failed to read the cache file. No worry. File: {}, Err: {}", cacheFile, ex);
    }
    return null;
  }

  void writeCacheFile(JsonNode input) {
    LOGGER.debug("Writing cache file. File={}", cacheFile);
    if (cacheFile == null || !tryLockCacheFile()) {
      // no cache file or it failed to lock file
      LOGGER.debug(
          "No cache file exists or failed to lock the file. Skipping writing the cache", false);
      return;
    }
    // NOTE: must unlock cache file
    try {
      if (input == null) {
        return;
      }
      try (Writer writer =
          new OutputStreamWriter(new FileOutputStream(cacheFile), DEFAULT_FILE_ENCODING)) {
        writer.write(input.toString());
      }
    } catch (IOException ex) {
      LOGGER.debug("Failed to write the cache file. File: {}", cacheFile);
    } finally {
      if (!unlockCacheFile()) {
        LOGGER.debug("Failed to unlock cache file", false);
      }
    }
  }

  void deleteCacheFile() {
    LOGGER.debug("Deleting cache file. File={}, Lock File={}", cacheFile, cacheLockFile);

    if (cacheFile == null) {
      return;
    }

    unlockCacheFile();
    if (!cacheFile.delete()) {
      LOGGER.debug("Failed to delete the file: {}", cacheFile);
    }
  }

  /**
   * Tries to lock the cache file
   *
   * @return true if success or false
   */
  private boolean tryLockCacheFile() {
    int cnt = 0;
    boolean locked = false;
    while (cnt < 100 && !(locked = lockCacheFile())) {
      try {
        Thread.sleep(100);
      } catch (InterruptedException ex) {
        // doesn't matter
      }
      ++cnt;
    }
    if (!locked) {
      LOGGER.debug("Failed to lock the cache file.", false);
    }
    return locked;
  }

  /**
   * Lock cache file by creating a lock directory
   *
   * @return true if success or false
   */
  private boolean lockCacheFile() {
    return cacheLockFile.mkdirs();
  }

  /**
   * Unlock cache file by deleting a lock directory
   *
   * @return true if success or false
   */
  private boolean unlockCacheFile() {
    return cacheLockFile.delete();
  }

  private boolean checkCacheLockFile() {
    long currentTime = new Date().getTime();
    long cacheFileTs = fileCreationTime(cacheFile);

    if (!cacheLockFile.exists()
        && cacheFileTs > 0
        && currentTime - this.cacheExpirationInMilliseconds <= cacheFileTs) {
      LOGGER.debug("No cache file lock directory exists and cache file is up to date.", false);
      return true;
    }

    long lockFileTs = fileCreationTime(cacheLockFile);
    if (lockFileTs < 0) {
      // failed to get the timestamp of lock directory
      return false;
    }
    if (lockFileTs < currentTime - this.cacheFileLockExpirationInMilliseconds) {
      // old lock file
      if (!cacheLockFile.delete()) {
        LOGGER.debug("Failed to delete the directory. Dir: {}", cacheLockFile);
        return false;
      }
      LOGGER.debug("Deleted the cache lock directory, because it was old.", false);
      return currentTime - this.cacheExpirationInMilliseconds <= cacheFileTs;
    }
    LOGGER.debug("Failed to lock the file. Ignored.", false);
    return false;
  }

  /**
   * Gets file/dir creation time in epoch (ms)
   *
   * @return epoch time in ms
   */
  private static long fileCreationTime(File targetFile) {
    if (!targetFile.exists()) {
      LOGGER.debug("File not exists. File: {}", targetFile);
      return -1;
    }
    try {
      Path cacheFileLockPath = Paths.get(targetFile.getAbsolutePath());
      BasicFileAttributes attr = Files.readAttributes(cacheFileLockPath, BasicFileAttributes.class);
      return attr.creationTime().toMillis();
    } catch (IOException ex) {
      LOGGER.debug("Failed to get creation time. File/Dir: {}, Err: {}", targetFile, ex);
    }
    return -1;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy