net.spy.memcached.protocol.binary.OperationImpl Maven / Gradle / Ivy
The newest version!
/*
* arcus-java-client : Arcus Java client
* Copyright 2010-2014 NAVER Corp.
* Copyright 2014-2021 JaM2in Co., Ltd.
*
* 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 net.spy.memcached.protocol.binary;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.atomic.AtomicInteger;
import net.spy.memcached.CASResponse;
import net.spy.memcached.KeyUtil;
import net.spy.memcached.ops.CASOperationStatus;
import net.spy.memcached.ops.OperationCallback;
import net.spy.memcached.ops.OperationErrorType;
import net.spy.memcached.ops.OperationState;
import net.spy.memcached.ops.OperationStatus;
import net.spy.memcached.ops.StatusCode;
import net.spy.memcached.protocol.BaseOperationImpl;
/**
* Base class for binary operations.
*/
abstract class OperationImpl extends BaseOperationImpl {
protected static final byte REQ_MAGIC = (byte) 0x80;
protected static final byte RES_MAGIC = (byte) 0x81;
protected static final int MIN_RECV_PACKET = 24;
/**
* Error code for items that were not found.
*/
protected static final int ERR_NOT_FOUND = 1;
protected static final int ERR_EXISTS = 2;
protected static final int ERR_EINVAL = 4;
protected static final int ERR_NOT_STORED = 5;
protected static final OperationStatus NOT_FOUND_STATUS =
new CASOperationStatus(false, "Not Found",
CASResponse.NOT_FOUND, StatusCode.ERR_NOT_FOUND);
protected static final OperationStatus EXISTS_STATUS =
new CASOperationStatus(false, "Object exists",
CASResponse.EXISTS, StatusCode.ERR_EXISTS);
protected static final OperationStatus NOT_STORED_STATUS =
new CASOperationStatus(false, "Not Stored",
CASResponse.NOT_FOUND, StatusCode.ERR_NOT_STORED);
protected static final byte[] EMPTY_BYTES = new byte[0];
protected static final OperationStatus STATUS_OK =
new CASOperationStatus(true, "OK",
CASResponse.OK, StatusCode.SUCCESS);
private static final AtomicInteger seqNumber = new AtomicInteger(0);
// request header fields
private final int cmd;
protected final int opaque;
private final byte[] header = new byte[MIN_RECV_PACKET];
private int headerOffset = 0;
private byte[] payload = null;
// Response header fields
protected int keyLen;
protected int responseCmd;
protected int errorCode;
protected int responseOpaque;
protected long responseCas;
private int payloadOffset = 0;
/**
* Construct with opaque.
*
* @param o the opaque value.
* @param cb
*/
protected OperationImpl(int c, int o, OperationCallback cb) {
super();
cmd = c;
opaque = o;
setCallback(cb);
}
protected void resetInput() {
payload = null;
payloadOffset = 0;
headerOffset = 0;
}
// Base response packet format:
// 0 1 2 3 4 5 6 7 8 9 10 11
// # magic, opcode, keylen, extralen, datatype, status, bodylen,
// 12,3,4,5 16
// opaque, cas
// RES_PKT_FMT=">BBHBBHIIQ"
@Override
public void readFromBuffer(ByteBuffer b) throws IOException {
// First process headers if we haven't completed them yet
if (headerOffset < MIN_RECV_PACKET) {
int toRead = MIN_RECV_PACKET - headerOffset;
int available = b.remaining();
toRead = Math.min(toRead, available);
getLogger().debug("Reading %d header bytes", toRead);
b.get(header, headerOffset, toRead);
headerOffset += toRead;
// We've completed reading the header. Prepare body read.
if (headerOffset == MIN_RECV_PACKET) {
int magic = header[0];
assert magic == RES_MAGIC : "Invalid magic: " + magic;
responseCmd = header[1];
assert cmd == -1 || responseCmd == cmd
: "Unexpected response command value";
keyLen = decodeShort(header, 2);
// TODO: Examine extralen and datatype
errorCode = decodeShort(header, 6);
int bytesToRead = decodeInt(header, 8);
payload = new byte[bytesToRead];
responseOpaque = decodeInt(header, 12);
responseCas = decodeLong(header, 16);
assert opaqueIsValid() : "Opaque is not valid";
}
}
// Now process the payload if we can.
if (headerOffset >= MIN_RECV_PACKET && payload == null) {
finishedPayload(EMPTY_BYTES);
} else if (payload != null) {
int toRead = payload.length - payloadOffset;
int available = b.remaining();
toRead = Math.min(toRead, available);
getLogger().debug("Reading %d payload bytes", toRead);
b.get(payload, payloadOffset, toRead);
payloadOffset += toRead;
// Have we read it all?
if (payloadOffset == payload.length) {
finishedPayload(payload);
}
} else {
// Haven't read enough to make up a payload. Must read more.
getLogger().debug("Only read %d of the %d needed to fill a header",
headerOffset, MIN_RECV_PACKET);
}
}
protected void finishedPayload(byte[] pl) throws IOException {
if (errorCode != 0) {
OperationStatus status = getStatusForErrorCode(errorCode, pl);
if (status == null) {
handleError(OperationErrorType.SERVER, new String(pl));
} else {
getCallback().receivedStatus(status);
transitionState(OperationState.COMPLETE);
}
} else {
decodePayload(pl);
transitionState(OperationState.COMPLETE);
}
}
/**
* Get the OperationStatus object for the given error code.
*
* @param errCode the error code
* @return the status to return, or null if this is an exceptional case
*/
protected OperationStatus getStatusForErrorCode(int errCode, byte[] errPl) {
return null;
}
/**
* Decode the given payload for this command.
*
* @param pl the payload.
*/
protected void decodePayload(byte[] pl) {
assert pl.length == 0 : "Payload has bytes, but decode isn't overridden";
getCallback().receivedStatus(STATUS_OK);
}
/**
* Validate an opaque value from the header.
* This may be overridden from a subclass where the opaque isn't expected
* to always be the same as the request opaque.
*/
protected boolean opaqueIsValid() {
if (responseOpaque != opaque) {
getLogger().warn("Expected opaque: %d, got opaque: %d\n",
responseOpaque, opaque);
}
return responseOpaque == opaque;
}
static int decodeShort(byte[] data, int i) {
return (data[i] & 0xff) << 8
| (data[i + 1] & 0xff);
}
static int decodeInt(byte[] data, int i) {
return (data[i] & 0xff) << 24
| (data[i + 1] & 0xff) << 16
| (data[i + 2] & 0xff) << 8
| (data[i + 3] & 0xff);
}
static long decodeUnsignedInt(byte[] data, int i) {
return ((long) (data[i] & 0xff) << 24)
| ((data[i + 1] & 0xff) << 16)
| ((data[i + 2] & 0xff) << 8)
| (data[i + 3] & 0xff);
}
static long decodeLong(byte[] data, int i) {
return (data[i] & 0xff) << 56
| (data[i + 1] & 0xff) << 48
| (data[i + 2] & 0xff) << 40
| (data[i + 3] & 0xff) << 32
| (data[i + 4] & 0xff) << 24
| (data[i + 5] & 0xff) << 16
| (data[i + 6] & 0xff) << 8
| (data[i + 7] & 0xff);
}
/**
* Prepare a send buffer.
*
* @param key the key (for keyed ops)
* @param cas the cas value
* @param val the data payload
* @param extraHeaders any additional headers that need to be sent
*/
protected void prepareBuffer(String key, long cas, byte[] val,
Object... extraHeaders) {
int extraLen = 0;
for (Object o : extraHeaders) {
if (o instanceof Integer) {
extraLen += 4;
} else if (o instanceof byte[]) {
extraLen += ((byte[]) o).length;
} else if (o instanceof Long) {
extraLen += 8;
} else {
assert false : "Unhandled extra header type: " + o.getClass();
}
}
final byte[] keyBytes = KeyUtil.getKeyBytes(key);
int bufSize = MIN_RECV_PACKET + keyBytes.length + val.length;
// # magic, opcode, keylen, extralen, datatype, [reserved],
// bodylen, opaque, cas
// REQ_PKT_FMT=">BBHBBxxIIQ"
// set up the initial header stuff
ByteBuffer bb = ByteBuffer.allocate(bufSize + extraLen);
assert bb.order() == ByteOrder.BIG_ENDIAN;
bb.put(REQ_MAGIC);
bb.put((byte) cmd);
bb.putShort((short) keyBytes.length);
bb.put((byte) extraLen);
bb.put((byte) 0); // data type
bb.putShort((short) 0); // reserved
bb.putInt(keyBytes.length + val.length + extraLen);
bb.putInt(opaque);
bb.putLong(cas);
// Add the extra headers.
for (Object o : extraHeaders) {
if (o instanceof Integer) {
bb.putInt((Integer) o);
} else if (o instanceof byte[]) {
bb.put((byte[]) o);
} else if (o instanceof Long) {
bb.putLong((Long) o);
} else {
assert false : "Unhandled extra header type: " + o.getClass();
}
}
// Add the normal stuff
bb.put(keyBytes);
bb.put(val);
((Buffer) bb).flip();
setBuffer(bb);
}
/**
* Generate an opaque ID.
*/
static int generateOpaque() {
int rv = seqNumber.incrementAndGet();
while (rv < 0) {
seqNumber.compareAndSet(rv, 0);
rv = seqNumber.incrementAndGet();
}
return rv;
}
}