/*
* Copyright 2013 The Netty Project
*
* The Netty Project licenses this file to you 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.deps.io.netty.handler.codec.memcache.binary;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.TooLongFrameException;
import com.couchbase.client.deps.io.netty.handler.codec.memcache.AbstractMemcacheObjectAggregator;
import com.couchbase.client.deps.io.netty.handler.codec.memcache.FullMemcacheMessage;
import com.couchbase.client.deps.io.netty.handler.codec.memcache.LastMemcacheContent;
import com.couchbase.client.deps.io.netty.handler.codec.memcache.MemcacheContent;
import com.couchbase.client.deps.io.netty.handler.codec.memcache.MemcacheMessage;
import com.couchbase.client.deps.io.netty.handler.codec.memcache.MemcacheObject;
import java.util.List;
/**
* An object aggregator for the memcache binary protocol.
*
* It aggregates {@link BinaryMemcacheMessage}s and {@link MemcacheContent} into {@link FullBinaryMemcacheRequest}s
* or {@link FullBinaryMemcacheResponse}s.
*/
public class BinaryMemcacheObjectAggregator extends AbstractMemcacheObjectAggregator {
private boolean tooLongFrameFound;
public BinaryMemcacheObjectAggregator(int maxContentLength) {
super(maxContentLength);
}
@Override
protected void decode(ChannelHandlerContext ctx, MemcacheObject msg, List<Object> out) throws Exception {
FullMemcacheMessage currentMessage = this.currentMessage;
if (msg instanceof MemcacheMessage) {
tooLongFrameFound = false;
MemcacheMessage m = (MemcacheMessage) msg;
if (!m.getDecoderResult().isSuccess()) {
out.add(toFullMessage(m));
this.currentMessage = null;
return;
}
if (msg instanceof BinaryMemcacheRequest) {
this.currentMessage = toFullRequest((BinaryMemcacheRequest) msg,
Unpooled.compositeBuffer(getMaxCumulationBufferComponents()));
} else if (msg instanceof BinaryMemcacheResponse) {
this.currentMessage = toFullResponse((BinaryMemcacheResponse) msg,
Unpooled.compositeBuffer(getMaxCumulationBufferComponents()));
} else {
throw new Error();
}
} else if (msg instanceof MemcacheContent) {
if (tooLongFrameFound) {
if (msg instanceof LastMemcacheContent) {
this.currentMessage = null;
}
return;
}
MemcacheContent chunk = (MemcacheContent) msg;
CompositeByteBuf content = (CompositeByteBuf) currentMessage.content();
if (content.readableBytes() > getMaxContentLength() - chunk.content().readableBytes()) {
tooLongFrameFound = true;
currentMessage.release();
this.currentMessage = null;
throw new TooLongFrameException("Memcache content length exceeded " + getMaxContentLength()
+ " bytes.");
}
if (chunk.content().isReadable()) {
chunk.retain();
content.addComponent(chunk.content());
content.writerIndex(content.writerIndex() + chunk.content().readableBytes());
}
final boolean last;
if (!chunk.getDecoderResult().isSuccess()) {
currentMessage.setDecoderResult(
DecoderResult.failure(chunk.getDecoderResult().cause()));
last = true;
} else {
last = chunk instanceof LastMemcacheContent;
}
if (last) {
this.currentMessage = null;
out.add(currentMessage);
}
} else {
throw new Error();
}
}
/**
* Convert a invalid message into a full message.
*
* This method makes sure that upstream handlers always get a full message returned, even
* when invalid chunks are failing.
*
* @param msg the message to transform.
* @return a full message containing parts of the original message.
*/
private static FullMemcacheMessage toFullMessage(final MemcacheMessage msg) {
if (msg instanceof FullMemcacheMessage) {
return ((FullMemcacheMessage) msg).retain();
}
FullMemcacheMessage fullMsg;
if (msg instanceof BinaryMemcacheRequest) {
fullMsg = toFullRequest((BinaryMemcacheRequest) msg, Unpooled.EMPTY_BUFFER);
} else if (msg instanceof BinaryMemcacheResponse) {
fullMsg = toFullResponse((BinaryMemcacheResponse) msg, Unpooled.EMPTY_BUFFER);
} else {
throw new IllegalStateException();
}
return fullMsg;
}
private static FullBinaryMemcacheRequest toFullRequest(BinaryMemcacheRequest request, ByteBuf content) {
FullBinaryMemcacheRequest fullRequest = new DefaultFullBinaryMemcacheRequest(request.getKey(),
request.getExtras(), content);
fullRequest.setMagic(request.getMagic());
fullRequest.setOpcode(request.getOpcode());
fullRequest.setKeyLength(request.getKeyLength());
fullRequest.setExtrasLength(request.getExtrasLength());
fullRequest.setDataType(request.getDataType());
fullRequest.setTotalBodyLength(request.getTotalBodyLength());
fullRequest.setOpaque(request.getOpaque());
fullRequest.setCAS(request.getCAS());
fullRequest.setReserved(request.getReserved());
return fullRequest;
}
private static FullBinaryMemcacheResponse toFullResponse(BinaryMemcacheResponse response, ByteBuf content) {
FullBinaryMemcacheResponse fullResponse = new DefaultFullBinaryMemcacheResponse(response.getKey(),
response.getExtras(), content);
fullResponse.setMagic(response.getMagic());
fullResponse.setOpcode(response.getOpcode());
fullResponse.setKeyLength(response.getKeyLength());
fullResponse.setExtrasLength(response.getExtrasLength());
fullResponse.setDataType(response.getDataType());
fullResponse.setTotalBodyLength(response.getTotalBodyLength());
fullResponse.setOpaque(response.getOpaque());
fullResponse.setCAS(response.getCAS());
fullResponse.setStatus(response.getStatus());
return fullResponse;
}
}