All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javax.cache.implementation.RICache Maven / Gradle / Ivy

The newest version!
/**
 *  Copyright 2011 Terracotta, Inc.
 *  Copyright 2011 Oracle America Incorporated
 *
 *  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 javax.cache.implementation;

import javax.cache.CacheConfiguration;
import javax.cache.CacheLoader;
import javax.cache.mbeans.CacheMXBean;
import javax.cache.CacheStatistics;
import javax.cache.CacheWriter;
import javax.cache.Status;
import javax.cache.event.CacheEntryListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.locks.ReentrantLock;

/**
 * The reference implementation for JSR107.
 * 

* This is meant to act as a proof of concept for the API. It is not threadsafe or high performance and does limit * the size of caches or provide eviction. It therefore is not suitable for use in production. Please use a * production implementation of the API. *

* This implementation implements all optional parts of JSR107 except for the Transactions chapter. Transactions support * simply uses the JTA API. The JSR107 specification details how JTA should be applied to caches. * * @param the type of keys maintained by this map * @param the type of mapped values* * @author Greg Luck * @author Yannis Cosmadopoulos * @since 1.0 */ public final class RICache extends AbstractCache { private final RISimpleCache store; private final Set> cacheEntryListeners = new CopyOnWriteArraySet>(); private volatile Status status; private final RICacheStatistics statistics; private final CacheMXBean mBean; private final LockManager lockManager = new LockManager(); /** * Constructs a cache. * * @param cacheName the cache name * @param cacheManagerName the cache manager name * @param classLoader the class loader * @param configuration the configuration * @param cacheLoader the cache loader * @param cacheWriter the cache writer * @param listeners the cache listeners */ private RICache(String cacheName, String cacheManagerName, ClassLoader classLoader, CacheConfiguration configuration, CacheLoader cacheLoader, CacheWriter cacheWriter, Set> listeners) { super(cacheName, cacheManagerName, classLoader, configuration, cacheLoader, cacheWriter); status = Status.UNINITIALISED; store = configuration.isStoreByValue() ? new RIByValueSimpleCache(new RISerializer(classLoader), new RISerializer(classLoader)) : new RIByReferenceSimpleCache(); statistics = new RICacheStatistics(this); mBean = new DelegatingCacheMXBean(this); for (CacheEntryListener listener : listeners) { registerCacheEntryListener(listener); } } /** * {@inheritDoc} */ @Override public V get(K key) { checkStatusStarted(); if (key == null) { throw new NullPointerException(); } return getInternal(key); } /** * {@inheritDoc} */ @Override public Map getAll(Set keys) { checkStatusStarted(); if (keys.contains(null)) { throw new NullPointerException("key"); } // will throw NPE if keys=null HashMap map = new HashMap(keys.size()); for (K key : keys) { V value = getInternal(key); if (value != null) { map.put(key, value); } } return map; } /** * {@inheritDoc} */ @Override public boolean containsKey(K key) { checkStatusStarted(); if (key == null) { throw new NullPointerException(); } lockManager.lock(key); try { return store.containsKey(key); } finally { lockManager.unLock(key); } } /** * {@inheritDoc} */ @Override public Future load(K key) { checkStatusStarted(); if (key == null) { throw new NullPointerException("key"); } if (getCacheLoader() == null) { return null; } if (containsKey(key)) { return null; } FutureTask task = new FutureTask(new RICacheLoaderLoadCallable(this, getCacheLoader(), key)); submit(task); return task; } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public Future> loadAll(Set keys) { checkStatusStarted(); if (keys == null) { throw new NullPointerException("keys"); } if (getCacheLoader() == null) { return null; } if (keys.contains(null)) { throw new NullPointerException("key"); } Callable> callable = new RICacheLoaderLoadAllCallable(this, getCacheLoader(), keys); FutureTask> task = new FutureTask>(callable); submit(task); return task; } /** * {@inheritDoc} */ @Override public CacheStatistics getStatistics() { checkStatusStarted(); if (statisticsEnabled()) { return statistics; } else { return null; } } /** * {@inheritDoc} */ @Override public void put(K key, V value) { checkStatusStarted(); long start = statisticsEnabled() ? System.nanoTime() : 0; lockManager.lock(key); try { store.put(key, value); } finally { lockManager.unLock(key); } if (statisticsEnabled()) { statistics.increaseCachePuts(1); statistics.addPutTimeNano(System.nanoTime() - start); } } @Override public V getAndPut(K key, V value) { checkStatusStarted(); long start = statisticsEnabled() ? System.nanoTime() : 0; V result; lockManager.lock(key); try { result = store.getAndPut(key, value); } finally { lockManager.unLock(key); } if (statisticsEnabled()) { statistics.increaseCachePuts(1); statistics.addPutTimeNano(System.nanoTime() - start); } return result; } /** * {@inheritDoc} */ @Override public void putAll(Map map) { checkStatusStarted(); long start = statisticsEnabled() ? System.nanoTime() : 0; if (map.containsKey(null)) { throw new NullPointerException("key"); } //store.putAll(map); for (Map.Entry entry : map.entrySet()) { K key = entry.getKey(); lockManager.lock(key); try { store.put(key, entry.getValue()); } finally { lockManager.unLock(key); } } if (statisticsEnabled()) { statistics.increaseCachePuts(map.size()); statistics.addPutTimeNano(System.nanoTime() - start); } } /** * {@inheritDoc} */ @Override public boolean putIfAbsent(K key, V value) { checkStatusStarted(); long start = statisticsEnabled() ? System.nanoTime() : 0; boolean result; lockManager.lock(key); try { result = store.putIfAbsent(key, value); } finally { lockManager.unLock(key); } if (result && statisticsEnabled()) { statistics.increaseCachePuts(1); statistics.addPutTimeNano(System.nanoTime() - start); } return result; } /** * {@inheritDoc} */ @Override public boolean remove(K key) { checkStatusStarted(); long start = statisticsEnabled() ? System.nanoTime() : 0; boolean result; lockManager.lock(key); try { result = store.remove(key); } finally { lockManager.unLock(key); } if (result && statisticsEnabled()) { statistics.increaseCacheRemovals(1); statistics.addRemoveTimeNano(System.nanoTime() - start); } return result; } /** * {@inheritDoc} */ @Override public boolean remove(K key, V oldValue) { checkStatusStarted(); long start = statisticsEnabled() ? System.nanoTime() : 0; boolean result; lockManager.lock(key); try { result = store.remove(key, oldValue); } finally { lockManager.unLock(key); } if (result && statisticsEnabled()) { statistics.increaseCacheRemovals(1); statistics.addRemoveTimeNano(System.nanoTime() - start); } return result; } /** * {@inheritDoc} */ @Override public V getAndRemove(K key) { checkStatusStarted(); V result; lockManager.lock(key); try { result = store.getAndRemove(key); } finally { lockManager.unLock(key); } if (statisticsEnabled()) { if (result != null) { statistics.increaseCacheHits(1); statistics.increaseCacheRemovals(1); } else { statistics.increaseCacheMisses(1); } } return result; } /** * {@inheritDoc} */ @Override public boolean replace(K key, V oldValue, V newValue) { checkStatusStarted(); boolean result; lockManager.lock(key); try { result = store.replace(key, oldValue, newValue); } finally { lockManager.unLock(key); } if (result && statisticsEnabled()) { statistics.increaseCachePuts(1); } return result; } /** * {@inheritDoc} */ @Override public boolean replace(K key, V value) { checkStatusStarted(); boolean result; lockManager.lock(key); try { result = store.replace(key, value); } finally { lockManager.unLock(key); } if (result && statisticsEnabled()) { statistics.increaseCachePuts(1); } return result; } /** * {@inheritDoc} */ @Override public V getAndReplace(K key, V value) { checkStatusStarted(); V result; lockManager.lock(key); try { result = store.getAndReplace(key, value); } finally { lockManager.unLock(key); } if (statisticsEnabled()) { if (result != null) { statistics.increaseCacheHits(1); statistics.increaseCachePuts(1); } else { statistics.increaseCacheMisses(1); } } return result; } /** * {@inheritDoc} */ @Override public void removeAll(Set keys) { checkStatusStarted(); for (K key : keys) { lockManager.lock(key); try { store.remove(key); } finally { lockManager.unLock(key); } } if (statisticsEnabled()) { statistics.increaseCacheRemovals(keys.size()); } } /** * {@inheritDoc} */ @Override public void removeAll() { checkStatusStarted(); int size = (statisticsEnabled()) ? store.size() : 0; //store.removeAll(); Iterator> iterator = store.iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); K key = entry.getKey(); lockManager.lock(key); try { iterator.remove(); } finally { lockManager.unLock(key); } } //possible race here but it is only stats if (statisticsEnabled()) { statistics.increaseCacheRemovals(size); } } /** * {@inheritDoc} */ @Override public boolean registerCacheEntryListener(CacheEntryListener cacheEntryListener) { return cacheEntryListeners.add(cacheEntryListener); } /** * {@inheritDoc} */ @Override public boolean unregisterCacheEntryListener(CacheEntryListener cacheEntryListener) { return cacheEntryListeners.remove(cacheEntryListener); } /** * {@inheritDoc} */ @Override public Object invokeEntryProcessor(K key, EntryProcessor entryProcessor) { checkStatusStarted(); if (key == null) { throw new NullPointerException(); } if (key == entryProcessor) { throw new NullPointerException(); } Object result = null; lockManager.lock(key); try { RIMutableEntry entry = new RIMutableEntry(key, store); result = entryProcessor.process(entry); entry.commit(); } finally { lockManager.unLock(key); } return result; } /** * {@inheritDoc} */ @Override public Iterator> iterator() { checkStatusStarted(); return new RIEntryIterator(store.iterator(), lockManager); } @Override public CacheMXBean getMBean() { return mBean; } /** * {@inheritDoc} */ @Override public void start() { status = Status.STARTED; } /** * {@inheritDoc} */ @Override public void stop() { super.stop(); store.removeAll(); status = Status.STOPPED; } private void checkStatusStarted() { if (!status.equals(Status.STARTED)) { throw new IllegalStateException("The cache status is not STARTED"); } } /** * {@inheritDoc} */ @Override public Status getStatus() { return status; } @Override public T unwrap(java.lang.Class cls) { if (cls.isAssignableFrom(this.getClass())) { return cls.cast(this); } throw new IllegalArgumentException("Unwrapping to " + cls + " is not a supported by this implementation"); } private boolean statisticsEnabled() { return getConfiguration().isStatisticsEnabled(); } private V getInternal(K key) { long start = statisticsEnabled() ? System.nanoTime() : 0; V value = null; lockManager.lock(key); try { value = store.get(key); } finally { lockManager.unLock(key); } if (statisticsEnabled()) { statistics.addGetTimeNano(System.nanoTime() - start); } if (value == null) { if (statisticsEnabled()) { statistics.increaseCacheMisses(1); } if (getCacheLoader() != null) { return getFromLoader(key); } else { return null; } } else { if (statisticsEnabled()) { statistics.increaseCacheHits(1); } return value; } } private V getFromLoader(K key) { Entry entry = getCacheLoader().load(key); if (entry != null) { store.put(entry.getKey(), entry.getValue()); return entry.getValue(); } else { return null; } } /** * Returns the size of the cache. * * @return the size in entries of the cache */ long getSize() { return store.size(); } /** * {@inheritDoc} * * @author Yannis Cosmadopoulos */ private static class RIEntry implements Entry { private final K key; private final V value; public RIEntry(K key, V value) { if (key == null) { throw new NullPointerException("key"); } this.key = key; this.value = value; } @Override public K getKey() { return key; } @Override public V getValue() { return value; } /** * {@inheritDoc} */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; RIEntry e2 = (RIEntry) o; return this.getKey().equals(e2.getKey()) && this.getValue().equals(e2.getValue()); } /** * {@inheritDoc} */ @Override public int hashCode() { return getKey().hashCode() ^ getValue().hashCode(); } } /** * {@inheritDoc} * TODO: not obvious how iterator should behave with locking. * TODO: in the impl below, by the time we get the lock, value may be stale * * @author Yannis Cosmadopoulos */ private static final class RIEntryIterator implements Iterator> { private final Iterator> mapIterator; private final LockManager lockManager; private RIEntryIterator(Iterator> mapIterator, LockManager lockManager) { this.mapIterator = mapIterator; this.lockManager = lockManager; } /** * {@inheritDoc} */ @Override public boolean hasNext() { return mapIterator.hasNext(); } /** * {@inheritDoc} */ @Override public Entry next() { Map.Entry mapEntry = mapIterator.next(); K key = mapEntry.getKey(); lockManager.lock(key); try { return new RIEntry(key, mapEntry.getValue()); } finally { lockManager.unLock(key); } } /** * {@inheritDoc} */ @Override public void remove() { mapIterator.remove(); } } /** * Callable used for cache loader. * * @param the type of the key * @param the type of the value * @author Yannis Cosmadopoulos */ private static class RICacheLoaderLoadCallable implements Callable { private final RICache cache; private final CacheLoader cacheLoader; private final K key; RICacheLoaderLoadCallable(RICache cache, CacheLoader cacheLoader, K key) { this.cache = cache; this.cacheLoader = cacheLoader; this.key = key; } @Override public V call() throws Exception { Entry entry = cacheLoader.load(key); cache.put(entry.getKey(), entry.getValue()); return entry.getValue(); } } /** * Callable used for cache loader. * * @param the type of the key * @param the type of the value * @author Yannis Cosmadopoulos */ private static class RICacheLoaderLoadAllCallable implements Callable> { private final RICache cache; private final CacheLoader cacheLoader; private final Collection keys; RICacheLoaderLoadAllCallable(RICache cache, CacheLoader cacheLoader, Collection keys) { this.cache = cache; this.cacheLoader = cacheLoader; this.keys = keys; } @Override public Map call() throws Exception { ArrayList keysNotInStore = new ArrayList(); for (K key : keys) { if (!cache.containsKey(key)) { keysNotInStore.add(key); } } Map value = cacheLoader.loadAll(keysNotInStore); cache.putAll(value); return value; } } /** * A Builder for RICache. * * @param * @param * @author Yannis Cosmadopoulos */ public static class Builder extends AbstractCache.Builder { private final Set> listeners = new CopyOnWriteArraySet>(); /** * Construct a builder. * * @param cacheName the name of the cache to be built * @param cacheManagerName the name of the cache manager * @param classLoader the class loader */ public Builder(String cacheName, String cacheManagerName, ClassLoader classLoader) { this(cacheName, cacheManagerName, classLoader, new RICacheConfiguration.Builder()); } private Builder(String cacheName, String cacheManagerName, ClassLoader classLoader, RICacheConfiguration.Builder configurationBuilder) { super(cacheName, cacheManagerName, classLoader, configurationBuilder); } /** * Builds the cache * * @return a constructed cache. */ @Override public RICache build() { CacheConfiguration configuration = createCacheConfiguration(); RICache riCache = new RICache(cacheName, cacheManagerName, classLoader, configuration, cacheLoader, cacheWriter, listeners); ((RICacheConfiguration) configuration).setRiCache(riCache); return riCache; } @Override public Builder registerCacheEntryListener(CacheEntryListener listener) { listeners.add(listener); return this; } } /** * Simple lock management * @param the type of the object to be locked * @author Yannis Cosmadopoulos * @since 1.0 */ private static final class LockManager { private final ConcurrentHashMap locks = new ConcurrentHashMap(); private final LockFactory lockFactory = new LockFactory(); private LockManager() { } /** * Lock the object * @param key the key */ private void lock(K key) { ReentrantLock lock = lockFactory.getLock(); while (true) { ReentrantLock oldLock = locks.putIfAbsent(key, lock); if (oldLock == null) { return; } // there was a lock oldLock.lock(); // now we have it. Because of possibility that someone had it for remove, // we don't re-use directly lockFactory.release(oldLock); } } /** * Unlock the object * @param key the object */ private void unLock(K key) { ReentrantLock lock = locks.remove(key); lockFactory.release(lock); } /** * Factory/pool * @author Yannis Cosmadopoulos * @since 1.0 */ private static final class LockFactory { private static final int CAPACITY = 100; private static final ArrayList LOCKS = new ArrayList(CAPACITY); private LockFactory() { } private ReentrantLock getLock() { ReentrantLock qLock = null; synchronized (LOCKS) { if (!LOCKS.isEmpty()) { qLock = LOCKS.remove(0); } } ReentrantLock lock = qLock != null ? qLock : new ReentrantLock(); lock.lock(); return lock; } private void release(ReentrantLock lock) { lock.unlock(); synchronized (LOCKS) { if (LOCKS.size() <= CAPACITY) { LOCKS.add(lock); } } } } } /** * A mutable entry * @param * @param * @author Yannis Cosmadopoulos * @since 1.0 */ private static class RIMutableEntry implements MutableEntry { private final K key; private V value; private final RISimpleCache store; private boolean exists; private boolean remove; RIMutableEntry(K key, RISimpleCache store) { this.key = key; this.store = store; exists = store.containsKey(key); } private void commit() { if (remove) { store.remove(key); } else if (value != null) { store.put(key, value); } } @Override public boolean exists() { return exists; } @Override public void remove() { remove = true; exists = false; value = null; } @Override public void setValue(V value) { if (value == null) { throw new NullPointerException(); } exists = true; remove = false; this.value = value; } @Override public K getKey() { return key; } @Override public V getValue() { return value != null ? value : store.get(key); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy