package com.temenos.interaction.core.cache; /* * #%L * interaction-core * %% * Copyright (C) 2012 - 2014 Temenos Holdings N.V. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Dumbest possible cache implementation. * Doesn't even use a ConcurrentHashMap because get makes two accesses so needs to be locked anyway * * @author amcguinness * */ public class HashMapCache implements Cache { public HashMapCache() { logger.debug( "HashMap Cache initialized" ); } private static final Logger logger = LoggerFactory.getLogger(HashMapCache.class); private boolean checkKey(Object key) { if ( key == null ) { logger.warn( "Attempt to cache null key" ); return false; } logger.debug( "cache key: " + key ); return true; } /** Insert a value into the cache. * @param maxAge the entry will be invalid after this many seconds */ @Override public synchronized void put(Object key, Response.ResponseBuilder value, int maxAge) { put(key, value, maxAge, System.currentTimeMillis() ); } // logic without live time exposed for testing synchronized void put(Object key, Response.ResponseBuilder value, int maxAge, long now) { if ( !checkKey( key ) ) return; long expires = now + 1000L * maxAge; Entry old = data.get( key ); if ( old != null ) { old.expires = expires; logger.debug( "Extending expires time for [" + key + "] to " + expires ); } else { Entry entry = new Entry( value, expires ); data.put( key.toString(), entry ); logger.debug( "Caching [" + key + "] -> [" + value.toString() + "] until " + expires ); } } /** Retrieve a value from the cache * @return the value if present and valid */ @Override public synchronized Response.ResponseBuilder get(Object key) { return get(key, System.currentTimeMillis()); } public synchronized Response.ResponseBuilder get(Object key, long now) { if ( !checkKey( key ) ) return null; Entry entry = data.get( key.toString() ); if ( entry != null ) { if ( entry.expires > now ) { logger.debug("using cached response for " + key); return entry.value; } else { logger.debug("cached value for [" + key + "] expired " + entry.expires); data.remove( key ); } } else { logger.debug("No cached value for [" + key + "]"); } return null; } /** inspect the cache state * @param now a timestamp * @return list of all keys which are stored and not expired as of the provided timestamp */ public List<String> validStoredKeys(long now) { ArrayList<String> keys = new ArrayList<String>(data.size()/4); for (Map.Entry<String, Entry> e : data.entrySet()) { if (e.getValue().expires > now) keys.add(e.getKey()); } return keys; }; private final HashMap<String,Entry> data = new HashMap<String,Entry>(); private static class Entry { Entry( Response.ResponseBuilder v, long e ) { expires = e; value = v; } long expires; Response.ResponseBuilder value; } }