package org.infinispan.cache.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.infinispan.AdvancedCache;
import org.infinispan.Cache;
import org.infinispan.CacheCollection;
import org.infinispan.CacheSet;
import org.infinispan.CacheStream;
import org.infinispan.commands.read.AbstractCloseableIteratorCollection;
import org.infinispan.commons.util.CloseableIterator;
import org.infinispan.commons.util.CloseableIteratorMapper;
import org.infinispan.commons.util.CloseableSpliterator;
import org.infinispan.commons.util.CloseableSpliteratorMapper;
import org.infinispan.commons.util.InjectiveFunction;
import org.infinispan.compat.ConverterKeyMapper;
import org.infinispan.compat.ConverterEntryMapper;
import org.infinispan.compat.ConverterValueMapper;
import org.infinispan.compat.TypeConverter;
import org.infinispan.container.InternalEntryFactory;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.context.Flag;
import org.infinispan.factories.ComponentRegistry;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.interceptors.compat.BaseTypeConverterInterceptor;
import org.infinispan.metadata.Metadata;
/**
* Advanced cache that converts key/value passed in to the new type before passing to the underlying cache. Results
* are then also converted back using the converter.
* @author wburns
* @since 9.0
*/
public class TypeConverterDelegatingAdvancedCache<K, V> extends AbstractDelegatingAdvancedCache<K, V> {
private final TypeConverter converter;
private InternalEntryFactory entryFactory;
private final Function<K, K> unboxKey = this::unboxKey;
private final Function<V, V> unboxValue = (InjectiveFunction & Function<V, V>) this::unboxValue;
public TypeConverterDelegatingAdvancedCache(AdvancedCache<K, V> cache, TypeConverter converter) {
super(cache, c -> new TypeConverterDelegatingAdvancedCache<>(c, converter));
this.converter = converter;
}
protected TypeConverterDelegatingAdvancedCache(AdvancedCache<K, V> cache, AdvancedCacheWrapper<K, V> wrapper,
TypeConverter converter) {
super(cache, wrapper);
this.converter = converter;
}
@Inject
public void wireRealCache(ComponentRegistry registry, InternalEntryFactory entryFactory) {
registry.wireDependencies(cache);
this.entryFactory = entryFactory;
}
protected K boxKey(K key) {
return (K) getConverter().boxKey(key);
}
protected V boxValue(V value) {
return (V) getConverter().boxValue(value);
}
protected K unboxKey(K key) {
return (K) getConverter().unboxKey(key);
}
protected V unboxValue(V value) {
return (V) getConverter().unboxValue(value);
}
protected CacheEntry<K, V> convertEntry(K newKey, V newValue, CacheEntry<K, V> entry) {
if (entry instanceof InternalCacheEntry) {
return entryFactory.create(newKey, newValue, (InternalCacheEntry) entry);
} else {
return entryFactory.create(newKey, newValue, entry.getMetadata().version(), entry.getCreated(),
entry.getLifespan(), entry.getLastUsed(), entry.getMaxIdle());
}
}
protected TypeConverter getConverter() {
return converter;
}
private Function<? super K, ? extends V> convertFunction(Function<? super K, ? extends V> mappingFunction) {
return k -> mappingFunction.apply(boxKey(k));
}
private BiFunction<? super K, ? super V, ? extends V> convertFunction(
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return (k, v) -> remappingFunction.apply(unboxKey(k), unboxValue(v));
}
private Map<K, V> boxMap(Map<? extends K, ? extends V> map) {
Map<K, V> newMap = new HashMap<>(map.size());
map.forEach((k, v) -> newMap.put(boxKey(k), boxValue(v)));
return newMap;
}
private Map<K, V> unboxMap(Map<K, V> map) {
// To make sure results are ordered
Map<K, V> newMap = new LinkedHashMap<>(map.size());
map.forEach((k, v) -> newMap.put(unboxKey(k), unboxValue(v)));
return newMap;
}
private Map<K, CacheEntry<K, V>> unboxEntryMap(Map<K, CacheEntry<K, V>> map) {
Map<K, CacheEntry<K, V>> entryMap = new HashMap<>(map.size());
map.values().forEach(v -> {
K originalKey = v.getKey();
K unboxedKey = unboxKey(originalKey);
V originalValue = v.getValue();
V unboxedValue = unboxValue(originalValue);
CacheEntry<K, V> entryToPut;
if (unboxedKey != originalKey || unboxedValue != originalValue) {
entryToPut = convertEntry(unboxedKey, unboxedValue, v);
} else {
entryToPut = v;
}
entryMap.put(unboxedKey, entryToPut);
});
return entryMap;
}
private Collection<K> convertKeys(Collection<? extends K> keys) {
List<K> list = new ArrayList<>(keys.size());
keys.forEach(k -> list.add(boxKey(k)));
return list;
}
private Set<?> convertKeys(Set<?> keys) {
// Use LinkedHashSet just incase if the set was ordered
Set<K> newKeys = new LinkedHashSet<>(keys.size());
keys.forEach(k -> newKeys.add(boxKey((K) k)));
return newKeys;
}
@Override
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
V returned = super.compute(boxKey(key), convertFunction(remappingFunction));
return unboxValue(returned);
}
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
V returned = super.computeIfAbsent(boxKey(key), convertFunction(mappingFunction));
return unboxValue(returned);
}
@Override
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
V returned = super.computeIfPresent(boxKey(key), convertFunction(remappingFunction));
return unboxValue(returned);
}
@Override
public V put(K key, V value) {
V returned = super.put(boxKey(key), boxValue(value));
return unboxValue(returned);
}
@Override
public V put(K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdleTime, TimeUnit maxIdleTimeUnit) {
V returned = super.put(boxKey(key), boxValue(value), lifespan, lifespanUnit, maxIdleTime, maxIdleTimeUnit);
return unboxValue(returned);
}
@Override
public V put(K key, V value, long lifespan, TimeUnit unit) {
V returned = super.put(boxKey(key), boxValue(value), lifespan, unit);
return unboxValue(returned);
}
@Override
public V put(K key, V value, Metadata metadata) {
V returned = super.put(boxKey(key), boxValue(value), metadata);
return unboxValue(returned);
}
@Override
public V putIfAbsent(K key, V value) {
V returned = super.putIfAbsent(boxKey(key), boxValue(value));
return unboxValue(returned);
}
@Override
public V putIfAbsent(K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdleTime, TimeUnit maxIdleTimeUnit) {
V returned = super.putIfAbsent(boxKey(key), boxValue(value), lifespan, lifespanUnit, maxIdleTime, maxIdleTimeUnit);
return unboxValue(returned);
}
@Override
public V putIfAbsent(K key, V value, long lifespan, TimeUnit unit) {
V returned = super.putIfAbsent(boxKey(key), boxValue(value), lifespan, unit);
return unboxValue(returned);
}
@Override
public V putIfAbsent(K key, V value, Metadata metadata) {
V returned = super.putIfAbsent(boxKey(key), boxValue(value), metadata);
return unboxValue(returned);
}
@Override
public void putAll(Map<? extends K, ? extends V> map, long lifespan, TimeUnit lifespanUnit, long maxIdleTime, TimeUnit maxIdleTimeUnit) {
super.putAll(boxMap(map), lifespan, lifespanUnit, maxIdleTime, maxIdleTimeUnit);
}
@Override
public void putAll(Map<? extends K, ? extends V> map, long lifespan, TimeUnit unit) {
super.putAll(boxMap(map), lifespan, unit);
}
@Override
public void putAll(Map<? extends K, ? extends V> t) {
super.putAll(boxMap(t));
}
@Override
public CompletableFuture<Void> putAllAsync(Map<? extends K, ? extends V> data) {
return super.putAllAsync(boxMap(data));
}
@Override
public CompletableFuture<Void> putAllAsync(Map<? extends K, ? extends V> data, long lifespan, TimeUnit lifespanUnit, long maxIdle, TimeUnit maxIdleUnit) {
return super.putAllAsync(boxMap(data), lifespan, lifespanUnit, maxIdle, maxIdleUnit);
}
@Override
public CompletableFuture<Void> putAllAsync(Map<? extends K, ? extends V> data, long lifespan, TimeUnit unit) {
return super.putAllAsync(boxMap(data), lifespan, unit);
}
@Override
public CompletableFuture<V> putAsync(K key, V value) {
return super.putAsync(boxKey(key), boxValue(value)).thenApply(unboxValue);
}
@Override
public CompletableFuture<V> putAsync(K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdle, TimeUnit maxIdleUnit) {
return super.putAsync(boxKey(key), boxValue(value), lifespan, lifespanUnit, maxIdle, maxIdleUnit)
.thenApply(unboxValue);
}
@Override
public CompletableFuture<V> putAsync(K key, V value, long lifespan, TimeUnit unit) {
return super.putAsync(boxKey(key), boxValue(value), lifespan, unit)
.thenApply(unboxValue);
}
@Override
public void putForExternalRead(K key, V value) {
super.putForExternalRead(boxKey(key), boxValue(value));
}
@Override
public void putForExternalRead(K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdle, TimeUnit maxIdleUnit) {
super.putForExternalRead(boxKey(key), boxValue(value), lifespan, lifespanUnit, maxIdle, maxIdleUnit);
}
@Override
public void putForExternalRead(K key, V value, long lifespan, TimeUnit unit) {
super.putForExternalRead(boxKey(key), boxValue(value), lifespan, unit);
}
@Override
public CompletableFuture<V> putIfAbsentAsync(K key, V value) {
return super.putIfAbsentAsync(boxKey(key), boxValue(value))
.thenApply(unboxValue);
}
@Override
public CompletableFuture<V> putIfAbsentAsync(K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdle, TimeUnit maxIdleUnit) {
return super.putIfAbsentAsync(boxKey(key), boxValue(value), lifespan, lifespanUnit, maxIdle, maxIdleUnit)
.thenApply(unboxValue);
}
@Override
public CompletableFuture<V> putIfAbsentAsync(K key, V value, long lifespan, TimeUnit unit) {
return super.putIfAbsentAsync(boxKey(key), boxValue(value), lifespan, unit)
.thenApply(unboxValue);
}
@Override
public V remove(Object key) {
V returned = super.remove(boxKey((K) key));
return unboxValue(returned);
}
@Override
public boolean remove(Object key, Object value) {
return super.remove(boxKey((K) key), boxValue((V) value));
}
@Override
public CompletableFuture<V> removeAsync(Object key) {
return super.removeAsync(boxKey((K) key))
.thenApply(unboxValue);
}
@Override
public CompletableFuture<Boolean> removeAsync(Object key, Object value) {
return super.removeAsync(boxKey((K) key), boxValue((V) value));
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
return super.replace(boxKey(key), boxValue(oldValue), boxValue(newValue));
}
@Override
public boolean replace(K key, V oldValue, V value, long lifespan, TimeUnit lifespanUnit, long maxIdleTime, TimeUnit maxIdleTimeUnit) {
return super.replace(boxKey(key), boxValue(oldValue), boxValue(value), lifespan, lifespanUnit, maxIdleTime, maxIdleTimeUnit);
}
@Override
public boolean replace(K key, V oldValue, V value, long lifespan, TimeUnit unit) {
return super.replace(boxKey(key), boxValue(oldValue), boxValue(value), lifespan, unit);
}
@Override
public V replace(K key, V value) {
V returned = super.replace(boxKey(key), boxValue(value));
return unboxValue(returned);
}
@Override
public V replace(K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdleTime, TimeUnit maxIdleTimeUnit) {
V returned = super.replace(boxKey(key), boxValue(value), lifespan, lifespanUnit, maxIdleTime, maxIdleTimeUnit);
return unboxValue(returned);
}
@Override
public V replace(K key, V value, long lifespan, TimeUnit unit) {
V returned = super.replace(boxKey(key), boxValue(value), lifespan, unit);
return unboxValue(returned);
}
@Override
public CompletableFuture<Boolean> replaceAsync(K key, V oldValue, V newValue) {
return super.replaceAsync(boxKey(key), boxValue(oldValue), boxValue(newValue));
}
@Override
public CompletableFuture<Boolean> replaceAsync(K key, V oldValue, V newValue, long lifespan, TimeUnit lifespanUnit, long maxIdle, TimeUnit maxIdleUnit) {
return super.replaceAsync(boxKey(key), boxValue(oldValue), boxValue(newValue), lifespan, lifespanUnit, maxIdle, maxIdleUnit);
}
@Override
public CompletableFuture<Boolean> replaceAsync(K key, V oldValue, V newValue, long lifespan, TimeUnit unit) {
return super.replaceAsync(boxKey(key), boxValue(oldValue), boxValue(newValue), lifespan, unit);
}
@Override
public CompletableFuture<V> replaceAsync(K key, V value) {
return super.replaceAsync(boxKey(key), boxValue(value))
.thenApply(unboxValue);
}
@Override
public CompletableFuture<V> replaceAsync(K key, V value, long lifespan, TimeUnit lifespanUnit, long maxIdle, TimeUnit maxIdleUnit) {
return super.replaceAsync(boxKey(key), boxValue(value), lifespan, lifespanUnit, maxIdle, maxIdleUnit)
.thenApply(unboxValue);
}
@Override
public CompletableFuture<V> replaceAsync(K key, V value, long lifespan, TimeUnit unit) {
return super.replaceAsync(boxKey(key), boxValue(value), lifespan, unit)
.thenApply(unboxValue);
}
@Override
protected void set(K key, V value) {
super.set(boxKey(key), boxValue(value));
}
@Override
public V get(Object key) {
V returned = super.get(boxKey((K) key));
return unboxValue(returned);
}
@Override
public CompletableFuture<V> getAsync(K key) {
return super.getAsync(boxKey(key))
.thenApply(unboxValue);
}
@Override
public void evict(K key) {
super.evict(boxKey((K) key));
}
@Override
public boolean containsKey(Object key) {
return super.containsKey(boxKey((K) key));
}
@Override
public boolean containsValue(Object value) {
return super.containsValue(boxValue((V) value));
}
@Override
public boolean replace(K key, V oldValue, V value, Metadata metadata) {
return super.replace(boxKey(key), boxValue(oldValue), boxValue(value), metadata);
}
@Override
public V replace(K key, V value, Metadata metadata) {
V returned = super.replace(boxKey(key), boxValue(value), metadata);
return unboxValue(returned);
}
@Override
public void putForExternalRead(K key, V value, Metadata metadata) {
super.putForExternalRead(boxKey(key), boxValue(value), metadata);
}
@Override
public CompletableFuture<V> putAsync(K key, V value, Metadata metadata) {
return super.putAsync(boxKey(key), boxValue(value), metadata)
.thenApply(unboxValue);
}
@Override
public void putAll(Map<? extends K, ? extends V> map, Metadata metadata) {
super.putAll(boxMap(map), metadata);
}
@Override
public boolean lock(Collection<? extends K> keys) {
return super.lock(convertKeys(keys));
}
@Override
public boolean lock(K... keys) {
K[] newKeys = (K[]) new Object[keys.length];
for (int i = 0; i < keys.length; ++i) {
newKeys[i] = boxKey(keys[i]);
}
return super.lock(newKeys);
}
@Override
public void removeExpired(K key, V value, Long lifespan) {
super.removeExpired(boxKey(key), boxValue(value), lifespan);
}
final ConverterEntryMapper entryMapper = new ConverterEntryMapper();
private <E extends Entry<K, V>> CacheSet<E> cast(CacheSet set) {
return (CacheSet<E>) set;
}
private class TypeConverterEntrySet extends AbstractCloseableIteratorCollection<CacheEntry<K, V>, K, V> implements CacheSet<CacheEntry<K, V>> {
private final CacheSet<CacheEntry<K, V>> actualCollection;
public TypeConverterEntrySet(Cache<K, V> cache, CacheSet<CacheEntry<K, V>> actualCollection) {
super(cache);
this.actualCollection = actualCollection;
}
@Override
public CacheStream<CacheEntry<K, V>> stream() {
return actualCollection.stream().map(entryMapper);
}
@Override
public CacheStream<CacheEntry<K, V>> parallelStream() {
return actualCollection.parallelStream().map(entryMapper);
}
@Override
public CloseableIterator<CacheEntry<K, V>> iterator() {
return new BaseTypeConverterInterceptor.TypeConverterIterator<>(actualCollection.iterator(), getConverter(),
entryFactory);
}
@Override
public CloseableSpliterator<CacheEntry<K, V>> spliterator() {
return new CloseableSpliteratorMapper<>(actualCollection.spliterator(),
(InjectiveFunction & Function<CacheEntry<K, V>, CacheEntry<K, V>>) entry -> {
K key = entry.getKey();
K unboxedKey = unboxKey(key);
V value = entry.getValue();
V unboxedValue = unboxValue(value);
// If boxed of either don't match unboxed, make sure to use unboxed
if (unboxedKey != key || unboxedValue != value) {
return convertEntry(unboxedKey, unboxedValue, entry);
}
return entry;
});
}
@Override
public boolean contains(Object o) {
Map.Entry<K, V> entry = toEntry(o);
if (entry != null) {
return actualCollection.contains(entry);
}
return false;
}
@Override
public boolean remove(Object o) {
Map.Entry<K, V> entry = toEntry(o);
if (entry != null) {
return actualCollection.remove(entry);
}
return false;
}
Map.Entry toEntry(Object o) {
if (o instanceof Map.Entry) {
Map.Entry<K, V> entry = (Map.Entry<K, V>) o;
K key = entry.getKey();
K newKey = boxKey(key);
V value = entry.getValue();
V newValue = boxValue(value);
if (key != newKey || value != newValue) {
if (o instanceof CacheEntry) {
CacheEntry returned = (CacheEntry) o;
return convertEntry(newKey, newValue, returned);
} else {
return entryFactory.create(newKey, newValue, (Metadata) null);
}
}
return entry;
}
return null;
}
}
@Override
public CacheSet<Entry<K, V>> entrySet() {
return cast(new TypeConverterEntrySet(this, cast(super.cacheEntrySet())));
}
@Override
public CacheSet<CacheEntry<K, V>> cacheEntrySet() {
return new TypeConverterEntrySet(this, super.cacheEntrySet());
}
final ConverterKeyMapper keyMapper = new ConverterKeyMapper();
class TypeConverterKeySet extends AbstractCloseableIteratorCollection<K, K, V> implements CacheSet<K> {
private final CacheSet<K> actualCollection;
public TypeConverterKeySet(Cache<K, V> cache, CacheSet<K> actualCollection) {
super(cache);
this.actualCollection = actualCollection;
}
@Override
public CacheStream<K> stream() {
return actualCollection.stream().map(keyMapper);
}
@Override
public CacheStream<K> parallelStream() {
return actualCollection.parallelStream().map(keyMapper);
}
@Override
public CloseableIterator<K> iterator() {
return new CloseableIteratorMapper<>(actualCollection.iterator(), unboxKey);
}
@Override
public CloseableSpliterator<K> spliterator() {
return new CloseableSpliteratorMapper<>(actualCollection.spliterator(), unboxKey);
}
@Override
public boolean contains(Object o) {
return actualCollection.contains(boxKey((K) o));
}
@Override
public boolean remove(Object o) {
return actualCollection.remove(boxKey((K) o));
}
}
@Override
public CacheSet<K> keySet() {
return new TypeConverterKeySet(this, super.keySet());
}
final ConverterValueMapper valueMapper = new ConverterValueMapper();
class TypeConverterValuesCollection extends AbstractCloseableIteratorCollection<V, K, V> implements CacheCollection<V> {
private final CacheCollection<V> actualCollection;
public TypeConverterValuesCollection(Cache<K, V> cache, CacheCollection<V> actualCollection) {
super(cache);
this.actualCollection = actualCollection;
}
@Override
public CacheStream<V> stream() {
return actualCollection.stream().map(valueMapper);
}
@Override
public CacheStream<V> parallelStream() {
return actualCollection.parallelStream().map(valueMapper);
}
@Override
public CloseableIterator<V> iterator() {
return new CloseableIteratorMapper<>(actualCollection.iterator(), unboxValue);
}
@Override
public CloseableSpliterator<V> spliterator() {
return new CloseableSpliteratorMapper<>(actualCollection.spliterator(), unboxValue);
}
@Override
public boolean contains(Object o) {
return actualCollection.contains(boxValue((V) o));
}
@Override
public boolean remove(Object o) {
return actualCollection.remove(boxValue((V) o));
}
}
@Override
public CacheCollection<V> values() {
return new TypeConverterValuesCollection(this, super.values());
}
@Override
public Map<K, V> getGroup(String groupName) {
Map<K, V> returned = super.getGroup(groupName);
return unboxMap(returned);
}
@Override
public CacheEntry<K, V> getCacheEntry(Object key) {
K boxedKey = boxKey((K) key);
CacheEntry<K, V> returned = super.getCacheEntry(boxedKey);
if (returned != null) {
V originalValue = returned.getValue();
V unboxedValue = unboxValue(originalValue);
// If boxed of either don't match unboxed, make sure to use unboxed
if (boxedKey != key || unboxedValue != originalValue) {
return convertEntry((K) key, unboxedValue, returned);
}
}
return returned;
}
@Override
public Map<K, V> getAll(Set<?> keys) {
Map<K, V> returned = super.getAll(convertKeys(keys));
return unboxMap(returned);
}
@Override
public Map<K, CacheEntry<K, V>> getAllCacheEntries(Set<?> keys) {
Map<K, CacheEntry<K, V>> returned = super.getAllCacheEntries(convertKeys(keys));
return unboxEntryMap(returned);
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
super.forEach((k, v) -> {
K newK = unboxKey(k);
V newV = unboxValue(v);
action.accept(newK, newV);
});
}
@Override
public V getOrDefault(Object key, V defaultValue) {
V returned = super.getOrDefault(boxKey((K) key), defaultValue);
if (returned == defaultValue) {
return returned;
}
return unboxValue(returned);
}
@Override
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
V returned = super.merge(boxKey(key), value, (oldV, newV) -> {
// oldV will be boxed, however we left newV alone
V oldVUnboxed = unboxValue(oldV);
return remappingFunction.apply(oldVUnboxed, newV);
});
return unboxValue(returned);
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
super.replaceAll(convertFunction(function));
}
@Override
public AdvancedCache<K, V> with(ClassLoader classLoader) {
AdvancedCache<K, V> returned = super.with(classLoader);
if (returned != this && returned instanceof TypeConverterDelegatingAdvancedCache) {
((TypeConverterDelegatingAdvancedCache) returned).entryFactory = this.entryFactory;
}
return returned;
}
@Override
public AdvancedCache<K, V> withFlags(Flag... flags) {
AdvancedCache<K, V> returned = super.withFlags(flags);
if (returned != this && returned instanceof TypeConverterDelegatingAdvancedCache) {
((TypeConverterDelegatingAdvancedCache) returned).entryFactory = this.entryFactory;
}
return returned;
}
}