gr.wavesoft.webng.io.cache.DiskCacheStorage.java Source code

Java tutorial

Introduction

Here is the source code for gr.wavesoft.webng.io.cache.DiskCacheStorage.java

Source

/*
 * DiskCacheStorage.java
 *
 * BrowserNG - A workbench for the browser of the new generation
 * Copyright (C) 2012 Ioannis Charalampidis
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package gr.wavesoft.webng.io.cache;

import gr.wavesoft.webng.io.Database;
import gr.wavesoft.webng.io.SystemConsole;
import gr.wavesoft.webng.io.WebNGSystem;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import org.apache.http.Header;
import org.apache.http.StatusLine;
import org.apache.http.client.cache.HttpCacheEntry;
import org.apache.http.client.cache.HttpCacheStorage;
import org.apache.http.client.cache.HttpCacheUpdateCallback;
import org.apache.http.client.cache.HttpCacheUpdateException;
import org.apache.http.impl.client.cache.FileResource;

/**
 *
 * @author icharala
 */
public class DiskCacheStorage implements HttpCacheStorage {

    private static final SystemConsole.Logger systemLogger = new SystemConsole.Logger(DiskCacheStorage.class,
            "DiskCache");
    private String baseDir;
    private Database cacheDB;
    private long cacheSize;

    private static int IO_BUFFER_SIZE = 4 * 1024;
    private static Long CACHE_MAX_SIZE;

    private String itemPrefix;

    private PreparedStatement stmInsert;
    private PreparedStatement stmSelect;
    private PreparedStatement stmUpdate;

    /**
     * Instantiate disk cache entity
     * 
     * @param baseDir
     * @param cacheDB 
     */
    protected DiskCacheStorage(String baseDir, String itemPrefix, Database cacheDB) {

        // Fetch objects
        this.baseDir = baseDir;
        this.cacheDB = cacheDB;
        this.itemPrefix = itemPrefix;

        // Calculate max cache size
        CACHE_MAX_SIZE = WebNGCache.config.MAX_OBJECT_SIZE * WebNGCache.config.MAX_ENTRIES_DISK;

        // Prepare stements
        stmInsert = this.cacheDB.prepareStatement(
                "INSERT OR REPLACE INTO cache_store " + "(key,updated,data,size) " + "VALUES (?,?,?,?)");
        stmSelect = this.cacheDB.prepareStatement("SELECT * FROM cache_store " + "WHERE key = ?");
        stmUpdate = this.cacheDB.prepareStatement("UPDATE cache_store " + "SET data = ? " + "WHERE key = ?");

        // Update cache size
        updateCacheSize();

    }

    private void updateCacheSize() {
        try {
            // Query for the ID
            ResultSet res = cacheDB.query("SELECT key, size FROM cache_store");
            if (res == null)
                return;

            // Calculate cache size
            cacheSize = 0l;
            while (res.next()) {

                // Ensure the file exists (in case someone removed the files by hand)
                File f = new File(baseDir + "/" + itemPrefix + res.getString("key") + ".cache");
                if (!f.exists()) {

                    // If not, cleanup
                    cacheDB.update("DELETE FROM cache_store WHERE key = ?", res.getString("key"));

                } else {

                    // Get disk size if the size in the DB is zero for some reason
                    Long size = res.getLong("size");
                    if (size == 0) {
                        size = f.length();
                        cacheDB.update("UPDATE cache_store SET size = ? WHERE key = ?", size, res.getString("key"));
                    }

                    // Update cache size
                    cacheSize += res.getLong("size");
                }
            }

        } catch (SQLException ex) {
            systemLogger.except(ex);
        }
    }

    private Boolean cleanupToFit(Long size) {
        // If there is no need to free space, return
        if (cacheSize + size <= CACHE_MAX_SIZE)
            return true;

        // If there is no way to obtain this space, return false
        if (size > CACHE_MAX_SIZE) {
            systemLogger.error("Excessive cache space requested! Will not cache!");
            return false;
        }

        // Select items to purge, start from oldest
        try {

            // Query for the IDs of big size
            ResultSet res = cacheDB
                    .query("SELECT key, size FROM cache_store WHERE state = 1 ORDER BY request_date ASC");
            if (res == null)
                return false;

            // Start deleting until we reached free space
            while (res.next()) {

                // Remove file
                File f = new File(baseDir + "/" + itemPrefix + res.getString("key") + ".cache");
                if (f.exists()) {
                    if (!f.delete()) {
                        systemLogger.error("Unable to delete ", f);
                    } else {
                        cacheDB.update("DELETE FROM cache_store WHERE key = ?", res.getString("id"));
                    }
                }

                // Release size
                cacheSize -= res.getLong("size");

                // Check free space
                if (cacheSize + size <= CACHE_MAX_SIZE)
                    return true;
            }

            // Get the cache size
            res = cacheDB.query("SELECT COUNT(*) FROM cache_store");
            if (res == null)
                return false;

            // Check how many objects we have to delete
            if (!res.next())
                return false;

            int entriesCount = res.getInt(0);
            if (entriesCount + 1 > WebNGCache.config.MAX_ENTRIES_DISK) {

                // Calculate required space
                Integer reqSpace = entriesCount + 1 - WebNGCache.config.MAX_ENTRIES_DISK;

                // We need to delete items
                cacheDB.query("SELECT FROM cache_store ORDER BY request_date ASC LIMIT 0," + reqSpace);

            }

            // Something went wrong :/
            return false;

        } catch (SQLException ex) {
            systemLogger.except(ex);
            return false;
        }
    }

