/************************************************************************* * * * CESeCore: CE Security Core * * * * This software is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or any later version. * * * * See terms of license at gnu.org. * * * *************************************************************************/ package org.cesecore.internal; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.log4j.Logger; /** * Object and name to id lookup cache base implementation. * * Note that this type of cache is not optimized for short-lived objects, but * will prevent memory leaks to some extent through checking for stale data * during updates. * * @version $Id: CommonCacheBase.java 28332 2018-02-20 14:40:52Z anatom $ */ public abstract class CommonCacheBase implements CommonCache { private class CacheEntry { long lastUpdate; final int digest; final String name; final T object; CacheEntry(long lastUpdate, int digest, String name, T object) { this.lastUpdate = lastUpdate; this.digest = digest; this.name = name; this.object = object; } } private final Logger log = Logger.getLogger(CommonCacheBase.class); private Map cache = new HashMap(); private Map nameToIdMap = new HashMap(); /** @return how long to cache objects in milliseconds. */ protected abstract long getCacheTime(); /** @return the maximum allowed time an object may reside in the cache before it is purged. 0 means live forever. */ protected abstract long getMaxCacheLifeTime(); @Override public T getEntry(final Integer id) { final CacheEntry cacheEntry = getCacheEntry(id); if (cacheEntry == null) { return null; } return cacheEntry.object; } @Override public T getEntry(final int id) { return getEntry(Integer.valueOf(id)); } public Set getAllEntries() { Set result = new HashSet(); for(CacheEntry cacheEntry : cache.values()) { result.add(cacheEntry.object); } return result; } @Override public boolean shouldCheckForUpdates(final int id) { final long now = System.currentTimeMillis(); final long cacheTime = getCacheTime(); if (cacheTime<0) { // Cache is disabled, caller should check db return true; } final Integer key = Integer.valueOf(id); final CacheEntry cacheEntry = cache.get(key); if (cacheEntry == null) { // No such object in cache, caller should check db return true; } if (cacheEntry.lastUpdate+cacheTime cacheStage = new HashMap(); final Map nameToIdMapStage = new HashMap(); final long maxCacheLifeTime = getMaxCacheLifeTime(); final long staleCutOffTime = System.currentTimeMillis()-maxCacheLifeTime; synchronized (this) { // Process all entries except for the one that will change for (final Entry entry : cache.entrySet()) { final Integer currentId = entry.getKey(); if (!key.equals(currentId)) { final CacheEntry currentCacheEntry = entry.getValue(); // By flushing older entries we at least limit how much // this registry will grow when used for short-lived objects // in a clustered environment. if (maxCacheLifeTime<1 || currentCacheEntry.lastUpdate >= staleCutOffTime) { // Keep using the current entry in the new cache cacheStage.put(entry.getKey(), currentCacheEntry); nameToIdMapStage.put(currentCacheEntry.name, entry.getKey()); } } } // Process the one that will change if (cacheEntry == null) { // Don't add if to the new version of the cache if it existed (e.g. remove it) } else { cacheStage.put(key, cacheEntry); nameToIdMapStage.put(cacheEntry.name, key); } cache = cacheStage; nameToIdMap = Collections.unmodifiableMap(nameToIdMapStage); } } @Override public Map getNameToIdMap() { return nameToIdMap; } @Override public void flush() { final Map cacheStage = new HashMap(); final Map nameToIdMapStage = new HashMap(); replaceCache(cacheStage, nameToIdMapStage); } @Override public void replaceCacheWith(List keys) { Map cacheStage = new HashMap(); Map nameToIdMapStage = new HashMap(); for(Integer key : keys) { CacheEntry entry = cache.get(key); cacheStage.put(key, entry); String name = entry.name; nameToIdMapStage.put(name, nameToIdMap.get(name)); } replaceCache(cacheStage, nameToIdMapStage); } private void replaceCache(Map cacheStage, Map nameToIdMapStage) { synchronized (this) { cache = cacheStage; nameToIdMap = nameToIdMapStage; } } }