package org.zalando.catwatch.backend.github; import com.squareup.okhttp.Cache; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.OkUrlFactory; import org.apache.commons.lang.StringUtils; import org.kohsuke.github.GitHub; import org.kohsuke.github.GitHubBuilder; import org.kohsuke.github.extras.OkHttpConnector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.zalando.catwatch.backend.model.util.Scorer; import javax.annotation.PostConstruct; import java.io.File; import java.io.IOException; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Date; import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * Initializes http cache directory and http client. Submits TakeSnapshotTasks * and returns futures of Snapshots with organization data. */ @Component public class SnapshotProvider { private static final Logger logger = LoggerFactory.getLogger(SnapshotProvider.class); private static final int MEGABYTE = 1024 * 1024; private final Scorer scorer; private final String cachePath; private final Integer cacheSize; private final String login; private final String password; private final String token; private final ExecutorService pool = Executors.newCachedThreadPool(); /** * OkHttpClient has to be shared between threads. * * @see <a href="https://github.com/square/okhttp/wiki/Recipes#response-caching">OkHttp Wiki</a> */ private OkHttpClient httpClient; @Autowired public SnapshotProvider(Scorer scorer, @Value("${cache.path}") String cachePath, @Value("${cache.size}") Integer cacheSize, @Value("${github.login:#{null}}") String login, @Value("${github.password:#{null}}") String password, @Value("${github.oauth.token:#{null}}") String token) { this.scorer = scorer; this.cachePath = cachePath; this.cacheSize = cacheSize; this.login = login; this.password = password; this.token = token; } /** * Initializes cache after the bean is created */ @PostConstruct public void init() { Optional<File> cacheDirectoryOptional = getCacheDirectory(); if (cacheDirectoryOptional.isPresent()) { Cache cache = new Cache(cacheDirectoryOptional.get(), cacheSize * MEGABYTE); this.httpClient = new OkHttpClient().setCache(cache); logger.info("Initialized http client with {} mb cache.", cacheSize); } else { this.httpClient = new OkHttpClient(); logger.warn("Initialized http client without cache."); } } public Future<Snapshot> takeSnapshot(String organizationName, Date snapshotDate) throws IOException { GitHubBuilder builder = new GitHubBuilder(); if (StringUtils.isNotEmpty(token)) { builder.withOAuthToken(token); } else if (StringUtils.isNotEmpty(login) && StringUtils.isNotEmpty(password)) { builder.withPassword(login, password); } else { logger.error("GitHub credentials not found, proceeding unauthenticated. That will enforce 60 requests per hour limit."); } GitHub gitHub = builder.withConnector(new OkHttpConnector(new OkUrlFactory(httpClient))).build(); return pool.submit(new TakeSnapshotTask(gitHub, organizationName, scorer, snapshotDate)); } private Optional<File> getCacheDirectory() { Path path = Paths.get(cachePath); if (Files.isDirectory(path)) { if (Files.isWritable(path)) { logger.info("Cache directory found: {}", path.toAbsolutePath().toString()); return Optional.of(path.toFile()); } logger.warn("Unable to write to cache directory '{}'.", cachePath); return Optional.empty(); } logger.info("Cache directory '{}' is not found. Creating new directory.", cachePath); try { path = Files.createDirectories(path); logger.info("Cache directory created successfully."); return Optional.of(path.toFile()); } catch (FileAlreadyExistsException e) { logger.warn("Failed to created cache directory: file already exists."); } catch (SecurityException e) { logger.warn("Failed to created cache directory: access denied."); } catch (IOException e) { logger.warn("Failed to created cache directory.", e); } return Optional.empty(); } }