Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
net.sf.ehcache.jcache.JCache Maven / Gradle / Ivy
/**
* Copyright 2003-2010 Terracotta, Inc.
*
* 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 net.sf.ehcache.jcache;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.config.CopyStrategyConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.cache.Cache;
import javax.cache.CacheBuilder;
import javax.cache.CacheConfiguration;
import javax.cache.CacheException;
import javax.cache.CacheLoader;
import javax.cache.CacheManager;
import javax.cache.CacheStatistics;
import javax.cache.CacheWriter;
import javax.cache.Caching;
import javax.cache.InvalidConfigurationException;
import javax.cache.Status;
import javax.cache.event.CacheEntryListener;
import javax.cache.event.NotificationScope;
import javax.cache.transaction.IsolationLevel;
import javax.cache.transaction.Mode;
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.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
/**
* Implementation of a {@link Cache} that wraps an underlying ehcache.
*
* @param the type of keys used by this JCache
* @param the type of values that are loaded by this JCache
* @author Ryan Gardner
*/
public class JCache implements Cache {
private static final Logger LOG = LoggerFactory.getLogger(JCache.class);
private static final int CACHE_LOADER_THREADS = 2;
private static final int DEFAULT_EXECUTOR_TIMEOUT = 10;
private final ExecutorService executorService = Executors.newFixedThreadPool(CACHE_LOADER_THREADS);
private final Set> cacheEntryListeners = new CopyOnWriteArraySet>();
/**
* An Ehcache backing instance
*/
private Ehcache ehcache;
private JCacheManager cacheManager;
private JCacheCacheLoaderAdapter cacheLoaderAdapter;
private JCacheCacheWriterAdapter cacheWriterAdapter;
private ClassLoader classLoader;
private JCacheConfiguration configuration;
/**
* A constructor for JCache.
*
* JCache is an adaptor for an Ehcache, and therefore requires an Ehcache in its constructor.
*
*
* // TODO - perhaps this should not be exposed
*
* @param ehcache An ehcache
* @param cacheManager the CacheManager that manages this wrapped ehcache
* @param classLoader the classloader to use to serialize / deserialize cache entries
* @see "class description for recommended usage"
* @since 1.4
*/
public JCache(Ehcache ehcache, JCacheManager cacheManager, ClassLoader classLoader) {
this.cacheManager = cacheManager;
this.ehcache = ehcache;
this.classLoader = classLoader;
this.configuration = new JCacheConfiguration(ehcache.getCacheConfiguration());
}
/**
* Retrieve the underlying ehcache cache.
*
* @return the ehcache that this JCache adapter wraps
*/
public Ehcache getEhcache() {
return ehcache;
}
private void checkStatusStarted() {
if (!JCacheStatusAdapter.adaptStatus(ehcache.getStatus()).equals(Status.STARTED)) {
throw new IllegalStateException("The cache status is not STARTED");
}
}
/**
* {@inheritDoc}
*/
@Override
public V get(K key) throws CacheException {
checkStatusStarted();
checkKey(key);
Element cacheElement = ehcache.get(key);
if (cacheElement == null) {
if (cacheLoaderAdapter != null /* && configuration.isReadThrough() */) {
return getFromLoader(key);
} else {
return null;
}
}
return (V) cacheElement.getValue();
}
private V getFromLoader(K key) {
Cache.Entry entry = (Entry) cacheLoaderAdapter.load(key);
if (entry != null) {
ehcache.put(new Element(entry.getKey(), entry.getValue()));
return entry.getValue();
} else {
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public Map getAll(Collection extends K> keys) throws CacheException {
checkStatusStarted();
if (keys == null || keys.contains(null)) {
throw new NullPointerException("key cannot be null");
}
// will throw NPE if keys=null
HashMap map = new HashMap(keys.size());
for (K key : keys) {
V value = get(key);
if (value != null) {
map.put(key, value);
}
}
return map;
}
/**
* {@inheritDoc}
*/
@Override
public boolean containsKey(K key) throws CacheException {
checkStatusStarted();
checkKey(key);
return ehcache.isKeyInCache(key);
}
/**
* {@inheritDoc}
*/
@Override
public Future load(K key) throws CacheException {
checkStatusStarted();
checkKey(key);
if (ehcache.getRegisteredCacheLoaders().size() == 0) {
return null;
}
FutureTask task = new FutureTask(new JCacheLoaderCallable(this, key));
executorService.submit(task);
return task;
}
private void checkKey(Object key) {
if (key == null) {
throw new NullPointerException("key can't be null");
}
}
private void checkValue(Object value) {
if (value == null) {
throw new NullPointerException("value can't be null");
}
}
/**
* {@inheritDoc}
*/
@Override
public Future> loadAll(Collection extends K> keys) throws CacheException {
checkStatusStarted();
if (keys == null) {
throw new NullPointerException("keys");
}
if (keys.contains(null)) {
throw new NullPointerException("key");
}
if (cacheLoaderAdapter == null) {
return null;
}
FutureTask> task =
new FutureTask>(
new JCacheLoaderLoadAllCallable(
this, cacheLoaderAdapter.getJCacheCacheLoader(), keys
)
);
executorService.submit(task);
return task;
}
/**
* {@inheritDoc}
*/
@Override
public CacheStatistics getStatistics() {
checkStatusStarted();
if (!(configuration.isStatisticsEnabled())) {
return null;
} else {
return new JCacheStatistics(this, ehcache.getLiveCacheStatistics());
}
}
/**
* {@inheritDoc}
*/
@Override
public void put(K key, V value) throws CacheException {
checkStatusStarted();
checkKey(key);
checkValue(value);
ehcache.put(new Element(key, value));
}
/**
* {@inheritDoc}
*/
@Override
public V getAndPut(K key, V value) throws CacheException, NullPointerException, IllegalStateException {
checkStatusStarted();
if (key == null || value == null) {
throw new NullPointerException("Key cannot be null");
}
try {
Element old = ehcache.get(key);
put(key, value);
return old != null ? (V) old.getValue() : null;
} catch (Exception e) {
throw new CacheException("Unable to getAndPut.", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void putAll(Map extends K, ? extends V> map) throws CacheException {
checkStatusStarted();
if (map == null || map.containsKey(null)) {
throw new NullPointerException("Map of keys cannot be null, and no key in map can be null");
}
for (Map.Entry extends K, ? extends V> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean putIfAbsent(K key, V value) throws CacheException {
checkStatusStarted();
boolean present = containsKey(key);
if (present) {
return false;
} else {
put(key, value);
}
return containsKey(key);
}
/**
* {@inheritDoc}
*/
@Override
public boolean remove(K key) throws CacheException {
checkStatusStarted();
checkKey(key);
return ehcache.remove(key);
}
/**
* {@inheritDoc}
*/
@Override
public boolean remove(K key, V oldValue) throws CacheException {
checkStatusStarted();
checkKey(key);
checkValue(oldValue);
if (containsKey(key) && get(key).equals(oldValue)) {
return remove(key);
} else {
return false;
}
}
/**
* {@inheritDoc}
*/
@Override
public V getAndRemove(K key) throws CacheException {
checkStatusStarted();
checkKey(key);
if (containsKey(key)) {
V oldValue = get(key);
remove(key);
return oldValue;
} else {
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean replace(K key, V oldValue, V newValue) throws CacheException {
checkStatusStarted();
checkKey(key);
checkValue(oldValue);
checkValue(newValue);
// This is a workaround for https://jira.terracotta.org/jira/browse/EHC-894
Element e = ehcache.get(key);
if (e != null && e.getValue().equals(oldValue)) {
ehcache.put(new Element(key, newValue));
return true;
}
return false;
//return ehcache.replace(new Element(key, oldValue), new Element(key, newValue));
}
/**
* {@inheritDoc}
*/
@Override
public boolean replace(K key, V value) throws CacheException {
checkStatusStarted();
checkKey(key);
checkValue(value);
return (ehcache.replace(new Element(key, value)) != null);
}
/**
* {@inheritDoc}
*/
@Override
public V getAndReplace(K key, V value) throws CacheException {
checkStatusStarted();
checkKey(key);
checkValue(value);
Element replaced = ehcache.replace(new Element(key, value));
return replaced != null ? (V) replaced.getValue() : null;
}
/**
* {@inheritDoc}
*/
@Override
public void removeAll(Collection extends K> keys) throws CacheException {
checkStatusStarted();
for (K key : keys) {
remove(key);
}
}
/**
* {@inheritDoc}
*/
@Override
public void removeAll() throws CacheException {
checkStatusStarted();
ehcache.removeAll();
}
/**
* {@inheritDoc}
*/
@Override
public JCacheConfiguration getConfiguration() {
return configuration;
}
/**
* {@inheritDoc}
*/
@Override
public boolean registerCacheEntryListener(CacheEntryListener super K, ? super V> cacheEntryListener, NotificationScope scope, boolean synchronous) {
checkValue(cacheEntryListener);
checkValue(scope);
JCacheListenerAdapter listener = new JCacheListenerAdapter(cacheEntryListener);
ScopedListener scopedListener = new ScopedListener(listener, scope, synchronous);
ehcache.getCacheEventNotificationService().registerListener(listener, JCacheListenerAdapter.adaptScope(scope));
return cacheEntryListeners.add(scopedListener);
}
/**
* {@inheritDoc}
*/
@Override
public boolean unregisterCacheEntryListener(CacheEntryListener, ?> cacheEntryListener) {
if (cacheEntryListener == null) {
return false;
}
//Only cacheEntryListener is checked for equality
JCacheListenerAdapter listenerAdapter = new JCacheListenerAdapter((CacheEntryListener) cacheEntryListener);
ScopedListener scopedListener = new ScopedListener(listenerAdapter, null, true);
ehcache.getCacheEventNotificationService().unregisterListener(listenerAdapter);
return cacheEntryListeners.remove(scopedListener);
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return this.ehcache.getName();
}
/**
* {@inheritDoc}
*/
@Override
public CacheManager getCacheManager() {
return cacheManager;
}
/**
* {@inheritDoc}
*/
@Override
public T unwrap(Class cls) {
if (this.getClass().isAssignableFrom(cls)) {
return (T) this;
}
if (cls.isAssignableFrom(Ehcache.class)) {
return (T) ehcache;
}
throw new IllegalArgumentException("Can't cast the the specified class");
}
/**
* {@inheritDoc}
*/
@Override
public void start() throws CacheException {
//
}
/**
* {@inheritDoc}
*/
@Override
public void stop() throws CacheException {
executorService.shutdown();
try {
executorService.awaitTermination(DEFAULT_EXECUTOR_TIMEOUT, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new CacheException(e);
}
if (ehcache.getStatus().equals(net.sf.ehcache.Status.STATUS_ALIVE)) {
ehcache.dispose();
}
}
/**
* {@inheritDoc}
*/
@Override
public Status getStatus() {
return JCacheStatusAdapter.adaptStatus(ehcache.getStatus());
}
/**
* Returns an iterator over a set of elements of type T.
*
* @return an Iterator.
*/
@Override
public Iterator> iterator() {
checkStatusStarted();
return new EhcacheIterator(ehcache.getKeys().iterator());
}
/**
* Iterator for EHCache Entries
*
* @author Ryan Gardner
*/
private class EhcacheIterator implements Iterator> {
private final Iterator keyIterator;
private K lastKey = null;
public EhcacheIterator(Iterator keyIterator) {
this.keyIterator = keyIterator;
}
/**
* {@inheritdoc}
*/
public boolean hasNext() {
return keyIterator.hasNext();
}
/**
* {@inheritdoc}
*/
public Entry next() {
final K key = (K) keyIterator.next();
lastKey = key;
return new JCacheEntry(ehcache.get(key));
}
/**
* {@inheritdoc}
*/
public void remove() {
if (lastKey == null) {
throw new IllegalStateException();
}
ehcache.remove(lastKey);
lastKey = null;
}
}
protected JCacheCacheLoaderAdapter getCacheLoaderAdapter() {
return this.cacheLoaderAdapter;
}
protected JCacheCacheWriterAdapter getCacheWriterAdapter() {
return this.cacheWriterAdapter;
}
/**
* Callable used for cache loader.
*
* (Based on the CacheLoader pattern used in {@link javax.cache.implementation.RICache.RICacheLoaderLoadCallable})
*
* @param the type of the key
* @param the type of the value
* @author Ryan Gardner
*/
@SuppressWarnings("JavadocReference")
private static class JCacheLoaderCallable implements Callable {
private final JCache cache;
private final K key;
JCacheLoaderCallable(JCache cache, K key) {
this.cache = cache;
this.key = key;
}
@Override
public V call() throws Exception {
if (key == null) {
throw new NullPointerException("Can't load null values");
}
// the adapter is convenient to pass of to the underlying Ehcache so other things that
// hit the ehcache directly can call things like getWithLoader() - but here want to speak native JSR107
// to the CacheLoader here so we retrieve the adapted loader and hit it directly for this load method.
Cache.Entry loadedEntry = (Cache.Entry) cache.getCacheLoaderAdapter().getJCacheCacheLoader().load(key);
V loadedValue = loadedEntry.getValue();
if (loadedValue == null) {
throw new NullPointerException("Can't load null values");
}
cache.put(loadedEntry.getKey(), loadedEntry.getValue());
return loadedValue;
}
}
/**
* Callable used for cache loader.
*
* @param the type of the key
* @param the type of the value
* @author Yannis Cosmadopoulos
* @link javax.cache.implementation.RICache.RICacheLoaderLoadAllCallable
*/
private static class JCacheLoaderLoadAllCallable implements Callable> {
private final JCache cache;
private final Collection extends K> keys;
private final CacheLoader cacheLoader;
JCacheLoaderLoadAllCallable(JCache cache, CacheLoader cacheLoader, Collection extends K> keys) {
this.cache = cache;
this.keys = keys;
this.cacheLoader = cacheLoader;
}
@Override
public Map call() throws Exception {
ArrayList keysNotInStore = new ArrayList();
// ehcache has an underlying loadAll method that could, potentially,
// be used instead of this.
for (K key : keys) {
if (!cache.containsKey(key)) {
keysNotInStore.add(key);
}
}
Map value = cacheLoader.loadAll(keysNotInStore);
cache.putAll(value);
return value;
}
}
/**
* A Builder for the JCache wrapper for Ehcache.
*
* Using this builder, you can create a new Cache. When the cache is built by calling the
* {@link #build()} method the cache will be created and added to the cache manager.
*
* @param The type of keys that are used for this cache
* @param The type of values that are stored in this cache
* @author Ryan Gardner
*/
public static class Builder implements CacheBuilder {
private String cacheName;
private ClassLoader classLoader;
private JCacheConfiguration cacheConfiguration;
private CacheLoader cacheLoader;
private CacheWriter cacheWriter;
private final CopyOnWriteArraySet> listeners = new CopyOnWriteArraySet>();
private final JCacheConfiguration.Builder configurationBuilder = new JCacheConfiguration.Builder();
private JCacheManager cacheManager;
/**
* Create a new CacheBuilder that can be used to initialize a new cache with the
* {@code cacheName} in the {@code cacheManager} using the specified {@code classLoader}
*
* @param cacheName name of the cache that this cacheBuilder will create
* @param cacheManager the {@link CacheManager} that will manage this cache when it is built
* @param classLoader the classLoader that will be used for this cache
*/
public Builder(String cacheName, JCacheManager cacheManager, ClassLoader classLoader) {
this.cacheName = cacheName;
this.cacheManager = cacheManager;
this.classLoader = classLoader;
}
@Override
public JCache build() {
if (cacheName == null) {
throw new InvalidConfigurationException("cache name can't be null");
}
cacheConfiguration = configurationBuilder.build();
if (cacheConfiguration.isReadThrough() && (cacheLoader == null)) {
throw new InvalidConfigurationException("cacheLoader can't be null on a readThrough cache");
}
if (cacheConfiguration.isWriteThrough() && (cacheWriter == null)) {
throw new InvalidConfigurationException("cacheWriter can't be null on a writeThrough cache");
}
cacheConfiguration.getCacheConfiguration().setName(cacheName);
if (cacheConfiguration.isStoreByValue()) {
cacheConfiguration.getCacheConfiguration().setCopyOnWrite(true);
cacheConfiguration.getCacheConfiguration().setCopyOnRead(true);
CopyStrategyConfiguration copyStrategyConfiguration =
cacheConfiguration.getCacheConfiguration().getCopyStrategyConfiguration();
copyStrategyConfiguration.setCopyStrategyInstance(new JCacheCopyOnWriteStrategy(this.classLoader));
}
// in ehcache 2.5 caches if the cacheManager doesn't specify a maxBytesLocalHeap then a cache must specify
// either a size in bytes or in elements
if (cacheManager.getEhcacheManager().getConfiguration().getMaxBytesLocalHeap() == 0) {
if (cacheConfiguration.getCacheConfiguration().getMaxBytesLocalHeap() == 0
&& cacheConfiguration.getCacheConfiguration().getMaxEntriesLocalHeap() == 0) {
LOG.warn("There is no maxBytesLocalHeap set for the cacheManager '{}'. ",
cacheManager.getEhcacheManager().getName() );
LOG.warn("Ehcache requires either maxBytesLocalHeap be set at the cacheManager Level or " +
"requires maxEntriesLocalHeap or maxBytesLocalHeap to be set at the Cache level" );
LOG.warn("The default value of 10000 maxElementsLocalHeap is being used for now. To fix this in " +
"the future create an ehcache{}.xml file in the classpath that configures maxBytesLocalHeap",
(cacheManager.getName() == Caching.DEFAULT_CACHE_MANAGER_NAME )? "" : "-" + cacheName );
cacheConfiguration.getCacheConfiguration().maxEntriesLocalHeap(10000);
}
}
cacheConfiguration.getCacheConfiguration().setStatistics(cacheConfiguration.isStatisticsEnabled());
// this needs to be exposed via configuration methods
cacheConfiguration.getCacheConfiguration().setDiskPersistent(false);
net.sf.ehcache.Cache cache = new net.sf.ehcache.Cache(cacheConfiguration.getCacheConfiguration());
JCache jcache = new JCache(cache, this.cacheManager, this.classLoader);
jcache.configuration = cacheConfiguration;
Ehcache decoratedCache = new JCacheEhcacheDecorator(cache, jcache);
jcache.ehcache = decoratedCache;
if (cacheLoader != null) {
jcache.cacheLoaderAdapter = (new JCacheCacheLoaderAdapter(cacheLoader));
// needed for the loadAll
jcache.ehcache.registerCacheLoader(jcache.cacheLoaderAdapter);
}
if (cacheWriter != null) {
jcache.cacheWriterAdapter = (new JCacheCacheWriterAdapter(cacheWriter));
// needed for the writeAll
jcache.ehcache.registerCacheWriter(jcache.cacheWriterAdapter);
}
for (ListenerRegistration listenerRegistration : listeners) {
jcache.registerCacheEntryListener(
listenerRegistration.cacheEntryListener,
listenerRegistration.scope,
listenerRegistration.synchronous
);
}
return jcache;
}
/**
* Set the cache loader.
*
* @param cacheLoader the CacheLoader
* @return the builder
*/
@Override
public Builder setCacheLoader(CacheLoader cacheLoader) {
if (cacheLoader == null) {
throw new NullPointerException("cacheLoader");
}
this.cacheLoader = cacheLoader;
return this;
}
@Override
public CacheBuilder setCacheWriter(CacheWriter cacheWriter) {
if (cacheWriter == null) {
throw new NullPointerException("cacheWriter");
}
this.cacheWriter = cacheWriter;
return this;
}
@Override
public CacheBuilder registerCacheEntryListener(CacheEntryListener listener, NotificationScope scope, boolean synchronous) {
listeners.add(new ListenerRegistration(listener, scope, synchronous));
return this;
}
@Override
public CacheBuilder setStoreByValue(boolean storeByValue) {
configurationBuilder.setStoreByValue(storeByValue);
return this;
}
@Override
public CacheBuilder setTransactionEnabled(IsolationLevel isolationLevel, Mode mode) {
configurationBuilder.setTransactionEnabled(isolationLevel, mode);
return this;
}
@Override
public CacheBuilder setStatisticsEnabled(boolean enableStatistics) {
configurationBuilder.setStatisticsEnabled(enableStatistics);
return this;
}
@Override
public CacheBuilder setReadThrough(boolean readThrough) {
configurationBuilder.setReadThrough(readThrough);
return this;
}
@Override
public CacheBuilder setWriteThrough(boolean writeThrough) {
configurationBuilder.setWriteThrough(writeThrough);
return this;
}
@Override
public CacheBuilder setExpiry(CacheConfiguration.ExpiryType type, CacheConfiguration.Duration timeToLive) {
if (type == null) {
throw new NullPointerException();
}
if (timeToLive == null) {
throw new NullPointerException();
}
configurationBuilder.setExpiry(type, timeToLive);
return this;
}
}
/**
* Combine a Listener and its NotificationScope. Equality and hashcode are based purely on the listener.
* This implies that the same listener cannot be added to the set of registered listeners more than
* once with different notification scopes.
*
* @author Greg Luck
*/
private static final class ScopedListener {
private final JCacheListenerAdapter listener;
private final NotificationScope scope;
private final boolean synchronous;
private ScopedListener(JCacheListenerAdapter listener, NotificationScope scope, boolean synchronous) {
this.listener = listener;
this.scope = scope;
this.synchronous = synchronous;
}
private JCacheListenerAdapter getListener() {
return listener;
}
private NotificationScope getScope() {
return scope;
}
/**
* Hash code based on listener
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return listener.hashCode();
}
/**
* Equals based on listener (NOT based on scope) - can't have same listener with two different scopes
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ScopedListener, ?> other = (ScopedListener, ?>) obj;
if (listener == null) {
if (other.listener != null) {
return false;
}
} else if (!listener.equals(other.listener)) {
return false;
}
return true;
}
@Override
public String toString() {
return listener.toString();
}
}
/**
* A struct :)
*
* @param
* @param
*/
private static final class ListenerRegistration {
private final CacheEntryListener cacheEntryListener;
private final NotificationScope scope;
private final boolean synchronous;
private ListenerRegistration(CacheEntryListener cacheEntryListener, NotificationScope scope, boolean synchronous) {
this.cacheEntryListener = cacheEntryListener;
this.scope = scope;
this.synchronous = synchronous;
}
}
}