package com.lambdaworks.redis;
import static com.lambdaworks.redis.ConnectionEventTrigger.local;
import static com.lambdaworks.redis.ConnectionEventTrigger.remote;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import com.lambdaworks.redis.codec.Utf8StringCodec;
import com.lambdaworks.redis.event.EventBus;
import com.lambdaworks.redis.event.connection.ConnectedEvent;
import com.lambdaworks.redis.event.connection.ConnectionActivatedEvent;
import com.lambdaworks.redis.event.connection.DisconnectedEvent;
import com.lambdaworks.redis.protocol.AsyncCommand;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
/**
* @author Mark Paluch
*/
class PlainChannelInitializer extends io.netty.channel.ChannelInitializer<Channel> implements RedisChannelInitializer {
static final RedisCommandBuilder<String, String> INITIALIZING_CMD_BUILDER = new RedisCommandBuilder<>(
new Utf8StringCodec());
protected final char[] password;
private boolean pingBeforeActivate;
private CompletableFuture<Boolean> initializedFuture = new CompletableFuture<>();
private final List<ChannelHandler> handlers;
private final EventBus eventBus;
public PlainChannelInitializer(boolean pingBeforeActivateConnection, char[] password, List<ChannelHandler> handlers,
EventBus eventBus) {
this.pingBeforeActivate = pingBeforeActivateConnection;
this.password = password;
this.handlers = handlers;
this.eventBus = eventBus;
}
@Override
protected void initChannel(Channel channel) throws Exception {
if (channel.pipeline().get("channelActivator") == null) {
channel.pipeline().addLast("channelActivator", new RedisChannelInitializerImpl() {
private AsyncCommand<?, ?, ?> pingCommand;
@Override
public CompletableFuture<Boolean> channelInitialized() {
return initializedFuture;
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
eventBus.publish(new DisconnectedEvent(local(ctx), remote(ctx)));
initializedFuture = new CompletableFuture<>();
pingCommand = null;
super.channelInactive(ctx);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof ConnectionEvents.Close) {
if (ctx.channel().isOpen()) {
ctx.channel().close();
}
}
if (evt instanceof ConnectionEvents.Activated) {
if (!initializedFuture.isDone()) {
initializedFuture.complete(true);
eventBus.publish(new ConnectionActivatedEvent(local(ctx), remote(ctx)));
}
}
super.userEventTriggered(ctx, evt);
}
@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
eventBus.publish(new ConnectedEvent(local(ctx), remote(ctx)));
if (pingBeforeActivate) {
if (password != null && password.length != 0) {
pingCommand = new AsyncCommand<>(INITIALIZING_CMD_BUILDER.auth(new String(password)));
} else {
pingCommand = new AsyncCommand<>(INITIALIZING_CMD_BUILDER.ping());
}
pingBeforeActivate(pingCommand, initializedFuture, ctx, handlers);
} else {
super.channelActive(ctx);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (!initializedFuture.isDone()) {
initializedFuture.completeExceptionally(cause);
}
super.exceptionCaught(ctx, cause);
}
});
}
for (ChannelHandler handler : handlers) {
removeIfExists(channel.pipeline(), handler.getClass());
channel.pipeline().addLast(handler);
}
}
static void pingBeforeActivate(final AsyncCommand<?, ?, ?> cmd, final CompletableFuture<Boolean> initializedFuture,
final ChannelHandlerContext ctx, final List<ChannelHandler> handlers) throws Exception {
cmd.handle((o, throwable) -> {
if (throwable == null) {
initializedFuture.complete(true);
ctx.fireChannelActive();
} else {
initializedFuture.completeExceptionally(throwable);
}
return null;
});
ctx.channel().writeAndFlush(cmd);
}
static void removeIfExists(ChannelPipeline pipeline, Class<? extends ChannelHandler> handlerClass) {
ChannelHandler channelHandler = pipeline.get(handlerClass);
if (channelHandler != null) {
pipeline.remove(channelHandler);
}
}
@Override
public CompletableFuture<Boolean> channelInitialized() {
return initializedFuture;
}
}