APLCache.java

220 lines | 8.972 kB Blame History Raw Download
/*
 * 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 br.ufrgs.inf.prosoft.aplcache.caching;

import br.ufrgs.inf.prosoft.cache.KeyNotFoundException;
import br.ufrgs.inf.prosoft.cache.MultiCache;
import br.ufrgs.inf.prosoft.jsonserialiser.JSONSerialiser;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 *
 * @author romulo
 * @param <V>
 */
public class APLCache<V> {

    private static final Logger LOGGER = Logger.getLogger(APLCache.class.getName());
    private static String APLCACHE_CACHEABLE_PARAMETERS = System.getenv("APLCACHE_CACHEABLE_PARAMETERS");
    private static final Map<String, Map<String, Long>> APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS = new HashMap<>();
    private static final String APLCACHE_LOG
            = System.getenv("APLCACHE_LOG") != null && !System.getenv("APLCACHE_LOG").isEmpty() && !System.getenv("APLCACHE_LOG").equals("null")
            ? System.getenv("APLCACHE_LOG") : null;
    private final MultiCache<String, V> multiCache;

    public APLCache() {
        this.multiCache = new MultiCache<>();
    }

    public APLCache(String name) {
        this.multiCache = new MultiCache<>(name);
    }

    private synchronized static void loadCacheableParameters() {
        if (APLCACHE_CACHEABLE_PARAMETERS == null) {
            return;
        }
        try (FileReader fileReader = new FileReader(APLCACHE_CACHEABLE_PARAMETERS)) {
            JsonParser jsonParser = new JsonParser();
            JsonObject jsonObject = jsonParser.parse(fileReader).getAsJsonObject();
            jsonObject.entrySet().forEach(entry -> {
                Map<String, Long> inputsHasTTL = new HashMap<>();
                if (entry.getValue().isJsonArray()) {
                    entry.getValue().getAsJsonArray().forEach(parameter -> {
                        inputsHasTTL.put(parameter.getAsString(), 0L);
                    });
                } else {
                    entry.getValue().getAsJsonObject().entrySet().forEach(innerEntry -> {
                        inputsHasTTL.put(innerEntry.getKey(), innerEntry.getValue().getAsLong());
                    });
                }
                APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS.put(entry.getKey(), inputsHasTTL);
            });
            LOGGER.log(Level.INFO, "cache file loaded");
        } catch (FileNotFoundException ex) {
            LOGGER.log(Level.SEVERE, "invalid cache file");
        } catch (IOException ex) {
            LOGGER.log(Level.SEVERE, "invalid cache file");
        }
        APLCACHE_CACHEABLE_PARAMETERS = null;
    }

    public boolean isCacheable(Thread currentThread, Object... parameters) {
        StackTraceElement[] stackTrace = currentThread.getStackTrace();
        StackTraceElement stackTraceElement = null;
        try {
            stackTraceElement = stackTrace[2];
        } catch (ArrayIndexOutOfBoundsException ex) {
            stackTraceElement = stackTrace[0];
        }
        return isCacheable(stackTraceElement, parameters);
    }

    public boolean isCacheable(StackTraceElement stackTraceElement, Object... parameters) {
        try {
            String methodName = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName();
            return isCacheable(methodName, parameters);
        } catch (Exception ex) {
            return false;
        }
    }

    public boolean isCacheable(String method, Object... parameters) {
        loadCacheableParameters();
        if (APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS.isEmpty()) {
            LOGGER.log(Level.WARNING, "no method is cacheable");
            return false;
        }
        Map<String, Long> cacheableInputsHasTTL = APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS.get(method);
        if (cacheableInputsHasTTL == null) {
            LOGGER.log(Level.WARNING, "method not cacheable: {0}", method);
            return false;
        }
        String serialisedParameters = Stream.of(parameters).map(JSONSerialiser::serialise)
                .collect(Collectors.joining(",", "[", "]"));
        if (cacheableInputsHasTTL.containsKey(serialisedParameters)) {
            return true;
        }
        log(this.multiCache.getCachePerformance().getName() + " : " + serialisedParameters);
        return false;
    }

