com.ignorelist.kassandra.steam.scraper.FileCache.java Source code

Java tutorial

Introduction

Here is the source code for com.ignorelist.kassandra.steam.scraper.FileCache.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.ignorelist.kassandra.steam.scraper;

import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.cache.AbstractLoadingCache;
import com.google.common.cache.CacheLoader;
import com.google.common.util.concurrent.Striped;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileTime;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.io.IOUtils;

/**
 *
 * @author poison
 */
public class FileCache extends AbstractLoadingCache<String, InputStream> {

    private static final Logger LOG = Logger.getLogger(FileCache.class.getName());

    private final Base32 codec;
    private final Striped<ReadWriteLock> stripedLock;
    private final Path cacheDirectory;
    private final CacheLoader<String, ? extends InputStream> valueLoader;
    private final long expireAfterMillis;

    public FileCache(Path cacheDirectory, CacheLoader<String, ? extends InputStream> valueLoader,
            long expireAfterMillis) {
        codec = new Base32();
        stripedLock = Striped.lazyWeakReadWriteLock(4 * Runtime.getRuntime().availableProcessors());
        this.cacheDirectory = cacheDirectory;
        this.valueLoader = valueLoader;
        this.expireAfterMillis = expireAfterMillis;
    }

    public FileCache(Path cacheDirectory, CacheLoader<String, ? extends InputStream> valueLoader,
            TimeUnit expireUnit, long expireDuration) {
        this(cacheDirectory, valueLoader, TimeUnit.MILLISECONDS.convert(expireDuration, expireUnit));
    }

    private String encodeFileName(String key) {
        if (Strings.isNullOrEmpty(key)) {
            throw new IllegalArgumentException("key may not be null or empty");
        }
        return codec.encodeAsString(key.getBytes(Charsets.UTF_8));
    }

    private String decodeFileName(String fileName) {
        return new String(codec.decode(fileName), Charsets.UTF_8);
    }

    private Path buildCacheFile(String key) {
        return cacheDirectory.resolve(encodeFileName(key));
    }

    private boolean exists(String key) {
        return Files.isRegularFile(buildCacheFile(key), LinkOption.NOFOLLOW_LINKS);
    }

    private long getModified(String key) throws IOException {
        FileTime lastModifiedTime = Files.getLastModifiedTime(buildCacheFile(key), LinkOption.NOFOLLOW_LINKS);
        return lastModifiedTime.toMillis();
    }

    private boolean isExpired(String key) {
        try {
            return (System.currentTimeMillis() - getModified(key)) >= expireAfterMillis;
        } catch (IOException ex) {
            return true;
        }
    }

    @Override
    public InputStream get(String key, Callable<? extends InputStream> valueLoader) throws ExecutionException {
        InputStream inputStream = getIfPresent(key);
        if (null != inputStream) {
            return inputStream;
        }
        final Lock writeLock = stripedLock.get(key).writeLock();
        writeLock.lock();
        try {
            inputStream = getIfPresentNonBlocking(key);
            if (null != inputStream) {
                return inputStream;
            }
            try {
                inputStream = valueLoader.call();
                try {
                    putNonBlocking(key, inputStream);
                    return getIfPresentNonBlocking(key);
                } catch (IOException ex) {
                    Logger.getLogger(FileCache.class.getName()).log(Level.SEVERE, "failed to write cache file", ex);
                    throw new ExecutionException("failed to load " + key, ex);
                }
            } catch (Exception e) {
                Logger.getLogger(FileCache.class.getName()).log(Level.SEVERE, null, e);
                throw new ExecutionException(e);
            }
        } finally {
            writeLock.unlock();
        }

    }

    @Override
    public void put(String key, InputStream value) {
        final Lock writeLock = stripedLock.get(key).writeLock();
        writeLock.lock();
        try {
            putNonBlocking(key, value);
        } catch (IOException ex) {
            Logger.getLogger(FileCache.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            writeLock.unlock();
        }

    }

    private void putNonBlocking(String key, InputStream value) throws IOException {
        final Path cacheFile = buildCacheFile(key);
        OutputStream outputStream = new GZIPOutputStream(Files.newOutputStream(cacheFile));
        try {
            IOUtils.copy(value, outputStream);
        } catch (IOException e) {
            IOUtils.closeQuietly(outputStream);
            Files.delete(cacheFile);
            throw e;
        } finally {
            IOUtils.closeQuietly(outputStream);
        }
    }

    @Override
    public InputStream get(final String key) throws ExecutionException {
        return get(key, new Callable<InputStream>() {
            @Override
            public InputStream call() throws Exception {
                return valueLoader.load(key);
            }
        });
    }

    @Override
    public InputStream getIfPresent(Object key) {
        final Lock readLock = stripedLock.get(key).readLock();
        readLock.lock();
        try {
            return getIfPresentNonBlocking(key);
        } finally {
            readLock.unlock();
        }
    }

    private InputStream getIfPresentNonBlocking(Object key) {
        if (isExpired(key.toString())) {
            return null;
        }
        try {
            return new GZIPInputStream(
                    Files.newInputStream(buildCacheFile(key.toString()), StandardOpenOption.READ));
        } catch (IOException ex) {
            LOG.log(Level.WARNING, "failed to open InputStream", ex);
            return null;
        }
    }

}