/* * JBoss, Home of Professional Open Source * Copyright 2009 Red Hat Inc. and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This 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 (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.loaders.dummy; import org.infinispan.Cache; import org.infinispan.container.entries.InternalCacheEntry; import org.infinispan.loaders.*; import org.infinispan.marshall.StreamingMarshaller; import org.infinispan.marshall.TestObjectStreamMarshaller; import org.infinispan.util.Util; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import java.io.ObjectInput; import java.io.ObjectOutput; import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @CacheLoaderMetadata(configurationClass = DummyInMemoryCacheStore.Cfg.class) public class DummyInMemoryCacheStore extends AbstractCacheStore { private static final Log log = LogFactory.getLog(DummyInMemoryCacheStore.class); private static final boolean trace = log.isTraceEnabled(); static final ConcurrentMap<String, Map<Object, InternalCacheEntry>> stores = new ConcurrentHashMap<String, Map<Object, InternalCacheEntry>>(); static final ConcurrentMap<String, ConcurrentMap<String, Integer>> storeStats = new ConcurrentHashMap<String, ConcurrentMap<String, Integer>>(); String storeName; Map<Object, InternalCacheEntry> store; // When a store is 'shared', multiple nodes could be trying to update it concurrently. ConcurrentMap<String, Integer> stats; Cfg config; public DummyInMemoryCacheStore(String storeName) { this.storeName = storeName; } public DummyInMemoryCacheStore() { } private void record(String method) { boolean replaced; long end = System.currentTimeMillis() + 5000; do { int i = stats.get(method); replaced = stats.replace(method, i, i + 1); if (!replaced) { try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } while (!replaced && end < System.currentTimeMillis()); } @Override public void store(InternalCacheEntry ed) { record("store"); if (ed != null) { if (trace) log.tracef("Store %s in dummy map store@%s", ed, Util.hexIdHashCode(store)); config.failIfNeeded(ed.getKey()); store.put(ed.getKey(), ed); } } @Override @SuppressWarnings("unchecked") public void fromStream(ObjectInput ois) throws CacheLoaderException { record("fromStream"); try { int numEntries = (Integer) marshaller.objectFromObjectStream(ois); for (int i = 0; i < numEntries; i++) { InternalCacheEntry e = (InternalCacheEntry) marshaller.objectFromObjectStream(ois); if (trace) log.tracef("Store %s from stream in dummy store@%s", e, Util.hexIdHashCode(store)); store.put(e.getKey(), e); } } catch (Exception e) { throw new CacheLoaderException(e); } } @Override public void toStream(ObjectOutput oos) throws CacheLoaderException { record("toStream"); try { marshaller.objectToObjectStream(store.size(), oos); for (InternalCacheEntry se : store.values()) marshaller.objectToObjectStream(se, oos); } catch (Exception e) { throw new CacheLoaderException(e); } } @Override public void clear() { record("clear"); if (trace) log.trace("Clear store"); store.clear(); } @Override public boolean remove(Object key) { record("remove"); if (store.remove(key) != null) { if (trace) log.tracef("Removed %s from dummy store", key); return true; } if (trace) log.tracef("Key %s not present in store, so don't remove", key); return false; } @Override protected void purgeInternal() throws CacheLoaderException { long currentTimeMillis = System.currentTimeMillis(); for (Iterator<InternalCacheEntry> i = store.values().iterator(); i.hasNext();) { InternalCacheEntry se = i.next(); if (se.isExpired(currentTimeMillis)) i.remove(); } } @Override public void init(CacheLoaderConfig config, Cache cache, StreamingMarshaller m) throws CacheLoaderException { super.init(config, cache, m); this.config = (Cfg) config; storeName = this.config.getStoreName(); if (marshaller == null) marshaller = new TestObjectStreamMarshaller(); } @Override public InternalCacheEntry load(Object key) { record("load"); if (key == null) return null; InternalCacheEntry se = store.get(key); if (se == null) return null; if (se.isExpired(System.currentTimeMillis())) { log.debugf("Key %s exists, but has expired. Entry is %s", key, se); store.remove(key); return null; } return se; } @Override public Set<InternalCacheEntry> loadAll() { record("loadAll"); Set<InternalCacheEntry> s = new HashSet<InternalCacheEntry>(); final long currentTimeMillis = System.currentTimeMillis(); for (Iterator<InternalCacheEntry> i = store.values().iterator(); i.hasNext();) { InternalCacheEntry se = i.next(); if (se.isExpired(currentTimeMillis)) { log.debugf("Key %s exists, but has expired. Entry is %s", se.getKey(), se); i.remove(); } else s.add(se); } return s; } @Override public Set<InternalCacheEntry> load(int numEntries) throws CacheLoaderException { record("load"); if (numEntries < 0) return loadAll(); Set<InternalCacheEntry> s = new HashSet<InternalCacheEntry>(numEntries); final long currentTimeMillis = System.currentTimeMillis(); for (Iterator<InternalCacheEntry> i = store.values().iterator(); i.hasNext() && s.size() < numEntries;) { InternalCacheEntry se = i.next(); if (se.isExpired(currentTimeMillis)) { log.debugf("Key %s exists, but has expired. Entry is %s", se.getKey(), se); i.remove(); } else if (s.size() < numEntries) { s.add(se); } } return s; } @Override public Set<Object> loadAllKeys(Set<Object> keysToExclude) throws CacheLoaderException { record("loadAllKeys"); Set<Object> set = new HashSet<Object>(); for (Object key: store.keySet()) { if (keysToExclude == null || !keysToExclude.contains(key)) { log.debugf("Load %s from store %s@%s", key, storeName, Util.hexIdHashCode(store)); set.add(key); } } return set; } @Override public Class<? extends CacheLoaderConfig> getConfigurationClass() { record("getConfigurationClass"); return Cfg.class; } @Override public void start() throws CacheLoaderException { super.start(); if (store != null) return; store = new ConcurrentHashMap<Object, InternalCacheEntry>(); stats = newStatsMap(); if (storeName != null) { if (cache != null) storeName += "_" + cache.getName(); Map<Object, InternalCacheEntry> existing = stores.putIfAbsent(storeName, store); if (existing != null) { store = existing; log.debugf("Reusing in-memory cache store %s", storeName); } else { log.debugf("Creating new in-memory cache store %s", storeName); } ConcurrentMap<String, Integer> existingStats = storeStats.putIfAbsent(storeName, stats); if (existing != null) { stats = existingStats; } } // record at the end! record("start"); } private ConcurrentMap<String, Integer> newStatsMap() { ConcurrentMap<String, Integer> m = new ConcurrentHashMap<String, Integer>(); for (Method method: CacheStore.class.getMethods()) { m.put(method.getName(), 0); } return m; } @Override public void stop() throws CacheLoaderException { record("stop"); super.stop(); if (config.isPurgeOnStartup()) { String storeName = config.getStoreName(); if (storeName != null) { stores.remove(storeName); } } } public boolean isEmpty() { return store.isEmpty(); } public Map<String, Integer> stats() { return Collections.unmodifiableMap(stats); } public void clearStats() { for (String k: stats.keySet()) stats.put(k, 0); } public static class Cfg extends AbstractCacheStoreConfig { private static final long serialVersionUID = 4258914047690999424L; boolean debug; String storeName = null; private Object failKey; public Cfg() { this(null); } public Cfg(String name) { setCacheLoaderClassName(DummyInMemoryCacheStore.class.getName()); storeName(name); } public boolean isDebug() { return debug; } /** * @deprecated use {@link #debug(boolean)} */ @Deprecated public void setDebug(boolean debug) { this.debug = debug; } public Cfg debug(boolean debug) { setDebug(debug); return this; } public String getStoreName() { return storeName; } /** * @deprecated use {@link #storeName(String)} */ @Deprecated public void setStoreName(String store) { this.storeName = store; } public Cfg storeName(String store) { setStoreName(store); return this; } @Override public Cfg clone() { return (Cfg) super.clone(); } /** * @deprecated use {@link #failKey(Object)} */ @Deprecated public void setFailKey(Object failKey) { this.failKey = failKey; } public Cfg failKey(Object failKey) { setFailKey(failKey); return this; } public void failIfNeeded(Object key) { if(failKey != null && failKey.equals(key)) throw new RuntimeException("Induced failure on key:" + key); } @Override public Cfg fetchPersistentState(Boolean fetchPersistentState) { super.fetchPersistentState(fetchPersistentState); return this; } @Override public Cfg ignoreModifications(Boolean ignoreModifications) { super.ignoreModifications(ignoreModifications); return this; } @Override public Cfg purgeOnStartup(Boolean purgeOnStartup) { super.purgeOnStartup(purgeOnStartup); return this; } @Override public Cfg purgerThreads(Integer purgerThreads) { super.purgerThreads(purgerThreads); return this; } @Override public Cfg purgeSynchronously(Boolean purgeSynchronously) { super.purgeSynchronously(purgeSynchronously); return this; } } }