/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.cache.infinispan.util;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.transaction.Status;
import javax.transaction.TransactionManager;
import org.infinispan.AdvancedCache;
import org.infinispan.commons.util.CloseableIterable;
import org.infinispan.commons.util.CloseableIterator;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.context.Flag;
import org.infinispan.filter.AcceptAllKeyValueFilter;
import org.infinispan.filter.Converter;
import org.infinispan.filter.KeyValueFilter;
import org.infinispan.filter.NullValueConverter;
import org.infinispan.remoting.rpc.RpcManager;
import org.infinispan.remoting.rpc.RpcOptions;
/**
* Helper for dealing with Infinispan cache instances.
*
* @author Galder ZamarreƱo
* @since 4.1
*/
public class Caches {
private Caches() {
// Suppresses default constructor, ensuring non-instantiability.
}
/**
* Call an operation within a transaction. This method guarantees that the
* right pattern is used to make sure that the transaction is always either
* committed or rollback.
*
* @param cache instance whose transaction manager to use
* @param c callable instance to run within a transaction
* @param <T> type of callable return
* @return returns whatever the callable returns
* @throws Exception if any operation within the transaction fails
*/
public static <T> T withinTx(
AdvancedCache cache,
Callable<T> c) throws Exception {
// Retrieve transaction manager
return withinTx( cache.getTransactionManager(), c );
}
/**
* Call an operation within a transaction. This method guarantees that the
* right pattern is used to make sure that the transaction is always either
* committed or rollbacked.
*
* @param tm transaction manager
* @param c callable instance to run within a transaction
* @param <T> type of callable return
* @return returns whatever the callable returns
* @throws Exception if any operation within the transaction fails
*/
public static <T> T withinTx(
TransactionManager tm,
Callable<T> c) throws Exception {
if ( tm == null ) {
try {
return c.call();
}
catch (Exception e) {
throw e;
}
}
else {
tm.begin();
try {
return c.call();
}
catch (Exception e) {
tm.setRollbackOnly();
throw e;
}
finally {
if ( tm.getStatus() == Status.STATUS_ACTIVE ) {
tm.commit();
}
else {
tm.rollback();
}
}
}
}
public static void withinTx(TransactionManager tm, final Runnable runnable) throws Exception {
withinTx(tm, new Callable<Void>() {
@Override
public Void call() throws Exception {
runnable.run();
return null;
}
});
}
/**
* Transform a given cache into a local cache
*
* @param cache to be transformed
* @return a cache that operates only in local-mode
*/
public static AdvancedCache localCache(AdvancedCache cache) {
return cache.withFlags( Flag.CACHE_MODE_LOCAL );
}
/**
* Transform a given cache into a cache that ignores return values for
* operations returning previous values, i.e. {@link AdvancedCache#put(Object, Object)}
*
* @param cache to be transformed
* @return a cache that ignores return values
*/
public static AdvancedCache ignoreReturnValuesCache(AdvancedCache cache) {
return cache.withFlags( Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP, Flag.IGNORE_RETURN_VALUES );
}
/**
* Transform a given cache into a cache that ignores return values for
* operations returning previous values, i.e. {@link AdvancedCache#put(Object, Object)},
* adding an extra flag.
*
* @param cache to be transformed
* @param extraFlag to add to the returned cache
* @return a cache that ignores return values
*/
public static AdvancedCache ignoreReturnValuesCache(
AdvancedCache cache, Flag extraFlag) {
return cache.withFlags(
Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP, Flag.IGNORE_RETURN_VALUES, extraFlag
);
}
/**
* Transform a given cache into a cache that writes cache entries without
* waiting for them to complete, adding an extra flag.
*
* @param cache to be transformed
* @param extraFlag to add to the returned cache
* @return a cache that writes asynchronously
*/
public static AdvancedCache asyncWriteCache(
AdvancedCache cache,
Flag extraFlag) {
return cache.withFlags(
Flag.SKIP_CACHE_LOAD,
Flag.SKIP_REMOTE_LOOKUP,
Flag.FORCE_ASYNCHRONOUS,
extraFlag
);
}
/**
* Transform a given cache into a cache that fails silently if cache writes fail.
*
* @param cache to be transformed
* @return a cache that fails silently if cache writes fail
*/
public static AdvancedCache failSilentWriteCache(AdvancedCache cache) {
return cache.withFlags(
Flag.FAIL_SILENTLY,
Flag.ZERO_LOCK_ACQUISITION_TIMEOUT,
Flag.SKIP_CACHE_LOAD,
Flag.SKIP_REMOTE_LOOKUP
);
}
/**
* Transform a given cache into a cache that fails silently if
* cache writes fail, adding an extra flag.
*
* @param cache to be transformed
* @param extraFlag to be added to returned cache
* @return a cache that fails silently if cache writes fail
*/
public static AdvancedCache failSilentWriteCache(
AdvancedCache cache,
Flag extraFlag) {
return cache.withFlags(
Flag.FAIL_SILENTLY,
Flag.ZERO_LOCK_ACQUISITION_TIMEOUT,
Flag.SKIP_CACHE_LOAD,
Flag.SKIP_REMOTE_LOOKUP,
extraFlag
);
}
/**
* Transform a given cache into a cache that fails silently if
* cache reads fail.
*
* @param cache to be transformed
* @return a cache that fails silently if cache reads fail
*/
public static AdvancedCache failSilentReadCache(AdvancedCache cache) {
return cache.withFlags(
Flag.FAIL_SILENTLY,
Flag.ZERO_LOCK_ACQUISITION_TIMEOUT
);
}
/**
* Broadcast an evict-all command with the given cache instance.
*
* @param cache instance used to broadcast command
*/
public static void broadcastEvictAll(AdvancedCache cache) {
final RpcManager rpcManager = cache.getRpcManager();
if ( rpcManager != null ) {
// Only broadcast evict all if it's clustered
final CacheCommandInitializer factory = cache.getComponentRegistry()
.getComponent( CacheCommandInitializer.class );
final boolean isSync = isSynchronousCache( cache );
final EvictAllCommand cmd = factory.buildEvictAllCommand( cache.getName() );
final RpcOptions options = rpcManager.getDefaultRpcOptions( isSync );
rpcManager.invokeRemotely( null, cmd, options );
}
}
/**
* Indicates whether the given cache is configured with
* {@link org.infinispan.configuration.cache.CacheMode#INVALIDATION_ASYNC} or
* {@link org.infinispan.configuration.cache.CacheMode#INVALIDATION_SYNC}.
*
* @param cache to check for invalidation configuration
* @return true if the cache is configured with invalidation, false otherwise
*/
public static boolean isInvalidationCache(AdvancedCache cache) {
return cache.getCacheConfiguration()
.clustering().cacheMode().isInvalidation();
}
/**
* Indicates whether the given cache is configured with
* {@link org.infinispan.configuration.cache.CacheMode#REPL_SYNC},
* {@link org.infinispan.configuration.cache.CacheMode#INVALIDATION_SYNC}, or
* {@link org.infinispan.configuration.cache.CacheMode#DIST_SYNC}.
*
* @param cache to check for synchronous configuration
* @return true if the cache is configured with synchronous mode, false otherwise
*/
public static boolean isSynchronousCache(AdvancedCache cache) {
return cache.getCacheConfiguration()
.clustering().cacheMode().isSynchronous();
}
/**
* Indicates whether the given cache is configured to cluster its contents.
* A cache is considered to clustered if it's configured with any cache mode
* except {@link org.infinispan.configuration.cache.CacheMode#LOCAL}
*
* @param cache to check whether it clusters its contents
* @return true if the cache is configured with clustering, false otherwise
*/
public static boolean isClustered(AdvancedCache cache) {
return cache.getCacheConfiguration()
.clustering().cacheMode().isClustered();
}
public static boolean isTransactionalCache(AdvancedCache cache) {
return cache.getCacheConfiguration().transaction().transactionMode().isTransactional();
}
public static void removeAll(AdvancedCache cache) {
CloseableIterator it = cache.keySet().iterator();
try {
while (it.hasNext()) {
// Cannot use it.next(); it.remove() due to ISPN-5653
cache.remove(it.next());
}
}
finally {
it.close();
}
}
/**
* This interface is provided for convenient fluent use of CloseableIterable
*/
public interface CollectableCloseableIterable<T> extends CloseableIterable<T> {
Set<T> toSet();
}
public interface MapCollectableCloseableIterable<K, V> extends CloseableIterable<CacheEntry<K, V>> {
Map<K, V> toMap();
}
public static <K, V> CollectableCloseableIterable<K> keys(AdvancedCache<K, V> cache, KeyValueFilter<K, V> filter) {
// HHH-10023: we can't use keySet()
final CloseableIterable<CacheEntry<K, Void>> entryIterable = cache
.filterEntries( filter )
.converter( NullValueConverter.getInstance() );
return new CollectableCloseableIterableImpl<K, Void, K>(entryIterable, Selector.KEY);
}
public static <K, V> CollectableCloseableIterable<V> values(AdvancedCache<K, V> cache, KeyValueFilter<K, V> filter) {
if (cache.getCacheConfiguration().transaction().transactionMode().isTransactional()) {
// Dummy read to enlist the LocalTransaction as workaround for ISPN-5676
cache.containsKey(false);
}
// HHH-10023: we can't use values()
final CloseableIterable<CacheEntry<K, V>> entryIterable = cache.filterEntries(filter);
return new CollectableCloseableIterableImpl<K, V, V>(entryIterable, Selector.VALUE);
}
public static <K, V, T> CollectableCloseableIterable<T> values(AdvancedCache<K, V> cache, KeyValueFilter<K, V> filter, Converter<K, V, T> converter) {
if (cache.getCacheConfiguration().transaction().transactionMode().isTransactional()) {
// Dummy read to enlist the LocalTransaction as workaround for ISPN-5676
cache.containsKey(false);
}
// HHH-10023: we can't use values()
final CloseableIterable<CacheEntry<K, T>> entryIterable = cache.filterEntries(filter).converter(converter);
return new CollectableCloseableIterableImpl<K, T, T>(entryIterable, Selector.VALUE);
}
public static <K, V> MapCollectableCloseableIterable<K, V> entrySet(AdvancedCache<K, V> cache) {
return entrySet(cache, (KeyValueFilter<K, V>) AcceptAllKeyValueFilter.getInstance());
}
public static <K, V> MapCollectableCloseableIterable<K, V> entrySet(AdvancedCache<K, V> cache, KeyValueFilter<K, V> filter) {
if (cache.getCacheConfiguration().transaction().transactionMode().isTransactional()) {
// Dummy read to enlist the LocalTransaction as workaround for ISPN-5676
cache.containsKey(false);
}
// HHH-10023: we can't use values()
final CloseableIterable<CacheEntry<K, V>> entryIterable = cache.filterEntries(filter);
return new MapCollectableCloseableIterableImpl<K, V>(entryIterable);
}
public static <K, V, T> MapCollectableCloseableIterable<K, T> entrySet(AdvancedCache<K, V> cache, KeyValueFilter<K, V> filter, Converter<K, V, T> converter) {
if (cache.getCacheConfiguration().transaction().transactionMode().isTransactional()) {
// Dummy read to enlist the LocalTransaction as workaround for ISPN-5676
cache.containsKey(false);
}
// HHH-10023: we can't use values()
final CloseableIterable<CacheEntry<K, T>> entryIterable = cache.filterEntries(filter).converter(converter);
return new MapCollectableCloseableIterableImpl<K, T>(entryIterable);
}
/* Function<CacheEntry<K, V>, T> */
private interface Selector<K, V, T> {
Selector KEY = new Selector<Object, Void, Object>() {
@Override
public Object apply(CacheEntry<Object, Void> entry) {
return entry.getKey();
}
};
Selector VALUE = new Selector<Object, Object, Object>() {
@Override
public Object apply(CacheEntry<Object, Object> entry) {
return entry.getValue();
}
};
T apply(CacheEntry<K, V> entry);
}
private static class CollectableCloseableIterableImpl<K, V, T> implements CollectableCloseableIterable<T> {
private final CloseableIterable<CacheEntry<K, V>> entryIterable;
private final Selector<K, V, T> selector;
public CollectableCloseableIterableImpl(CloseableIterable<CacheEntry<K, V>> entryIterable, Selector<K, V, T> selector) {
this.entryIterable = entryIterable;
this.selector = selector;
}
@Override
public void close() {
entryIterable.close();
}
@Override
public CloseableIterator<T> iterator() {
final CloseableIterator<CacheEntry<K, V>> entryIterator = entryIterable.iterator();
return new CloseableIterator<T>() {
@Override
public void close() {
entryIterator.close();
}
@Override
public boolean hasNext() {
return entryIterator.hasNext();
}
@Override
public T next() {
return selector.apply(entryIterator.next());
}
@Override
public void remove() {
throw new UnsupportedOperationException( "remove() not supported" );
}
};
}
@Override
public String toString() {
CloseableIterator<CacheEntry<K, V>> it = entryIterable.iterator();
try {
if (!it.hasNext()) {
return "[]";
}
StringBuilder sb = new StringBuilder();
sb.append('[');
for (; ; ) {
CacheEntry<K, V> entry = it.next();
sb.append(selector.apply(entry));
if (!it.hasNext()) {
return sb.append(']').toString();
}
sb.append(',').append(' ');
}
}
finally {
it.close();
}
}
@Override
public Set toSet() {
HashSet set = new HashSet();
CloseableIterator it = iterator();
try {
while (it.hasNext()) {
set.add(it.next());
}
}
finally {
it.close();
}
return set;
}
}
private static class MapCollectableCloseableIterableImpl<K, V> implements MapCollectableCloseableIterable<K, V> {
private final CloseableIterable<CacheEntry<K, V>> entryIterable;
public MapCollectableCloseableIterableImpl(CloseableIterable<CacheEntry<K, V>> entryIterable) {
this.entryIterable = entryIterable;
}
@Override
public Map<K, V> toMap() {
Map<K, V> map = new HashMap<K, V>();
CloseableIterator<CacheEntry<K, V>> it = entryIterable.iterator();
try {
while (it.hasNext()) {
CacheEntry<K, V> entry = it.next();
V value = entry.getValue();
if (value != null) {
map.put(entry.getKey(), value);
}
}
return map;
}
finally {
it.close();
}
}
@Override
public String toString() {
CloseableIterator<CacheEntry<K, V>> it = entryIterable.iterator();
try {
if (!it.hasNext()) {
return "{}";
}
StringBuilder sb = new StringBuilder();
sb.append('{');
for (; ; ) {
CacheEntry<K, V> entry = it.next();
sb.append(entry.getKey()).append('=').append(entry.getValue());
if (!it.hasNext()) {
return sb.append('}').toString();
}
sb.append(',').append(' ');
}
}
finally {
it.close();
}
}
@Override
public void close() {
entryIterable.close();
}
@Override
public CloseableIterator<CacheEntry<K, V>> iterator() {
return entryIterable.iterator();
}
}
}