CachedMetadataBlob.java

package doss.local;

import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.attribute.FileTime;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;

import org.skife.jdbi.v2.exceptions.UnableToExecuteStatementException;

import doss.Blob;

/**
 * Wrapper around a Blob which caches metadata like digests in the SQL database.
 */
class CachedMetadataBlob implements Blob {
    final Database db;
    final Blob blob;

    CachedMetadataBlob(Database database, Blob blob) {
        this.db = database;
        this.blob = blob;
    }

    @Override
    public long id() {
        return blob.id();
    }

    @Override
    public InputStream openStream() throws IOException {
        return blob.openStream();
    }

    @Override
    public SeekableByteChannel openChannel() throws IOException {
        return blob.openChannel();
    }

    @Override
    public long size() throws IOException {
        // TODO: cache this (?)
        return blob.size();
    }

    @Override
    public FileTime created() throws IOException {
        // TODO: cache this (?)
        return blob.created();
    }

    @Override
    public String digest(String algorithm) throws NoSuchAlgorithmException, IOException {
        String canonAlgorithm = Digests.canonicalizeAlgorithm(algorithm);
        String digest = db.getDigest(id(), canonAlgorithm);
        if (digest == null) {
            digest = blob.digest(canonAlgorithm);
            try {
                db.insertDigest(id(), canonAlgorithm, digest);
            } catch (UnableToExecuteStatementException e) {
                // that's okay, another thread probably beat us to it
            }
        }
        return digest;
    }

    @Override
    public List<String> verify() throws IOException {
        List<String> errors = new ArrayList<>();
        errors.addAll(blob.verify());
        Map<String, String> digests = db.getDigests(id());
        for (Entry<String, String> entry : digests.entrySet()) {
            String algorithm = entry.getKey();
            String expected = entry.getValue();
            try {
                String actual = blob.digest(algorithm);
                if (!Objects.equals(expected, actual)) {
                    errors.add(algorithm + " digest mismatch: " + expected
                            + " cached in database " + actual + " on disk");
                }
            } catch (NoSuchAlgorithmException e) {
                errors.add("digest " + algorithm + ": " + e.getMessage());
            }
        }
        return errors;
    }
}