package org.radargun.util; import org.radargun.traits.Debugable; import java.io.IOException; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * @author Matej Cimbora */ public class CacheTraitRepository extends CoreTraitRepository { public static Map<Class<?>, Object> getAllTraits() { Map<Class<?>, Object> traitMap = new HashMap<>(CoreTraitRepository.getAllTraits()); ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap(); traitMap.put(org.radargun.traits.BasicOperations.class, new BasicOperations(new BasicOperationsCache(concurrentHashMap))); traitMap.put(org.radargun.traits.BulkOperations.class, new BulkOperations(new BulkOperationsCache(concurrentHashMap))); traitMap.put(org.radargun.traits.ConditionalOperations.class, new ConditionalOperations(new ConditionalOperations.ConditionalOperationsCache(concurrentHashMap))); traitMap.put(org.radargun.traits.Iterable.class, new Iterable<>(concurrentHashMap)); traitMap.put(org.radargun.traits.CacheInformation.class, new CacheInformation(new CacheInformation.Cache(concurrentHashMap))); traitMap.put(org.radargun.traits.DistributedTaskExecutor.class, new DistributedTaskExecutor(concurrentHashMap)); traitMap.put(org.radargun.traits.TopologyHistory.class, new TopologyHistory()); traitMap.put(org.radargun.traits.Debugable.class, new Debuggable()); traitMap.put(org.radargun.traits.CacheListeners.class, new CacheListeners()); return traitMap; } public static class BasicOperations implements org.radargun.traits.BasicOperations { private final BasicOperationsCache cache; public BasicOperations(BasicOperationsCache cache) { this.cache = cache; } @Override public <K, V> Cache<K, V> getCache(String cacheName) { return cache; } } public static class BasicOperationsCache<K, V> implements BasicOperations.Cache<K, V>, Wrappable { protected ConcurrentHashMap<K, V> cache; public BasicOperationsCache() { this.cache = new ConcurrentHashMap<>(); } public BasicOperationsCache(ConcurrentHashMap<K, V> cache) { this.cache = cache; } @Override public Object get(Object key) { return cache.get(key); } @Override public boolean containsKey(Object key) { return cache.containsKey(key); } @Override public void put(Object key, Object value) { cache.put((K) key, (V) value); } @Override public Object getAndPut(Object key, Object value) { return cache.put((K) key, (V) value); } @Override public boolean remove(Object key) { return cache.remove(key) != null; } @Override public Object getAndRemove(Object key) { return cache.remove(key); } @Override public void clear() { cache.clear(); } public int size() { return cache.size(); } @Override public TxResource wrap() { return new BasicOperationsCacheTxWrapper<>(this); } } public static class BasicOperationsCacheTxWrapper<K, V> extends BasicOperationsCache<K, V> implements TxResource { private final BasicOperationsCache<K, V> localCache; private ConcurrentHashMap<K, V> operationsBuffer; public BasicOperationsCacheTxWrapper(BasicOperationsCache<K, V> cache) { super(cache.cache); this.localCache = cache; } @Override public Object get(Object key) { if (operationsBuffer == null) { throw new IllegalStateException("Tx has not begun yet"); } return operationsBuffer.get(key); } @Override public boolean containsKey(Object key) { if (operationsBuffer == null) { throw new IllegalStateException("Tx has not begun yet"); } return operationsBuffer.containsKey(key); } @Override public void put(Object key, Object value) { if (operationsBuffer == null) { throw new IllegalStateException("Tx has not begun yet"); } operationsBuffer.put((K) key, (V) value); } @Override public Object getAndPut(Object key, Object value) { if (operationsBuffer == null) { throw new IllegalStateException("Tx has not begun yet"); } V result = operationsBuffer.get(key); operationsBuffer.put((K) key, (V) value); return result; } @Override public boolean remove(Object key) { if (operationsBuffer == null) { throw new IllegalStateException("Tx has not begun yet"); } return operationsBuffer.remove(key) != null; } @Override public Object getAndRemove(Object key) { if (operationsBuffer == null) { throw new IllegalStateException("Tx has not begun yet"); } V result = operationsBuffer.get(key); operationsBuffer.remove(key); return result; } @Override public void clear() { if (operationsBuffer == null) { throw new IllegalStateException("Tx has not begun yet"); } operationsBuffer.clear(); } @Override public void begin() { operationsBuffer = new ConcurrentHashMap<>(localCache.cache); } @Override public void commit() { localCache.cache.putAll(operationsBuffer); operationsBuffer = null; } @Override public void rollback() { operationsBuffer = null; } } public static class BulkOperations implements org.radargun.traits.BulkOperations { private final BulkOperationsCache cache; public BulkOperations(BulkOperationsCache cache) { this.cache = cache; } @Override public <K, V> Cache<K, V> getCache(String cacheName, boolean preferAsync) { return cache; } } public static class BulkOperationsCache<K, V> implements BulkOperations.Cache<K, V>, Wrappable { protected ConcurrentHashMap<K, V> cache; public BulkOperationsCache() { this.cache = new ConcurrentHashMap<>(); } public BulkOperationsCache(ConcurrentHashMap<K, V> cache) { this.cache = cache; } @Override public Map<K, V> getAll(Set<K> keys) { Map<K, V> result = new HashMap<>(cache); result.keySet().retainAll(keys); return result; } @Override public void putAll(Map<K, V> entries) { cache.putAll(entries); } @Override public void removeAll(Set<K> keys) { cache.keySet().removeAll(keys); } public int size() { return cache.size(); } @Override public TxResource wrap() { return new BulkOperationsCacheTxWrapper<>(this); } } public static class BulkOperationsCacheTxWrapper<K, V> extends BulkOperationsCache<K, V> implements TxResource { private final BulkOperationsCache localCache; private ConcurrentHashMap<K, V> operationsBuffer; public BulkOperationsCacheTxWrapper(BulkOperationsCache cache) { this.localCache = cache; } @Override public Map<K, V> getAll(Set<K> keys) { if (operationsBuffer == null) { throw new IllegalStateException("Tx has not begun yet"); } Map<K, V> result = new HashMap<>(operationsBuffer); result.keySet().retainAll(keys); return result; } @Override public void putAll(Map<K, V> entries) { if (operationsBuffer == null) { throw new IllegalStateException("Tx has not begun yet"); } operationsBuffer.putAll(entries); } @Override public void removeAll(Set<K> keys) { if (operationsBuffer == null) { throw new IllegalStateException("Tx has not begun yet"); } operationsBuffer.keySet().removeAll(keys); } public int size() { return localCache.size(); } @Override public void begin() { operationsBuffer = new ConcurrentHashMap<>(localCache.cache); } @Override public void commit() { localCache.cache.putAll(operationsBuffer); operationsBuffer = null; } @Override public void rollback() { operationsBuffer = null; } } // TODO just a dummy implementation to satisfy mandatory dependencies, needs to work with existing cache instances (BasicOperationsCache etc) public static class CacheInformation implements org.radargun.traits.CacheInformation { private static final String DEFAULT_CACHE_NAME = "default"; private Cache cache; public CacheInformation(Cache cache) { this.cache = cache; } @Override public String getDefaultCacheName() { return DEFAULT_CACHE_NAME; } @Override public Collection<String> getCacheNames() { return Arrays.asList(DEFAULT_CACHE_NAME); } @Override public Cache getCache(String cacheName) { return cache; } private static class Cache implements org.radargun.traits.CacheInformation.Cache { private ConcurrentHashMap cache; public Cache() { this.cache = new ConcurrentHashMap(); } public Cache(ConcurrentHashMap cache) { this.cache = cache; } @Override public long getOwnedSize() { return cache.size(); } @Override public long getLocallyStoredSize() { return cache.size(); } @Override public long getMemoryStoredSize() { return cache.size(); } @Override public long getTotalSize() { return cache.size(); } @Override public Map<?, Long> getStructuredSize() { Map<String, Long> structuredSizeMap = new HashMap<>(); structuredSizeMap.put("part", Long.valueOf(cache.size())); return structuredSizeMap; } @Override public int getNumReplicas() { return 1; } @Override public int getEntryOverhead() { return 0; } } } public static class ConditionalOperations implements org.radargun.traits.ConditionalOperations { private ConditionalOperationsCache cache; public ConditionalOperations(ConditionalOperationsCache cache) { this.cache = cache; } @Override public <K, V> Cache<K, V> getCache(String cacheName) { return cache; } private static class ConditionalOperationsCache<K, V> implements org.radargun.traits.ConditionalOperations.Cache<K, V> { private ConcurrentHashMap<K, V> cache; public ConditionalOperationsCache() { this.cache = new ConcurrentHashMap<>(); } public ConditionalOperationsCache(ConcurrentHashMap<K, V> cache) { this.cache = cache; } @Override public boolean putIfAbsent(K key, V value) { return cache.putIfAbsent(key, value) == null; } // TODO check implementation (chm has a different one) @Override public boolean remove(K key, V oldValue) { return cache.remove(key, oldValue); } @Override public boolean replace(K key, V oldValue, V newValue) { return cache.replace(key, oldValue, newValue); } @Override public boolean replace(K key, V value) { return cache.replace(key, value) != null; } @Override public V getAndReplace(K key, V value) { for (;;) { V oldValue = cache.get(key); if (oldValue == null) return null; if (cache.replace(key, oldValue, value)) return oldValue; } } } } public static class Iterable<K, V, T> implements org.radargun.traits.Iterable { private ConcurrentHashMap<K, V> cache; public Iterable(ConcurrentHashMap<K, V> cache) { this.cache = cache; } @Override public <K, V> CloseableIterator<Map.Entry<K, V>> getIterator(String containerName, Filter<K, V> filter) { return new CloseableIterator<>(cache, null, null); } @Override public <K, V, T> CloseableIterator<T> getIterator(String containerName, Filter<K, V> filter, Converter<K, V, T> converter) { return new CloseableIterator<>(cache, null, null); } private static class CloseableIterator<T> implements org.radargun.traits.Iterable.CloseableIterator<T> { private Iterator iterator; public CloseableIterator(ConcurrentHashMap cache, Filter filter, Converter converter) { this.iterator = cache.entrySet().iterator(); } @Override public void close() throws IOException { } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public T next() { return (T) iterator.next(); } } } public static class TopologyHistory implements org.radargun.traits.TopologyHistory { private List<org.radargun.traits.TopologyHistory.Event> topologyChangeHistory = new LinkedList<>(); private List<org.radargun.traits.TopologyHistory.Event> rehashHistory = new LinkedList<>(); private List<org.radargun.traits.TopologyHistory.Event> cacheStatusChangeHistory = new LinkedList<>(); private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); private Event.EventType lastTopologyChangeHistoryType; private Event.EventType lastRehashHistoryType; public void triggerHistoryChanges(long delay, long period, TimeUnit timeUnit) { executorService.scheduleAtFixedRate(() -> { if (lastTopologyChangeHistoryType == null) { lastTopologyChangeHistoryType = Event.EventType.START; } else if (lastTopologyChangeHistoryType == Event.EventType.START) { lastTopologyChangeHistoryType = Event.EventType.END; } else if (lastTopologyChangeHistoryType == Event.EventType.END) { lastTopologyChangeHistoryType = Event.EventType.START; } if (lastRehashHistoryType == null) { lastRehashHistoryType = Event.EventType.START; } else if (lastRehashHistoryType == Event.EventType.START) { lastRehashHistoryType = Event.EventType.END; } else if (lastRehashHistoryType == Event.EventType.END) { lastRehashHistoryType = Event.EventType.START; } topologyChangeHistory.add(new Event(new Date(), lastTopologyChangeHistoryType, 1, 1)); rehashHistory.add(new Event(new Date(), lastRehashHistoryType, 1, 1)); cacheStatusChangeHistory.add(new Event(new Date(), Event.EventType.SINGLE, 1, 1)); }, delay, period, timeUnit); } public void stopHistoryChanges() throws InterruptedException { executorService.shutdown(); if (!topologyChangeHistory.isEmpty()) { org.radargun.traits.TopologyHistory.Event event = topologyChangeHistory.get(topologyChangeHistory.size() - 1); if (event.getType() == org.radargun.traits.TopologyHistory.Event.EventType.START) { topologyChangeHistory.add(new Event(new Date(event.getTime().getTime() + 1), org.radargun.traits.TopologyHistory.Event.EventType.END, 1, 1)); } } if (!rehashHistory.isEmpty()) { org.radargun.traits.TopologyHistory.Event event = rehashHistory.get(rehashHistory.size() - 1); if (event.getType() == org.radargun.traits.TopologyHistory.Event.EventType.START) { rehashHistory.add(new Event(new Date(event.getTime().getTime() + 1), org.radargun.traits.TopologyHistory.Event.EventType.END, 1, 1)); } } } @Override public List<org.radargun.traits.TopologyHistory.Event> getTopologyChangeHistory(String containerName) { return Collections.unmodifiableList(topologyChangeHistory); } @Override public List<org.radargun.traits.TopologyHistory.Event> getRehashHistory(String containerName) { return Collections.unmodifiableList(rehashHistory); } @Override public List<org.radargun.traits.TopologyHistory.Event> getCacheStatusChangeHistory(String containerName) { return Collections.unmodifiableList(cacheStatusChangeHistory); } private static class Event extends org.radargun.traits.TopologyHistory.Event { private Date time; private EventType type; private int membersAtStart; private int membersAtEnd; public Event(Date time, EventType type, int membersAtStart, int membersAtEnd) { this.time = time; this.type = type; this.membersAtStart = membersAtStart; this.membersAtEnd = membersAtEnd; } @Override public Date getTime() { return time; } @Override public EventType getType() { return type; } @Override public int getMembersAtStart() { return membersAtStart; } @Override public int getMembersAtEnd() { return membersAtEnd; } @Override public org.radargun.traits.TopologyHistory.Event copy() { return new Event(time, type, membersAtStart, membersAtEnd); } } } public static class DistributedTaskExecutor implements org.radargun.traits.DistributedTaskExecutor { private ConcurrentHashMap cache; public DistributedTaskExecutor(ConcurrentHashMap cache) { this.cache = cache; } @Override public Builder builder(String cacheName) { return new Builder(cache); } private static class Builder implements org.radargun.traits.DistributedTaskExecutor.Builder { private ConcurrentHashMap cache; public Builder(ConcurrentHashMap cache) { this.cache = cache; } @Override public org.radargun.traits.DistributedTaskExecutor.Builder callable(Callable callable) { return this; } @Override public org.radargun.traits.DistributedTaskExecutor.Builder executionPolicy(String executionPolicy) { return this; } @Override public org.radargun.traits.DistributedTaskExecutor.Builder failoverPolicy(String failoverPolicy) { return this; } @Override public org.radargun.traits.DistributedTaskExecutor.Builder nodeAddress(String nodeAddress) { return this; } @Override public Task build() { return new Task(cache); } } private static class Task implements org.radargun.traits.DistributedTaskExecutor.Task { private ConcurrentHashMap cache; public Task(ConcurrentHashMap cache) { this.cache = cache; } @Override public List<Future> execute() { return Arrays.asList(new Future<Object>() { private boolean isCancelled; @Override public boolean cancel(boolean mayInterruptIfRunning) { isCancelled = true; return isCancelled; } @Override public boolean isCancelled() { return isCancelled; } @Override public boolean isDone() { return true; } @Override public Object get() throws InterruptedException, ExecutionException { return Collections.unmodifiableMap(cache); } @Override public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return Collections.unmodifiableMap(cache); } }); } } } public static class Debuggable implements Debugable { @Override public Cache getCache(String cacheName) { return new Cache(); } private static class Cache implements Debugable.Cache { @Override public void debugKey(Object key) { System.out.println(key); } @Override public void debugInfo() { System.out.println("Debug info"); } } } public static class CacheListeners implements org.radargun.traits.CacheListeners { private List<CreatedListener> createdListeners = new ArrayList<>(); private List<UpdatedListener> updatedListeners = new ArrayList<>(); private List<RemovedListener> removedListeners = new ArrayList<>(); private List<EvictedListener> evictedListeners = new ArrayList<>(); private List<ExpiredListener> expiredListeners = new ArrayList<>(); @Override public Collection<Type> getSupportedListeners() { return Arrays.asList(org.radargun.traits.CacheListeners.Type.values()); } @Override public void addCreatedListener(String cacheName, CreatedListener listener, boolean sync) { createdListeners.add(listener); } @Override public void addUpdatedListener(String cacheName, UpdatedListener listener, boolean sync) { updatedListeners.add(listener); } @Override public void addRemovedListener(String cacheName, RemovedListener listener, boolean sync) { removedListeners.add(listener); } @Override public void addEvictedListener(String cacheName, EvictedListener listener, boolean sync) { evictedListeners.add(listener); } @Override public void addExpiredListener(String cacheName, ExpiredListener listener, boolean sync) { expiredListeners.add(listener); } @Override public void removeCreatedListener(String cacheName, CreatedListener listener, boolean sync) { createdListeners.remove(listener); } @Override public void removeUpdatedListener(String cacheName, UpdatedListener listener, boolean sync) { updatedListeners.remove(listener); } @Override public void removeRemovedListener(String cacheName, RemovedListener listener, boolean sync) { removedListeners.remove(listener); } @Override public void removeEvictedListener(String cacheName, EvictedListener listener, boolean sync) { evictedListeners.remove(listener); } @Override public void removeExpiredListener(String cacheName, ExpiredListener listener, boolean sync) { expiredListeners.remove(listener); } } }