package com.lambdaworks.redis; import java.io.Closeable; import java.io.IOException; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Collection; import java.util.concurrent.TimeUnit; import com.lambdaworks.redis.api.StatefulConnection; import com.lambdaworks.redis.internal.LettuceAssert; import com.lambdaworks.redis.protocol.RedisCommand; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; /** * Abstract base for every redis connection. Provides basic connection functionality and tracks open resources. * * @param <K> Key type. * @param <V> Value type. * @author Mark Paluch * @since 3.0 */ public abstract class RedisChannelHandler<K, V> extends ChannelInboundHandlerAdapter implements Closeable { private static final InternalLogger logger = InternalLoggerFactory.getInstance(RedisChannelHandler.class); protected long timeout; protected TimeUnit unit; private CloseEvents closeEvents = new CloseEvents(); private final RedisChannelWriter<K, V> channelWriter; private volatile boolean closed; private volatile boolean active = true; private volatile ClientOptions clientOptions; // If DEBUG level logging has been enabled at startup. private final boolean debugEnabled; /** * @param writer the channel writer * @param timeout timeout value * @param unit unit of the timeout */ public RedisChannelHandler(RedisChannelWriter<K, V> writer, long timeout, TimeUnit unit) { this.channelWriter = writer; debugEnabled = logger.isDebugEnabled(); writer.setRedisChannelHandler(this); setTimeout(timeout, unit); } @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { closed = false; } /** * Set the command timeout for this connection. * * @param timeout Command timeout. * @param unit Unit of time for the timeout. */ public void setTimeout(long timeout, TimeUnit unit) { this.timeout = timeout; this.unit = unit; } /** * Close the connection. */ @Override public synchronized void close() { if(debugEnabled) { logger.debug("close()"); } if (closed) { logger.warn("Connection is already closed"); return; } if (!closed) { active = false; closed = true; channelWriter.close(); closeEvents.fireEventClosed(this); closeEvents = new CloseEvents(); } } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { channelRead(msg); } /** * Invoked on a channel read. * * @param msg channel message */ public void channelRead(Object msg) { } protected <T, C extends RedisCommand<K, V, T>> C dispatch(C cmd) { if(debugEnabled) { logger.debug("dispatching command {}", cmd); } return channelWriter.write(cmd); } /** * Register Closeable resources. Internal access only. * * @param registry registry of closeables * @param closeables closeables to register */ public void registerCloseables(final Collection<Closeable> registry, final Closeable... closeables) { registry.addAll(Arrays.asList(closeables)); addListener(resource -> { for (Closeable closeable : closeables) { if (closeable == RedisChannelHandler.this) { continue; } try { closeable.close(); } catch (IOException e) { if(debugEnabled) { logger.debug(e.toString(), e); } } } registry.removeAll(Arrays.asList(closeables)); }); } protected void addListener(CloseEvents.CloseListener listener) { closeEvents.addListener(listener); } /** * * @return true if the connection is closed (final state in the connection lifecyle). */ public boolean isClosed() { return closed; } /** * Notification when the connection becomes active (connected). */ public void activated() { active = true; closed = false; } /** * Notification when the connection becomes inactive (disconnected). */ public void deactivated() { active = false; } /** * * @return the channel writer */ public RedisChannelWriter<K, V> getChannelWriter() { return channelWriter; } /** * * @return true if the connection is active and not closed. */ public boolean isOpen() { return active; } public void reset() { channelWriter.reset(); } public ClientOptions getOptions() { return clientOptions; } public void setOptions(ClientOptions clientOptions) { LettuceAssert.notNull(clientOptions, "ClientOptions must not be null"); this.clientOptions = clientOptions; } public long getTimeout() { return timeout; } public TimeUnit getTimeoutUnit() { return unit; } protected <T> T syncHandler(Object asyncApi, Class<?>... interfaces) { FutureSyncInvocationHandler<K, V> h = new FutureSyncInvocationHandler<>((StatefulConnection) this, asyncApi, interfaces); return (T) Proxy.newProxyInstance(AbstractRedisClient.class.getClassLoader(), interfaces, h); } public void setAutoFlushCommands(boolean autoFlush) { getChannelWriter().setAutoFlushCommands(autoFlush); } public void flushCommands() { getChannelWriter().flushCommands(); } }