package org.infinispan.counter.impl.strong; import static org.infinispan.counter.impl.entries.CounterValue.newCounterValue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import org.infinispan.AdvancedCache; import org.infinispan.commons.api.functional.FunctionalMap; import org.infinispan.counter.api.CounterConfiguration; import org.infinispan.counter.api.CounterListener; import org.infinispan.counter.api.CounterState; import org.infinispan.counter.api.Handle; import org.infinispan.counter.api.StrongCounter; import org.infinispan.counter.exception.CounterException; import org.infinispan.counter.impl.entries.CounterValue; import org.infinispan.counter.impl.function.AddFunction; import org.infinispan.counter.impl.function.CompareAndSetFunction; import org.infinispan.counter.impl.function.InitializeCounterFunction; import org.infinispan.counter.impl.function.ReadFunction; import org.infinispan.counter.impl.function.ResetFunction; import org.infinispan.counter.impl.listener.CounterEventImpl; import org.infinispan.counter.impl.listener.CounterFilterAndConvert; import org.infinispan.counter.impl.listener.NotificationManager; import org.infinispan.counter.logging.Log; import org.infinispan.counter.util.Utils; import org.infinispan.functional.impl.FunctionalMapImpl; import org.infinispan.functional.impl.ReadOnlyMapImpl; import org.infinispan.functional.impl.ReadWriteMapImpl; import org.infinispan.notifications.Listener; import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified; import org.infinispan.notifications.cachelistener.event.CacheEntryEvent; /** * A base strong consistent counter implementation. * <p> * Implementation: The value is stored in a single key and it uses the Infinispan's concurrency control and distribution * to apply the write and reads. It uses the functional API. * <p> * Writes: The writes are performed by the functional API in order. The single key approach allows us to provide atomic * properties for the counter value. * <p> * Reads: The reads read the value from the cache and it can go remotely. * <p> * Weak Reads: This implementation supports weak cached reads. It uses clustered listeners to receive the notifications * of the actual value to store it locally. * * @author Pedro Ruivo * @since 9.0 */ @Listener(clustered = true, observation = Listener.Observation.POST, sync = true) public abstract class AbstractStrongCounter implements StrongCounter { final StrongCounterKey key; private final FunctionalMap.ReadWriteMap<StrongCounterKey, CounterValue> readWriteMap; private final FunctionalMap.ReadOnlyMap<StrongCounterKey, CounterValue> readOnlyMap; private final NotificationManager notificationManager; private final CounterConfiguration configuration; private CounterValue weakCounter; AbstractStrongCounter(String counterName, AdvancedCache<StrongCounterKey, CounterValue> cache, CounterConfiguration configuration) { FunctionalMapImpl<StrongCounterKey, CounterValue> functionalMap = FunctionalMapImpl.create(cache) .withParams(Utils.getPersistenceMode(configuration.storage())); this.key = new StrongCounterKey(counterName); this.readWriteMap = ReadWriteMapImpl.create(functionalMap); this.readOnlyMap = ReadOnlyMapImpl.create(functionalMap); this.notificationManager = new NotificationManager(); this.weakCounter = null; this.configuration = configuration; registerListener(cache); } public final void init() { try { CounterValue existingValue = readWriteMap.eval(key, new InitializeCounterFunction<>(configuration)).get(); initCounterState(existingValue == null ? newCounterValue(configuration) : existingValue); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new CounterException(e); } catch (ExecutionException e) { throw Utils.rethrowAsCounterException(e); } } @Override public final String getName() { return key.getCounterName().toString(); } @Override public final CompletableFuture<Long> getValue() { return readOnlyMap.eval(key, ReadFunction.getInstance()).thenApply(this::handleReadResult); } @Override public final CompletableFuture<Long> addAndGet(long delta) { return readWriteMap.eval(key, new AddFunction<>(delta)).thenApply(this::handleAddResult); } @Override public final CompletableFuture<Void> reset() { return readWriteMap.eval(key, ResetFunction.getInstance()); } @Override public final <T extends CounterListener> Handle<T> addListener(T listener) { return notificationManager.addListener(listener); } @Override public CompletableFuture<Boolean> compareAndSet(long expect, long update) { return readWriteMap.eval(key, new CompareAndSetFunction<>(expect, update)).thenApply(this::handleCASResult); } @CacheEntryModified public synchronized void updateState(CacheEntryEvent<StrongCounterKey, CounterValue> event) { CounterValue snapshot = event.getValue(); notificationManager.notify(CounterEventImpl.create(weakCounter, snapshot)); weakCounter = snapshot; } @Override public final CounterConfiguration getConfiguration() { return configuration; } protected abstract Boolean handleCASResult(CounterState state); /** * Extracts and validates the value after a read. * <p> * Any exception should be thrown using {@link CompletionException}. * * @param counterValue The new {@link CounterValue}. * @return The new value stored in {@link CounterValue}. */ protected abstract long handleAddResult(CounterValue counterValue); /** * @return The {@link Log} to use. */ protected abstract Log getLog(); /** * Registers this instance as a cluster listener. * <p> * Note: It must be invoked when initialize the instance. */ private void registerListener(AdvancedCache<StrongCounterKey, CounterValue> cache) { CounterFilterAndConvert<StrongCounterKey> filter = new CounterFilterAndConvert<>(key.getCounterName()); cache.addListener(this, filter, filter); } /** * Initializes the weak value. * * @param value The initial value. */ private synchronized void initCounterState(CounterValue value) { if (weakCounter == null) { weakCounter = value; } } /** * Retrieves and validate the value after a read. * * @param value The current value. * @return The current value. */ private long handleReadResult(Long value) { if (value != null) { return value; } throw new CompletionException(getLog().counterDeleted()); } }