/*
* 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.message.kv.subdoc.multi;
import com.couchbase.client.core.annotations.InterfaceAudience;
import com.couchbase.client.core.annotations.InterfaceStability;
import com.couchbase.client.core.endpoint.kv.KeyValueAuthHandler;
import com.couchbase.client.core.endpoint.kv.KeyValueHandler;
import com.couchbase.client.core.message.kv.AbstractKeyValueRequest;
import com.couchbase.client.core.message.kv.subdoc.BinarySubdocMultiMutationRequest;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import java.util.Arrays;
import java.util.List;
/**
* Concrete implementation of a {@link BinarySubdocMultiMutationRequest}.
*
* @author Simon Baslé
* @since 1.2
*/
@InterfaceStability.Experimental
@InterfaceAudience.Public
public class SubMultiMutationRequest extends AbstractKeyValueRequest implements BinarySubdocMultiMutationRequest {
private final List<MutationCommand> commands;
private final ByteBuf encoded;
private final int expiration;
private final long cas;
/**
* Create a new {@link SubMultiMutationRequest}.
*
* @param key the key of the document to mutate into.
* @param bucket the bucket of the document.
* @param expiration the expiration (or TTL) to apply to the whole document additionally to the mutations.
* @param cas the CAS value to check for when applying the whole set of mutations.
* @param commands the set of internal mutations to apply to the document.
*/
public SubMultiMutationRequest(String key, String bucket, int expiration, long cas, List<MutationCommand> commands) {
super(key, bucket);
if (commands == null || commands.isEmpty()) {
throw new IllegalArgumentException("At least one mutation command is necessary");
}
this.commands = commands;
this.encoded = encode(commands);
this.expiration = expiration;
this.cas = cas;
}
/**
* Create a new {@link SubMultiMutationRequest}.
*
* @param key the key of the document to mutate into.
* @param bucket the bucket of the document.
* @param expiration the expiration (or TTL) to apply to the whole document additionally to the mutations.
* @param cas the CAS value to check for when applying the whole set of mutations.
* @param commands the set of internal mutations to apply to the document.
*/
public SubMultiMutationRequest(String key, String bucket, int expiration, long cas, MutationCommand... commands) {
this(key, bucket, expiration, cas, commands == null ? null : Arrays.asList(commands));
}
/**
* Create a new {@link SubMultiMutationRequest}.
*
* @param key the key of the document to mutate into.
* @param bucket the bucket of the document.
* @param commands the set of internal mutations to apply to the document.
*/
public SubMultiMutationRequest(String key, String bucket, List<MutationCommand> commands) {
this(key, bucket, 0, 0L, commands);
}
/**
* Create a new {@link SubMultiMutationRequest}.
*
* @param key the key of the document to mutate into.
* @param bucket the bucket of the document.
* @param commands the set of internal mutations to apply to the document.
*/
public SubMultiMutationRequest(String key, String bucket, MutationCommand... commands) {
this(key, bucket, 0, 0L, commands);
}
private static ByteBuf encode(List<MutationCommand> commands) {
//FIXME a way of using the pooled allocator?
CompositeByteBuf compositeBuf = Unpooled.compositeBuffer(commands.size());
for (MutationCommand command : commands) {
byte[] pathBytes = command.path().getBytes(CharsetUtil.UTF_8);
short pathLength = (short) pathBytes.length;
ByteBuf commandBuf = Unpooled.buffer(4 + pathLength + command.fragment().readableBytes());
commandBuf.writeByte(command.opCode());
byte flags = 0;
if (command.createIntermediaryPath()) {
flags |= KeyValueHandler.SUBDOC_BITMASK_MKDIR_P;
}
if (command.xattr()) {
flags |= KeyValueHandler.SUBDOC_FLAG_XATTR_PATH;
}
commandBuf.writeByte(flags);
commandBuf.writeShort(pathLength);
commandBuf.writeInt(command.fragment().readableBytes());
commandBuf.writeBytes(pathBytes);
//copy the fragment but don't move indexes (in case it is retained and reused)
commandBuf.writeBytes(command.fragment(), command.fragment().readerIndex(), command.fragment().readableBytes());
//eagerly release the fragment once it's been copied
command.fragment().release();
//add the command to the composite buffer
compositeBuf.addComponent(commandBuf);
compositeBuf.writerIndex(compositeBuf.writerIndex() + commandBuf.readableBytes());
}
return compositeBuf;
}
/**
* @return the expiration (or TTL) to apply to the document along the mutations, 0 for no TTL.
*/
@Override
public int expiration() {
return this.expiration;
}
/**
* @return the CAS to use for the mutations (if needed) or 0L to ignore
*/
@Override
public long cas() {
return this.cas;
}
/**
* @return a list of the {@link MutationCommand} describing the multiple operations to apply.
*/
@Override
public List<MutationCommand> commands() {
return this.commands;
}
@Override
public ByteBuf content() {
return this.encoded;
}
}