/* * Copyright (c) 2016 Couchbase, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.couchbase.client.core.endpoint.dcp; import com.couchbase.client.core.endpoint.kv.KeyValueStatus; import com.couchbase.client.core.env.CoreEnvironment; import com.couchbase.client.core.logging.CouchbaseLogger; import com.couchbase.client.core.logging.CouchbaseLoggerFactory; import com.couchbase.client.core.message.dcp.ConnectionType; import com.couchbase.client.core.message.dcp.ControlParameter; import com.couchbase.client.deps.io.netty.handler.codec.memcache.binary.BinaryMemcacheRequest; import com.couchbase.client.deps.io.netty.handler.codec.memcache.binary.DefaultBinaryMemcacheRequest; import com.couchbase.client.deps.io.netty.handler.codec.memcache.binary.DefaultFullBinaryMemcacheRequest; import com.couchbase.client.deps.io.netty.handler.codec.memcache.binary.FullBinaryMemcacheRequest; import com.couchbase.client.deps.io.netty.handler.codec.memcache.binary.FullBinaryMemcacheResponse; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOutboundHandler; import io.netty.channel.ChannelPromise; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.CharsetUtil; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import java.net.SocketAddress; /** * DCP connection handler makes sure that all channels have DCP connection initialized. * * @author Sergey Avseyev * @since 1.2.6 */ @Deprecated public class DCPConnectionHandler extends SimpleChannelInboundHandler<FullBinaryMemcacheResponse> implements ChannelOutboundHandler { /** * The logger used. */ private static final CouchbaseLogger LOGGER = CouchbaseLoggerFactory.getInstance(DCPConnectionHandler.class); private final String connectionName; private final CoreEnvironment env; /** * The connect promise issued by the connect process. */ private ChannelPromise originalPromise; /** * Creates a new {@link DCPConnectionHandler}. */ public DCPConnectionHandler(CoreEnvironment env) { this.connectionName = env.dcpConnectionName(); this.env = env; } /** * Once the channel is marked as active, the open DCP connection. * * @param ctx the handler context. * @throws Exception if something goes wrong. */ @Override public void channelActive(final ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(handleOpenConnectionRequest(ctx)); } /** * Dispatches incoming OPEN_CONNECTION responses and also initialize flow control. * * @param ctx the handler context. * @param msg the incoming message to investigate. * @throws Exception if something goes wrong during negotiation. */ @Override protected void channelRead0(ChannelHandlerContext ctx, FullBinaryMemcacheResponse msg) throws Exception { if (msg.getOpcode() == DCPHandler.OP_OPEN_CONNECTION) { if (msg.getStatus() == KeyValueStatus.SUCCESS.code()) { if (env.dcpConnectionBufferSize() > 0) { FullBinaryMemcacheRequest request = controlRequest(ctx, ControlParameter.CONNECTION_BUFFER_SIZE, env.dcpConnectionBufferSize()); ChannelFuture future = ctx.writeAndFlush(request); future.addListener(new GenericFutureListener<Future<Void>>() { @Override public void operationComplete(Future<Void> future) throws Exception { if (!future.isSuccess()) { LOGGER.warn("Error during setting CONNECTION_BUFFER_SIZE for DCP connection: {}.", future); } } }); } else { originalPromise.setSuccess(); ctx.pipeline().remove(this); ctx.fireChannelActive(); } } else { originalPromise.setFailure(new IllegalStateException("Bad status for DCP Open Connection: " + msg.getStatus())); } } else if (msg.getOpcode() == DCPHandler.OP_CONTROL) { if (msg.getStatus() == KeyValueStatus.SUCCESS.code()) { originalPromise.setSuccess(); ctx.pipeline().remove(this); ctx.fireChannelActive(); } else { originalPromise.setFailure(new IllegalStateException( "Bad status for setting CONNECTION_BUFFER_SIZE DCP Open Connection: " + msg.getStatus())); } } } /** * Creates a DCP Open Connection Request. * * @param ctx the channel handler context. * @return a converted {@link BinaryMemcacheRequest}. */ private BinaryMemcacheRequest handleOpenConnectionRequest(final ChannelHandlerContext ctx) { ByteBuf extras = ctx.alloc().buffer(8); extras.writeInt(0) // sequence number .writeInt(ConnectionType.CONSUMER.flags()); byte[] key = connectionName.getBytes(CharsetUtil.UTF_8); byte extrasLength = (byte) extras.readableBytes(); short keyLength = (short) key.length; BinaryMemcacheRequest request = new DefaultBinaryMemcacheRequest(key, extras); request.setOpcode(DCPHandler.OP_OPEN_CONNECTION); request.setKeyLength(keyLength); request.setExtrasLength(extrasLength); request.setTotalBodyLength(keyLength + extrasLength); return request; } private FullBinaryMemcacheRequest controlRequest(ChannelHandlerContext ctx, ControlParameter parameter, boolean value) { return controlRequest(ctx, parameter, Boolean.toString(value)); } private FullBinaryMemcacheRequest controlRequest(ChannelHandlerContext ctx, ControlParameter parameter, int value) { return controlRequest(ctx, parameter, Integer.toString(value)); } private FullBinaryMemcacheRequest controlRequest(ChannelHandlerContext ctx, ControlParameter parameter, String value) { byte[] key = parameter.value().getBytes(CharsetUtil.UTF_8); short keyLength = (short) key.length; byte[] val = value.getBytes(CharsetUtil.UTF_8); ByteBuf body = ctx.alloc().buffer(val.length); body.writeBytes(val); FullBinaryMemcacheRequest request = new DefaultFullBinaryMemcacheRequest(key, Unpooled.EMPTY_BUFFER, body); request.setOpcode(DCPHandler.OP_CONTROL); request.setKeyLength(keyLength); request.setTotalBodyLength(keyLength + body.readableBytes()); return request; } @Override public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception { ctx.bind(localAddress, promise); } @Override public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception { originalPromise = promise; ChannelPromise downPromise = ctx.newPromise(); downPromise.addListener(new GenericFutureListener<Future<Void>>() { @Override public void operationComplete(Future<Void> future) throws Exception { if (!future.isSuccess() && !originalPromise.isDone()) { originalPromise.setFailure(future.cause()); } } }); ctx.connect(remoteAddress, localAddress, downPromise); } @Override public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { ctx.disconnect(promise); } @Override public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { ctx.close(promise); } @Override public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { ctx.deregister(promise); } @Override public void read(ChannelHandlerContext ctx) throws Exception { ctx.read(); } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { ctx.write(msg, promise); } @Override public void flush(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } }