it.restrung.rest.cache.RequestCache.java Source code

Java tutorial

Introduction

Here is the source code for it.restrung.rest.cache.RequestCache.java

Source

/*
 * Copyright (C) 2012 47 Degrees, LLC
 * http://47deg.com
 * hello@47deg.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package it.restrung.rest.cache;

import android.content.Context;
import android.net.ConnectivityManager;
import it.restrung.rest.utils.IOUtils;
import org.apache.http.conn.ConnectTimeoutException;

import java.io.File;
import java.io.Serializable;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.Callable;

/**
 * Manages the request cache whenever enabled for a given request
 */
public class RequestCache {

    /**
     * Prevents instantiation
     */
    private RequestCache() {
    }

    /**
     * The extension used for the cache files
     */
    private static String CACHE_EXTENSION = ".obj.cache";

    /**
     * Enum representing the different policies available that determine where cached results will be served
     */
    public enum LoadPolicy {

        /**
         * Never use the cache
         */
        NEVER,

        /**
         * Load from the cache when we are offline
         */
        LOAD_IF_OFFLINE,

        /**
         * Load from the cache if we encounter an error
         */
        LOAD_ON_ERROR,

        /**
         * Load from the cache if we have data stored and the server returns a 304 (not modified) response
         */
        ETAG,

        /**
         * Load from the cache if we have data stored
         */
        ENABLED,

        /**
         * Load from the cache if the request times out
         */
        LOAD_IF_TIMEOUT,

        /**
         * Load from the cache then refresh the cache with a network call (calls subscribers twice)
         */
        NETWORK_ENABLED
    }

    /**
     * Enum representing the policies for storing the cached results
     */
    public enum StoragePolicy {

        /**
         * The cache has been disabled. Attempts to store data will silently fail
         */
        DISABLED,

        /**
         * Cache data permanently, until explicitly expired or flushed
         */
        PERMANENTLY
    }

    /**
     * Represents an operation wrapped in a callable that will be performed following the cache policies for loading
     * and storage
     *
     * @param <T>
     */
    public static class CacheAwareOperation<T extends Serializable> {

        /**
         * The provider containing information regarding the cache policies
         */
        private CacheRequestInfoProvider<T> provider;

        /**
         * The operation that is a candidate to be performed and results replaced with cached results
         */
        private Callable<T> operation;

        /**
         * The params that are considered as unique key for the cache
         */
        private Object[] params;

        /**
         * Constructs a cache aware operation
         *
         * @param cacheRequestInfoProvider The provider containing information regarding the cache policies
         * @param operation                The operation that is a candidate to be performed and results replaced with cached results
         * @param params                   The params that are considered as unique key for the cache
         */
        public CacheAwareOperation(CacheRequestInfoProvider<T> cacheRequestInfoProvider, Callable<T> operation,
                Object... params) {
            this.provider = cacheRequestInfoProvider;
            this.operation = operation;
            this.params = params;
        }

        /**
         * loads results from the cache based on this provider and params from which the key will be determined to lookup
         * the cache file
         *
         * @return the stored object
         */
        private T loadFromCache() {
            return get(provider, params);
        }

        /**
         * Performs the operation and stores the result in the cache
         *
         * @return the results
         */
        private T performOperation() throws Exception {
            T result = operation.call();
            provider.setCacheInfo(CacheInfo.NONE);
            if (StoragePolicy.PERMANENTLY.equals(provider.getCacheStoragePolicy())) {
                put(provider.getContextProvider().getContext(), result, params);
            }
            return result;
        }

        /**
         * Enforces the cache policies
         *
         * @return the operation result
         */
        public T execute() throws Exception {
            T result;
            switch (provider.getCacheLoadPolicy()) {
            case ENABLED:
            case NETWORK_ENABLED: //both enabled and network enabled load from cache first, network enabled requests are retried at the the RestClient level
                result = loadFromCache();
                if (result == null) {
                    result = performOperation();
                }
                break;
            case ETAG:
                throw new UnsupportedOperationException("ETAG not yet supported");
            case LOAD_IF_OFFLINE:
                ConnectivityManager connectivityManager = (ConnectivityManager) provider.getContextProvider()
                        .getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
                if (connectivityManager.getActiveNetworkInfo().isConnected()) {
                    result = performOperation();
                } else {
                    result = loadFromCache();
                }
                break;
            case LOAD_IF_TIMEOUT:
                try {
                    result = performOperation();
                } catch (ConnectTimeoutException cte) {
                    result = loadFromCache();
                } catch (SocketTimeoutException ste) {
                    result = loadFromCache();
                }
                break;
            case LOAD_ON_ERROR:
                try {
                    result = performOperation();
                } catch (Exception ex) {
                    result = loadFromCache();
                }
                break;
            default:
                result = performOperation();

            }
            return result;
        }
    }

    /**
     * Calculates a cache key based on a array of params by performing a deep hashcode lookup
     *
     * @param params the params
     * @return the unique cache key
     */
    public static int cacheKey(Object... params) {
        return Arrays.deepHashCode(params);
    }

    /**
     * Gets the cache file based on the requesting context, the cache key and the cache file extension
     *
     * @param context  the requesting context
     * @param cacheKey the cache key
     * @return the cache file
     */
    private static File getCacheFile(Context context, int cacheKey) {
        return new File(String.format("%s/%d%s", context.getCacheDir(), cacheKey, CACHE_EXTENSION));
    }

    /**
     * Invalidates the cache for the requesting context by removing all files associated with it
     *
     * @param context the requesting context
     */
    public static void invalidateAll(Context context) {
        for (File file : context.getCacheDir().listFiles()) {
            if (file.getName().endsWith(CACHE_EXTENSION)) {
                file.delete();
            }
        }
    }

    /**
     * Adds an object to the cache based on a list of params that are used to calculate the cache key
     *
     * @param context the requesting context
     * @param result  the object to be cached
     * @param params  the params to calculate the cache key
     */
    public static void put(Context context, Serializable result, Object... params) {
        File file = getCacheFile(context, cacheKey(params));
        IOUtils.saveSerializableObjectToDisk(result, file);
    }

    /**
     * Gets and deserializes an object from a file into its memory original representation
     *
     * @param context the requesting context
     * @param params  the params to calculate the cache key
     * @param <T>     the resulting type
     * @return the in memory original object
     */
    public static <T extends Serializable> T get(Context context, Object... params) {
        File file = getCacheFile(context, cacheKey(params));
        return IOUtils.loadSerializableObjectFromDisk(file);
    }

    /**
     * Gets and deserializes an object from a file into its memory original representation
     *
     * @param cacheRequestInfoProvider the cache info provider
     * @param params                   the params to calculate the cache key
     * @param <T>                      the resulting type
     * @return the in memory original object
     */
    public static <T extends Serializable> T get(CacheRequestInfoProvider<T> cacheRequestInfoProvider,
            Object... params) {
        File file = getCacheFile(cacheRequestInfoProvider.getContextProvider().getContext(), cacheKey(params));
        cacheRequestInfoProvider.setCacheInfo(new CacheInfo(true, new Date(file.lastModified())));
        return IOUtils.loadSerializableObjectFromDisk(file);
    }

    /**
     * Invalidates a cache entry
     *
     * @param context the requesting context
     * @param params  the params to calculate the cache key
     */
    public static void invalidate(Context context, Object... params) {
        File file = getCacheFile(context, cacheKey(params));
        if (file.exists()) {
            file.delete();
        }
    }
}