package com.lambdaworks.redis; import static com.lambdaworks.redis.protocol.CommandType.AUTH; import static com.lambdaworks.redis.protocol.CommandType.DISCARD; import static com.lambdaworks.redis.protocol.CommandType.EXEC; import static com.lambdaworks.redis.protocol.CommandType.MULTI; import static com.lambdaworks.redis.protocol.CommandType.READONLY; import static com.lambdaworks.redis.protocol.CommandType.READWRITE; import static com.lambdaworks.redis.protocol.CommandType.SELECT; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import com.lambdaworks.redis.api.StatefulRedisConnection; import com.lambdaworks.redis.api.async.RedisAsyncCommands; import com.lambdaworks.redis.api.rx.RedisReactiveCommands; import com.lambdaworks.redis.api.sync.RedisCommands; import com.lambdaworks.redis.cluster.api.sync.RedisClusterCommands; import com.lambdaworks.redis.codec.RedisCodec; import com.lambdaworks.redis.output.MultiOutput; import com.lambdaworks.redis.protocol.CompleteableCommand; import com.lambdaworks.redis.protocol.ConnectionWatchdog; import com.lambdaworks.redis.protocol.RedisCommand; import com.lambdaworks.redis.protocol.TransactionalCommand; import io.netty.channel.ChannelHandler; /** * A thread-safe connection to a Redis server. Multiple threads may share one {@link StatefulRedisConnectionImpl} * * A {@link ConnectionWatchdog} monitors each connection and reconnects automatically until {@link #close} is called. All * pending commands will be (re)sent after successful reconnection. * * @param <K> Key type. * @param <V> Value type. * @author Mark Paluch */ @ChannelHandler.Sharable public class StatefulRedisConnectionImpl<K, V> extends RedisChannelHandler<K, V> implements StatefulRedisConnection<K, V> { protected final RedisCodec<K, V> codec; protected final RedisCommands<K, V> sync; protected final RedisAsyncCommandsImpl<K, V> async; protected final RedisReactiveCommandsImpl<K, V> reactive; protected MultiOutput<K, V> multi; private char[] password; private int db; private boolean readOnly; /** * Initialize a new connection. * * @param writer the channel writer * @param codec Codec used to encode/decode keys and values. * @param timeout Maximum time to wait for a response. * @param unit Unit of time for the timeout. */ public StatefulRedisConnectionImpl(RedisChannelWriter<K, V> writer, RedisCodec<K, V> codec, long timeout, TimeUnit unit) { super(writer, timeout, unit); this.codec = codec; this.async = newRedisAsyncCommandsImpl(); this.sync = newRedisSyncCommandsImpl(); this.reactive = newRedisReactiveCommandsImpl(); } @Override public RedisAsyncCommands<K, V> async() { return async; } /** * Create a new instance of {@link RedisCommands}. Can be overriden to extend. * * @return a new instance */ protected RedisCommands<K, V> newRedisSyncCommandsImpl() { return syncHandler(async(), RedisCommands.class, RedisClusterCommands.class); } /** * Create a new instance of {@link RedisAsyncCommandsImpl}. Can be overriden to extend. * * @return a new instance */ protected RedisAsyncCommandsImpl<K, V> newRedisAsyncCommandsImpl() { return new RedisAsyncCommandsImpl<>(this, codec); } @Override public RedisReactiveCommands<K, V> reactive() { return reactive; } /** * Create a new instance of {@link RedisReactiveCommandsImpl}. Can be overriden to extend. * * @return a new instance */ protected RedisReactiveCommandsImpl<K, V> newRedisReactiveCommandsImpl() { return new RedisReactiveCommandsImpl<>(this, codec); } @Override public RedisCommands<K, V> sync() { return sync; } @Override public boolean isMulti() { return multi != null; } @Override public void activated() { super.activated(); // do not block in here, since the channel flow will be interrupted. if (password != null) { async.authAsync(new String(password)); } if (db != 0) { async.selectAsync(db); } if (readOnly) { async.readOnly(); } } @Override public <T, C extends RedisCommand<K, V, T>> C dispatch(C cmd) { RedisCommand<K, V, T> local = cmd; if (local.getType().name().equals(AUTH.name())) { local = attachOnComplete(local, status -> { if ("OK".equals(status) && cmd.getArgs().getFirstString() != null) { this.password = cmd.getArgs().getFirstString().toCharArray(); } }); } if (local.getType().name().equals(SELECT.name())) { local = attachOnComplete(local, status -> { if ("OK".equals(status) && cmd.getArgs().getFirstInteger() != null) { this.db = cmd.getArgs().getFirstInteger().intValue(); } }); } if (local.getType().name().equals(READONLY.name())) { local = attachOnComplete(local, status -> { if ("OK".equals(status)) { this.readOnly = true; } }); } if (local.getType().name().equals(READWRITE.name())) { local = attachOnComplete(local, status -> { if ("OK".equals(status)) { this.readOnly = false; } }); } if (local.getType().name().equals(DISCARD.name())) { if (multi != null) { multi.cancel(); multi = null; } } if (local.getType().name().equals(EXEC.name())) { MultiOutput<K, V> multiOutput = this.multi; this.multi = null; if (multiOutput == null) { multiOutput = new MultiOutput<>(codec); } local.setOutput((MultiOutput) multiOutput); } if (multi != null) { local = new TransactionalCommand<>(local); multi.add(local); } try { return (C) super.dispatch(local); } finally { if (cmd.getType().name().equals(MULTI.name())) { multi = (multi == null ? new MultiOutput<>(codec) : multi); } } } private <T> RedisCommand<K, V, T> attachOnComplete(RedisCommand<K, V, T> command, Consumer<T> consumer) { if (command instanceof CompleteableCommand) { CompleteableCommand<T> completeable = (CompleteableCommand<T>) command; completeable.onComplete(consumer); } return command; } }