package org.couchbase.mock.memcached.client;
import org.couchbase.mock.memcached.protocol.BinaryHelloCommand;
import org.couchbase.mock.memcached.protocol.BinarySubdocCommand;
import org.couchbase.mock.memcached.protocol.CommandCode;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Created by mnunberg on 1/15/14.
*/
public class CommandBuilder {
static int opaqueCounter = 0;
private byte[] key = {};
private byte[] value = {};
private byte[] extras = {};
private long cas = 0;
private int opaque = opaqueCounter++;
private short vbucket = 0;
private final CommandCode command;
public static class MultiLookupSpec {
final String path;
final CommandCode op;
public MultiLookupSpec(CommandCode op, String path) {
this.path = path;
this.op = op;
}
public static MultiLookupSpec exists(String path) {
return new MultiLookupSpec(CommandCode.SUBDOC_EXISTS, path);
}
public static MultiLookupSpec get(String path) {
return new MultiLookupSpec(CommandCode.SUBDOC_GET, path);
}
}
public static class MultiMutationSpec {
final CommandCode op;
final byte flags;
final String path;
final String value;
public MultiMutationSpec(CommandCode op, String path, String value, int flags) {
this.op = op;
this.path = path;
this.value = value;
this.flags = (byte)flags;
}
public MultiMutationSpec(CommandCode op, String path, String value, boolean create) {
this(op, path, value, create ? BinarySubdocCommand.PATHFLAG_MKDIR_P : 0x0);
}
public MultiMutationSpec(CommandCode op, String path, String value) {
this(op, path, value, false);
}
public MultiMutationSpec(CommandCode op, String path) {
this(op, path, null, (byte)0x0);
}
}
public CommandBuilder(CommandCode command) {
this.command = command;
}
public CommandBuilder key(String key, short vbucket) {
this.key = key.getBytes();
this.vbucket = vbucket;
return this;
}
public CommandBuilder vBucket(short vbucket) {
this.vbucket = vbucket;
return this;
}
public CommandBuilder value(String value) {
this.value = value.getBytes();
return this;
}
public CommandBuilder value(byte[] value) {
this.value = value;
return this;
}
public CommandBuilder cas(long cas) {
this.cas = cas;
return this;
}
public CommandBuilder extras(byte[] extras) {
this.extras = extras;
return this;
}
public CommandBuilder value(byte[] value, int flags) {
ByteBuffer bb = ByteBuffer.allocate(4);
bb.putInt(flags);
bb.rewind();
extras(bb.array());
value(value);
return this;
}
public CommandBuilder subdoc(byte[] sdPath, byte[] sdValue, int sdFlags, int docflags, int expiry) {
int numExtras = 3;
if (expiry != 0) {
numExtras += 4;
}
if (docflags != 0) {
numExtras ++;
}
ByteBuffer extrasBuf = ByteBuffer.allocate(numExtras);
extrasBuf.putShort((short)sdPath.length);
extrasBuf.put((byte)sdFlags);
if (expiry != 0) {
extrasBuf.putInt(expiry);
}
if (docflags != 0) {
extrasBuf.put((byte)docflags);
}
if (sdValue == null) {
sdValue = new byte[0];
}
ByteBuffer valuePack = ByteBuffer.allocate(sdPath.length + sdValue.length);
valuePack.put(sdPath);
valuePack.put(sdValue);
extras(extrasBuf.array());
value(valuePack.array());
return this;
}
public CommandBuilder subdoc(byte[] sdPath) {
return subdoc(sdPath, null, 0, 0, 0);
}
public CommandBuilder subdoc(byte[] sdPath, byte[] sdValue) {
return subdoc(sdPath, sdValue, 0, 0, 0);
}
public CommandBuilder subdoc(String sdPath, String sdValue) {
return subdoc(sdPath.getBytes(), sdValue.getBytes());
}
public CommandBuilder subdoc(byte[] sdPath, byte[] sdValue, int sdFlags) {
return subdoc(sdPath, sdValue, sdFlags, 0,0);
}
public CommandBuilder subdoc(String sdPath, String sdValue, int sdFlags) {
return subdoc(sdPath.getBytes(), sdValue.getBytes(), sdFlags);
}
private static byte[] subdocMultiLookupPayload(MultiLookupSpec[] specs) {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
for (MultiLookupSpec spec : specs) {
ByteBuffer bb = ByteBuffer.allocate(1 + 1 + 2 + spec.path.length());
bb.put((byte)spec.op.cc());
bb.put((byte)0x00);
bb.putShort((short)spec.path.getBytes().length);
bb.put(spec.path.getBytes());
try {
bao.write(bb.array());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
return bao.toByteArray();
}
public CommandBuilder subdocMultiLookup(int expiry, MultiLookupSpec... specs) {
if (expiry != -1) {
ByteBuffer bb = ByteBuffer.allocate(4);
bb.putInt(expiry);
extras(bb.array());
}
value(subdocMultiLookupPayload(specs));
return this;
}
public CommandBuilder subdocMultiLookup(MultiLookupSpec... specs) {
return subdocMultiLookup(-1, specs);
}
private static byte[] subdocMultiMutationPayload(MultiMutationSpec[] specs) {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
for (MultiMutationSpec spec : specs) {
String value = spec.value;
if (value == null) {
value = "";
}
ByteBuffer bb = ByteBuffer.allocate(1 + 1 + 2 + 4 +
spec.path.length() + value.length());
bb.put((byte)spec.op.cc());
bb.put(spec.flags);
bb.putShort((short)spec.path.getBytes().length);
bb.putInt(value.length());
bb.put(spec.path.getBytes());
bb.put(value.getBytes());
try {
bao.write(bb.array());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
return bao.toByteArray();
}
public CommandBuilder subdocMultiMutation(int expiry, int docflags, MultiMutationSpec... specs) {
int allocLen = 0;
if (expiry != -1) {
allocLen = 4;
}
if (docflags != 0) {
allocLen++;
}
if (allocLen > 0) {
ByteBuffer bb = ByteBuffer.allocate(allocLen);
if (expiry != -1) {
bb.putInt(expiry);
}
if (docflags != 0) {
bb.put((byte)docflags);
}
extras(bb.array());
}
value(subdocMultiMutationPayload(specs));
return this;
}
public CommandBuilder subdocMultiMutation(MultiMutationSpec... specs) {
return subdocMultiMutation(-1, 0, specs);
}
public static byte[] buildHello(String name, BinaryHelloCommand.Feature... features) {
CommandBuilder cBuilder = new CommandBuilder(CommandCode.HELLO);
cBuilder.key(name, (short)0);
ByteBuffer bb = ByteBuffer.allocate(features.length * 2);
for (BinaryHelloCommand.Feature f : features) {
bb.putShort((short)f.getValue());
}
bb.rewind();
cBuilder.value(bb.array());
return cBuilder.build();
}
public static byte[] buildStore(String key, short vbucket, String value) {
CommandBuilder cBuilder = new CommandBuilder(CommandCode.SET);
cBuilder.key(key, vbucket);
cBuilder.value(value.getBytes(), 0);
return cBuilder.build();
}
public static byte[] buildPlainAuth(String username, String password) {
byte[] bUsername = username.getBytes();
byte[] bPassword = password.getBytes();
ByteBuffer buf = ByteBuffer.allocate(bUsername.length + bPassword.length + 2);
buf.put((byte)0x00);
buf.put(bUsername);
buf.put((byte)0x00);
buf.put(bPassword);
return new CommandBuilder(CommandCode.SASL_AUTH)
.key("PLAIN", (short)0)
.value(buf.array())
.build();
}
public static byte[] buildSubdocGet(String key, short vbucket, String path) {
return new CommandBuilder(CommandCode.SUBDOC_GET)
.key(key, vbucket)
.subdoc(path.getBytes())
.build();
}
public byte[] build() {
int totalLen = 24 + key.length + value.length + extras.length;
byte[] ret = new byte[totalLen];
ByteBuffer buffer = ByteBuffer.wrap(ret);
// Magic: PROTOCOL_BINARY_REQ
buffer.put((byte) 0x80);
// Opcode
buffer.put((byte) command.cc());
// Key Length
buffer.putShort((short) key.length);
buffer.put((byte)extras.length);
// Datatype. Ignored
buffer.put((byte) 0);
// Vbucket
buffer.putShort(vbucket);
// Body length
buffer.putInt(totalLen - 24);
// Opaque
buffer.putInt(opaque);
// CAS
buffer.putLong(cas);
// Stuff the body..
buffer.put(extras);
buffer.put(key);
buffer.put(value);
return ret;
}
}