/* * Copyright (c) 2017 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.kv; import java.net.SocketAddress; import com.couchbase.client.core.endpoint.ServerFeatures; import com.couchbase.client.core.endpoint.ServerFeaturesEvent; import com.couchbase.client.core.logging.CouchbaseLogger; import com.couchbase.client.core.logging.CouchbaseLoggerFactory; import com.couchbase.client.deps.io.netty.handler.codec.memcache.binary.BinaryMemcacheRequest; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import com.couchbase.client.deps.io.netty.handler.codec.memcache.binary.DefaultBinaryMemcacheRequest; import com.couchbase.client.deps.io.netty.handler.codec.memcache.binary.FullBinaryMemcacheResponse; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOutboundHandler; import io.netty.channel.ChannelPromise; import io.netty.channel.SimpleChannelInboundHandler; /** * A Select bucket handler required for Spock cluster * * @author Subhashni Balakrishnan * @since 1.4.4 */ public class KeyValueSelectBucketHandler extends SimpleChannelInboundHandler<FullBinaryMemcacheResponse> implements ChannelOutboundHandler { /** * The logger used. */ private static final CouchbaseLogger LOGGER = CouchbaseLoggerFactory.getInstance(KeyValueSelectBucketHandler.class); /** * The bucket to select. */ private final String bucket; /** * The handler context. */ private ChannelHandlerContext ctx; /** * The connect promise issued by the connect process. */ private ChannelPromise originalPromise; /** * Boolean to be set based on HELLO response. */ private boolean selectBucketEnabled; /** * Select bucket opcode */ private static final byte SELECT_BUCKET_OPCODE = (byte)0x89; /** * Success response status */ private static final byte SUCCESS = (byte)0x00; /** * Access error response status */ private static final byte ACCESS_ERROR = (byte)0x24; /** * Creates a new {@link KeyValueSelectBucketHandler}. * * @param bucket the name of the user/bucket. */ public KeyValueSelectBucketHandler(String bucket) { this.bucket = bucket; } /** * Once the channel is marked as active, select bucket command is sent if the HELLO request has SELECT_BUCKET feature * enabled. * * @param ctx the handler context. * @throws Exception if something goes wrong during communicating to the server. */ @Override public void channelActive(final ChannelHandlerContext ctx) throws Exception { this.ctx = ctx; if (selectBucketEnabled) { byte[] key = bucket.getBytes(); short keyLength = (short) bucket.length(); BinaryMemcacheRequest request = new DefaultBinaryMemcacheRequest(key); request.setOpcode(SELECT_BUCKET_OPCODE); request.setKeyLength(keyLength); request.setTotalBodyLength(keyLength); this.ctx.writeAndFlush(request); } else { //remove the handler if the feature is not enabled originalPromise.setSuccess(); this.ctx.pipeline().remove(this); this.ctx.fireChannelActive(); } } /** * Handles incoming Select bucket responses. * * @param ctx the handler context. * @param msg the incoming message to investigate. * @throws Exception if something goes wrong during communicating to the server. */ @Override protected void channelRead0(ChannelHandlerContext ctx, FullBinaryMemcacheResponse msg) throws Exception { switch (msg.getStatus()) { case SUCCESS: originalPromise.setSuccess(); ctx.pipeline().remove(this); ctx.fireChannelActive(); break; case ACCESS_ERROR: originalPromise.setFailure(new AuthenticationException("Authentication failure on Select Bucket command")); break; default: originalPromise.setFailure(new AuthenticationException("Unhandled select bucket status: " + msg.getStatus())); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof ServerFeaturesEvent) { selectBucketEnabled = ((ServerFeaturesEvent) evt).supportedFeatures().contains(ServerFeatures.SELECT_BUCKET); } super.userEventTriggered(ctx, evt); } @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(); } }