    public boolean isCacheable(String method, String parameters) {
        loadCacheableParameters();
        if (APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS.isEmpty()) {
            LOGGER.log(Level.WARNING, "no method is cacheable");
            return false;
        }
        Map<String, Long> cacheableParameters = APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS.get(method);
        if (cacheableParameters == null) {
            LOGGER.log(Level.WARNING, "method not cacheable: {0}", method);
            return false;
        }
        if (cacheableParameters.containsKey(parameters)) {
            return true;
        }
        log(this.multiCache.getCachePerformance().getName() + " : " + parameters);
        return false;
    }

    public long getTTLforInput(String method, String input) {
        loadCacheableParameters();
        if (APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS.isEmpty()) {
            LOGGER.log(Level.WARNING, "no method is cacheable");
            return 0;
        }
        Map<String, Long> cacheableInputsHasTTL = APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS.get(method);
        if (cacheableInputsHasTTL == null) {
            LOGGER.log(Level.WARNING, "method not cacheable: {0}", method);
            return 0;
        }
        return cacheableInputsHasTTL.get(input);
    }

    public V computeIfAbsent(Thread currentThread, Object[] parameters, Supplier<V> supplier, long timeToLive) {
        StackTraceElement[] stackTrace = currentThread.getStackTrace();
        StackTraceElement stackTraceElement;
        try {
            stackTraceElement = stackTrace[2];
        } catch (ArrayIndexOutOfBoundsException ex) {
            stackTraceElement = stackTrace[0];
        }
        String methodName = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName();

        String serialisedParameters = Stream.of(parameters).map(JSONSerialiser::serialise)
                .collect(Collectors.joining(",", "[", "]"));
        synchronized (serialisedParameters) {
            try {
                return this.multiCache.get(serialisedParameters);
            } catch (KeyNotFoundException ex) {
                V get = supplier.get();
                if (isCacheable(methodName, serialisedParameters)) {
                    long recommendedTTLforInput = getTTLforInput(methodName, serialisedParameters);
                    if (recommendedTTLforInput == 0) {
                        this.multiCache.put(serialisedParameters, get, timeToLive);
                    } else {
                        LOGGER.log(Level.WARNING, "provided TTL being replaced for recommemded");
                        this.multiCache.put(serialisedParameters, get, recommendedTTLforInput);
                    }
                }
                return get;
            }
        }
    }

    public V computeIfAbsent(Thread currentThread, Object[] parameters, Supplier<V> supplier) {
        StackTraceElement[] stackTrace = currentThread.getStackTrace();
        StackTraceElement stackTraceElement;
        try {
            stackTraceElement = stackTrace[2];
        } catch (ArrayIndexOutOfBoundsException ex) {
            stackTraceElement = stackTrace[0];
        }
        String methodName = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName();

        String serialisedParameters = Stream.of(parameters).map(JSONSerialiser::serialise)
                .collect(Collectors.joining(",", "[", "]"));
        synchronized (serialisedParameters) {
            try {
                return this.multiCache.get(serialisedParameters);
            } catch (KeyNotFoundException ex) {
                V get = supplier.get();
                if (isCacheable(methodName, serialisedParameters)) {
                    long ttLforInput = getTTLforInput(methodName, serialisedParameters);
                    if (ttLforInput == 0) {
                        this.multiCache.put(serialisedParameters, get);
                    } else {
                        this.multiCache.put(serialisedParameters, get, ttLforInput);
                    }
                }
                return get;
            }
        }
    }

    private static void log(String message) {
        if (APLCACHE_LOG == null) {
            return;
        }
        try (FileWriter fileWriter = new FileWriter(APLCACHE_LOG, true)) {
            fileWriter.write(message + "\n");
        } catch (IOException ex) {
        }
    }
}