package io.lumify.core.model.user; import com.google.inject.Inject; import io.lumify.core.config.Configuration; import io.lumify.core.exception.LumifyException; import io.lumify.core.util.LumifyLogger; import io.lumify.core.util.LumifyLoggerFactory; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.atomic.AtomicValue; import org.apache.curator.framework.recipes.atomic.DistributedAtomicInteger; import org.apache.curator.retry.BoundedExponentialBackoffRetry; public class UserSessionCounterRepository { private static final LumifyLogger LOGGER = LumifyLoggerFactory.getLogger(UserSessionCounterRepository.class); public static final String DEFAULT_PATH_PREFIX = "/lumify/userSessionCounters/"; private static final int BASE_SLEEP_TIME_MS = 10; private static final int MAX_SLEEP_TIME_MS = 2000; private static final int MAX_RETRIES = 5; private final CuratorFramework curatorFramework; private final String pathPrefix; @Inject public UserSessionCounterRepository(final CuratorFramework curatorFramework, final Configuration configuration) { this.curatorFramework = curatorFramework; this.pathPrefix = configuration.get(Configuration.USER_SESSION_COUNTER_PATH_PREFIX, DEFAULT_PATH_PREFIX); } public int incrementAndGet(String userId) { LOGGER.debug("incrementing user session counter for %s", userId); int count = adjustAndGet(userId, Direction.INCREMENT); LOGGER.debug("user session counter for %s is now %d", userId, count); return count; } public int decrementAndGet(String userId) { LOGGER.debug("decrementing user session counter for %s", userId); int count = adjustAndGet(userId, Direction.DECREMENT); if (count < 1) { removeCounter(userId); } LOGGER.debug("user session counter for %s is now %d", userId, count); return count; } private int adjustAndGet(String userId, Direction direction) { RetryPolicy retryPolicy = new BoundedExponentialBackoffRetry(BASE_SLEEP_TIME_MS, MAX_SLEEP_TIME_MS, MAX_RETRIES); DistributedAtomicInteger distributedAtomicInteger = new DistributedAtomicInteger(curatorFramework, counterPath(userId), retryPolicy); try { distributedAtomicInteger.initialize(0); // this will respect an existing value but set uninitialized values to 0 } catch (Exception e) { throw new LumifyException("failed to initialize counter for " + userId); } try { AtomicValue<Integer> count = direction == Direction.INCREMENT ? distributedAtomicInteger.increment() : distributedAtomicInteger.decrement(); if (count.succeeded()) { return count.postValue(); } else { throw new LumifyException("failed to " + direction + " counter for " + userId); } } catch (Exception e) { throw new LumifyException("failed to " + direction + " counter for " + userId, e); } } private void removeCounter(String userId) { LOGGER.debug("removing user session counter for %s", userId); try { curatorFramework.delete().inBackground().forPath(counterPath(userId)); } catch (Exception e) { throw new LumifyException("failed to remove user session counter for " + userId, e); } } private String counterPath(String userId) { return pathPrefix + userId; } public enum Direction { INCREMENT, DECREMENT } }