    private byte[] serialize(HttpCacheEntry o) {
        ObjectOutputStream oos = null;
        try {

            // Setut vars
            ByteArrayOutputStream bos = new ByteArrayOutputStream();

            // Dump object
            oos = new ObjectOutputStream(bos);
            oos.writeLong(o.getRequestDate().getTime());
            oos.writeLong(o.getResponseDate().getTime());
            oos.writeObject(o.getStatusLine());
            oos.writeObject(o.getAllHeaders());

            // Complete stream
            oos.close();

            // Get byte array
            return bos.toByteArray();

        } catch (IOException ex) {
            systemLogger.except(ex);
        } finally {
            try {
                oos.close();
            } catch (IOException ex) {
                systemLogger.error("Error closing serialization buffer ", ex);
            }
        }
        return null;

    }

    private HttpCacheEntry unserialize(byte[] bytes, String key) {
        try {
            InputStream is = new ByteArrayInputStream(bytes);
            ObjectInputStream ois = new ObjectInputStream(is);

            // Read data
            Date dReq = new Date(ois.readLong());
            Date dRes = new Date(ois.readLong());
            StatusLine statusLine = (StatusLine) ois.readObject();
            Header[] responseHeaders = (Header[]) ois.readObject();

            // Close streams
            ois.close();
            is.close();

            // Build response
            HttpCacheEntry hce = new HttpCacheEntry(dReq, dRes, statusLine, responseHeaders,
                    new FileResource(new File(baseDir + "/" + itemPrefix + key + ".cache")));

            return hce;

        } catch (ClassNotFoundException ex) {
            systemLogger.except(ex);
            return null;
        } catch (UnsupportedEncodingException ex) {
            systemLogger.except(ex);
            return null;
        } catch (IOException ex) {
            systemLogger.except(ex);
            return null;
        }
    }

    public HttpCacheEntry getEntry(String string) throws IOException {
        String key = WebNGSystem.SHA1Sum(string);
        try {

            // Fetch item
            Database.assignArgStatements(stmSelect, new Object[] { key });
            ResultSet r = stmSelect.executeQuery();
            if (!r.next())
                return null;

            // Build entry
            HttpCacheEntry e = (HttpCacheEntry) unserialize(r.getBytes("data"), key);

            // Return the entry
            return e;

        } catch (SQLException ex) {
            systemLogger.except(ex);
            return null;
        }
    }

    public void removeEntry(String string) throws IOException {
        String key = WebNGSystem.SHA1Sum(string);
        cacheDB.update("DELETE FROM cache_store WHERE key = ?", key);

        // Remove file
        File f = new File(baseDir + "/" + itemPrefix + key + ".cache");
        if (f.exists()) {
            if (!f.delete())
                systemLogger.error("Unable to delete ", f);
        }
    }

    public void updateEntry(String string, HttpCacheUpdateCallback hcuc)
            throws IOException, HttpCacheUpdateException {
        String key = WebNGSystem.SHA1Sum(string);
        HttpCacheEntry hce = hcuc.update(getEntry(key));
        Database.assignArgStatements(stmUpdate, new Object[] { serialize(hce) });
    }

    public void putEntry(String string, HttpCacheEntry hce) throws IOException {
        String key = WebNGSystem.SHA1Sum(string);
        Long size = hce.getResource().length();

        // Firstly, can we fit the item?
        if (size > WebNGCache.config.MAX_OBJECT_SIZE) {
            systemLogger.error("Oversized item requested for key " + key);
            return;
        }

        // If yes, cleanup enough space to fit this item (if needed)
        if (!cleanupToFit(size)) {
            systemLogger.error("Unable to fit item " + key + " in cache");
            return;
        }

        // Dump file to disk
        InputStream is = hce.getResource().getInputStream();
        FileOutputStream fos = new FileOutputStream(baseDir + "/" + itemPrefix + key + ".cache");
        byte[] b = new byte[IO_BUFFER_SIZE];
        int read;
        while ((read = is.read(b)) != -1) {
            fos.write(b, 0, read);
        }
        fos.close();
        is.close();

        /*
        // Build headers
        String headers = "";
        for (Header h: hce.getAllHeaders()) {
        headers += h.getName()+": "+h.getValue()+"\n";
        }
            
        // Prepare some vars
        ProtocolVersion pv = hce.getStatusLine().getProtocolVersion();
         */

        // Update entry
        Database.assignArgStatements(stmInsert, new Object[] { key, System.currentTimeMillis(), "", size });

        // Save to DB
        try {
            stmInsert.setBytes(3, serialize(hce));
            stmInsert.execute();
        } catch (SQLException ex) {
            systemLogger.except(ex);
            return;
        }

    }

}