package org.infinispan.commands.write;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.concurrent.CompletableFuture;
import org.infinispan.commands.CommandInvocationId;
import org.infinispan.commands.TopologyAffectedCommand;
import org.infinispan.commands.remote.BaseRpcCommand;
import org.infinispan.commons.marshall.MarshallUtil;
import org.infinispan.commons.util.EnumUtil;
import org.infinispan.context.Flag;
import org.infinispan.context.InvocationContext;
import org.infinispan.context.InvocationContextFactory;
import org.infinispan.context.impl.FlagBitSets;
import org.infinispan.interceptors.AsyncInterceptorChain;
import org.infinispan.metadata.Metadata;
import org.infinispan.notifications.cachelistener.CacheNotifier;
import org.infinispan.util.ByteString;
/**
* A command sent from the primary owner to the backup owners of a key with the new update.
* <p>
* This command is only visited by the backups owner and in a remote context. No locks are acquired since it is sent in
* FIFO order, set by {@code sequence}. It can represent a update or remove operation.
*
* @author Pedro Ruivo
* @since 9.0
*/
public class BackupWriteRpcCommand extends BaseRpcCommand implements TopologyAffectedCommand {
public static final byte COMMAND_ID = 61;
private static final Operation[] CACHED_VALUES = Operation.values();
private Operation operation;
private CommandInvocationId commandInvocationId;
private Object key;
private Object value;
private Metadata metadata;
private int topologyId;
private long flags;
private long sequence;
private InvocationContextFactory invocationContextFactory;
private AsyncInterceptorChain interceptorChain;
private CacheNotifier cacheNotifier;
//for org.infinispan.commands.CommandIdUniquenessTest
public BackupWriteRpcCommand() {
super(null);
}
public BackupWriteRpcCommand(ByteString cacheName) {
super(cacheName);
}
private static Operation valueOf(int index) {
return CACHED_VALUES[index];
}
public void setWrite(CommandInvocationId id, Object key, Object value, Metadata metadata, long flags,
int topologyId) {
this.operation = Operation.WRITE;
setCommonAttributes(id, key, flags, topologyId);
this.value = value;
this.metadata = metadata;
}
public void setRemove(CommandInvocationId id, Object key, long flags, int topologyId) {
this.operation = Operation.REMOVE;
setCommonAttributes(id, key, flags, topologyId);
}
public void setRemoveExpired(CommandInvocationId id, Object key, Object value, long flags, int topologyId) {
this.operation = Operation.REMOVE_EXPIRED;
setCommonAttributes(id, key, flags, topologyId);
this.value = value;
}
public void setReplace(CommandInvocationId id, Object key, Object value, Metadata metadata, long flags,
int topologyId) {
this.operation = Operation.REPLACE;
setCommonAttributes(id, key, flags, topologyId);
this.value = value;
this.metadata = metadata;
}
public void init(InvocationContextFactory invocationContextFactory, AsyncInterceptorChain interceptorChain,
CacheNotifier cacheNotifier) {
this.invocationContextFactory = invocationContextFactory;
this.interceptorChain = interceptorChain;
this.cacheNotifier = cacheNotifier;
}
@Override
public CompletableFuture<Object> invokeAsync() throws Throwable {
DataWriteCommand command;
switch (operation) {
case REMOVE:
command = new RemoveCommand(key, null, cacheNotifier, flags, commandInvocationId);
break;
case REMOVE_EXPIRED:
command = new RemoveExpiredCommand(key, value, null, cacheNotifier, commandInvocationId);
break;
case WRITE:
command = new PutKeyValueCommand(key, value, false, cacheNotifier, metadata, flags,
commandInvocationId);
break;
case REPLACE:
command = new ReplaceCommand(key, null, value, cacheNotifier, metadata, flags, commandInvocationId);
break;
default:
throw new IllegalStateException();
}
command.addFlags(FlagBitSets.SKIP_LOCKING);
command.setValueMatcher(ValueMatcher.MATCH_ALWAYS);
command.setTopologyId(topologyId);
InvocationContext invocationContext = invocationContextFactory
.createRemoteInvocationContextForCommand(command, getOrigin());
return interceptorChain.invokeAsync(invocationContext, command);
}
public Object getKey() {
return key;
}
@Override
public byte getCommandId() {
return COMMAND_ID;
}
public CommandInvocationId getCommandInvocationId() {
return commandInvocationId;
}
@Override
public boolean isReturnValueExpected() {
return false;
}
@Override
public boolean canBlock() {
return true;
}
@Override
public void writeTo(ObjectOutput output) throws IOException {
MarshallUtil.marshallEnum(operation, output);
CommandInvocationId.writeTo(output, commandInvocationId);
output.writeObject(key);
switch (operation) {
case WRITE:
case REPLACE:
output.writeObject(value);
output.writeObject(metadata);
break;
case REMOVE_EXPIRED:
output.writeObject(value);
break;
default:
}
output.writeLong(FlagBitSets.copyWithoutRemotableFlags(flags));
output.writeLong(sequence);
}
@Override
public void readFrom(ObjectInput input) throws IOException, ClassNotFoundException {
operation = MarshallUtil.unmarshallEnum(input, BackupWriteRpcCommand::valueOf);
commandInvocationId = CommandInvocationId.readFrom(input);
key = input.readObject();
switch (operation) {
case WRITE:
case REPLACE:
value = input.readObject();
metadata = (Metadata) input.readObject();
break;
case REMOVE_EXPIRED:
value = input.readObject();
break;
default:
}
this.flags = input.readLong();
this.sequence = input.readLong();
}
@Override
public String toString() {
return "BackupWriteRpcCommand{" +
"operation=" + operation +
", commandInvocationId=" + commandInvocationId +
", key=" + key +
", value=" + value +
", metadata=" + metadata +
", topologyId=" + topologyId +
", flags=" + EnumUtil.prettyPrintBitSet(flags, Flag.class) +
", sequence=" + sequence +
'}';
}
@Override
public int getTopologyId() {
return topologyId;
}
@Override
public void setTopologyId(int topologyId) {
this.topologyId = topologyId;
}
public long getSequence() {
return sequence;
}
public void setSequence(long sequence) {
this.sequence = sequence;
}
private void setCommonAttributes(CommandInvocationId commandInvocationId, Object key, long flags, int topologyId) {
this.commandInvocationId = commandInvocationId;
this.key = key;
this.flags = flags;
this.topologyId = topologyId;
}
private enum Operation {
WRITE,
REMOVE,
REMOVE_EXPIRED,
REPLACE
}
}