org.togglz.redis.RedisLettuceStateRepository Maven / Gradle / Ivy
package org.togglz.redis;
import io.lettuce.core.api.StatefulConnection;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisHashCommands;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.togglz.core.Feature;
import org.togglz.core.repository.FeatureState;
import org.togglz.core.repository.StateRepository;
import java.util.Map;
import java.util.Optional;
/**
* A state repository which stores the feature state in Redis using Lettuce client.
*
* The class provides a builder which can be used to configure the state repository instance
* (e.g. StatefulConnection pool, key prefix).
*
*
* A state repository is able to work in standalone mode (using StatefulRedisConnection) and
* cluster mode (using StatefulRedisClusterConnection)
*
*
* Lettuce doesn't provide default client creator (localhost as a default host, 6379 as a default port)
* so an exception is made when StatefulConnection pool is not set correctly
*
*
* @author Jakub Klebek
*/
public class RedisLettuceStateRepository implements StateRepository {
public static final String ENABLED_FIELD = "enabled";
public static final String STRATEGY_FIELD = "strategy";
public static final String PARAMETER_PREFIX = "parameter:";
public static final int PARAMETER_PREFIX_LENGTH = PARAMETER_PREFIX.length();
protected final GenericObjectPool> pool;
protected final String keyPrefix;
private RedisLettuceStateRepository(final Builder builder) {
keyPrefix = builder.keyPrefix;
pool = Optional.ofNullable(builder.lettucePool)
.orElseThrow(() -> new RedisLettuceStateRepositoryException("Missing lettuce pool configuration"));
}
@Override
public FeatureState getFeatureState(final Feature feature) {
try (final StatefulConnection connection = pool.borrowObject()) {
final RedisHashCommands commands = getCommands(connection);
final Map redisMap = commands.hgetall(keyPrefix + feature.name());
if (redisMap.isEmpty()) {
return null;
}
final FeatureState featureState = new FeatureState(feature);
featureState.setEnabled(Boolean.valueOf(redisMap.get(ENABLED_FIELD)));
featureState.setStrategyId(redisMap.get(STRATEGY_FIELD));
for (final Map.Entry entry : redisMap.entrySet()) {
final String key = entry.getKey();
if (key.startsWith(PARAMETER_PREFIX)) {
featureState.setParameter(key.substring(PARAMETER_PREFIX_LENGTH), entry.getValue());
}
}
return featureState;
} catch (Exception e) {
throw new RedisLettuceStateRepositoryException("Error while getting feature state", e);
}
}
@Override
public void setFeatureState(final FeatureState featureState) {
try (final StatefulConnection connection = pool.borrowObject()) {
final RedisHashCommands commands = getCommands(connection);
final String featureKey = keyPrefix + featureState.getFeature().name();
commands.hset(featureKey, ENABLED_FIELD, Boolean.toString(featureState.isEnabled()));
final String strategyId = featureState.getStrategyId();
if (strategyId != null) {
commands.hset(featureKey, STRATEGY_FIELD, strategyId);
}
final Map parameterMap = featureState.getParameterMap();
if (parameterMap != null) {
for (final Map.Entry entry : parameterMap.entrySet()) {
commands.hset(featureKey, PARAMETER_PREFIX + entry.getKey(), entry.getValue());
}
}
} catch (Exception e) {
throw new RedisLettuceStateRepositoryException("Error while setting feature state", e);
}
}
private RedisHashCommands getCommands(final StatefulConnection connection) {
if (connection instanceof StatefulRedisConnection) {
return ((StatefulRedisConnection) connection).sync();
}
return ((StatefulRedisClusterConnection) connection).sync();
}
/**
* Builder for a {@link RedisLettuceStateRepository}.
*
* Can be used as follows:
*
*
* StateRepository stateRepository =
* new RedisLettuceStateRepository.Builder().
* lettucePool(ConnectionPoolSupport.createGenericObjectPool(
* () -> RedisClient.create(RedisURI.create("localhost", 6379)).connect(),
* new GenericObjectPoolConfig()))
* keyPrefix("toggles:").
* build();
*
*/
public static class Builder {
private GenericObjectPool> lettucePool;
private String keyPrefix = "togglz:";
/**
* Sets the Lettuce Pool.
*
* @param lettucePool the Lettuce Pool {@link GenericObjectPool}
*/
public Builder lettucePool(final GenericObjectPool> lettucePool) {
this.lettucePool = lettucePool;
return this;
}
/**
* Sets the Redis key prefix to be used when getting or setting the state of the features.
*
* @param keyPrefix the key prefix to be used in Redis
*/
public Builder keyPrefix(final String keyPrefix) {
this.keyPrefix = keyPrefix;
return this;
}
/**
* Creates a new {@link RedisLettuceStateRepository} using the current settings.
*/
public RedisLettuceStateRepository build() {
return new RedisLettuceStateRepository(this);
}
}
private static class RedisLettuceStateRepositoryException extends RuntimeException {
public RedisLettuceStateRepositoryException(String message, Throwable cause) {
super(message, cause);
}
public RedisLettuceStateRepositoryException(String message) {
super(message);
}
}
}