package org.infinispan.commands.write; import static org.infinispan.commons.util.Util.toStr; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import org.infinispan.commands.CommandInvocationId; import org.infinispan.commands.MetadataAwareCommand; import org.infinispan.commands.Visitor; import org.infinispan.commons.marshall.MarshallUtil; import org.infinispan.container.entries.MVCCEntry; import org.infinispan.context.InvocationContext; import org.infinispan.context.impl.FlagBitSets; import org.infinispan.metadata.Metadata; import org.infinispan.metadata.Metadatas; import org.infinispan.notifications.cachelistener.CacheNotifier; /** * @author Mircea.Markus@jboss.com * @author Galder ZamarreƱo * @since 4.0 */ public class ReplaceCommand extends AbstractDataWriteCommand implements MetadataAwareCommand { public static final byte COMMAND_ID = 11; private Object oldValue; private Object newValue; private Metadata metadata; private CacheNotifier<Object, Object> notifier; private boolean successful = true; private ValueMatcher valueMatcher; public ReplaceCommand() { } public ReplaceCommand(Object key, Object oldValue, Object newValue, CacheNotifier notifier, Metadata metadata, long flagsBitSet, CommandInvocationId commandInvocationId) { super(key, flagsBitSet, commandInvocationId); this.oldValue = oldValue; this.newValue = newValue; //noinspection unchecked this.notifier = notifier; this.metadata = metadata; this.valueMatcher = oldValue != null ? ValueMatcher.MATCH_EXPECTED : ValueMatcher.MATCH_NON_NULL; } public void init(CacheNotifier notifier) { //noinspection unchecked this.notifier = notifier; } @Override public Object acceptVisitor(InvocationContext ctx, Visitor visitor) throws Throwable { return visitor.visitReplaceCommand(ctx, this); } @Override public LoadType loadType() { return LoadType.PRIMARY; } @Override public Object perform(InvocationContext ctx) throws Throwable { // It's not worth looking up the entry if we're never going to apply the change. if (valueMatcher == ValueMatcher.MATCH_NEVER) { successful = false; return null; } //noinspection unchecked MVCCEntry<Object, Object> e = (MVCCEntry) ctx.lookupEntry(key); // We need the null check as in non-tx caches we don't always wrap the entry on the origin Object prevValue = e.getValue(); if (valueMatcher.matches(prevValue, oldValue, newValue)) { e.setChanged(true); Object old = e.setValue(newValue); Metadatas.updateMetadata(e, metadata); if (valueMatcher != ValueMatcher.MATCH_EXPECTED_OR_NEW) { return returnValue(old, e.getMetadata(), true, ctx); } else { // Return the expected value when retrying return returnValue(oldValue, e.getMetadata(), true, ctx); } } return returnValue(null, null, false, ctx); } private Object returnValue(Object beingReplaced, Metadata previousMetadata, boolean successful, InvocationContext ctx) { this.successful = successful; Object previousValue = oldValue == null ? beingReplaced : oldValue; if (successful) { notifier.notifyCacheEntryModified(key, newValue, metadata, previousValue, previousMetadata, true, ctx, this); } if (oldValue == null) { return beingReplaced; } else { return successful; } } @Override public byte getCommandId() { return COMMAND_ID; } @Override public void writeTo(ObjectOutput output) throws IOException { output.writeObject(key); output.writeObject(oldValue); output.writeObject(newValue); output.writeObject(metadata); MarshallUtil.marshallEnum(valueMatcher, output); output.writeLong(FlagBitSets.copyWithoutRemotableFlags(getFlagsBitSet())); CommandInvocationId.writeTo(output, commandInvocationId); } @Override public void readFrom(ObjectInput input) throws IOException, ClassNotFoundException { key = input.readObject(); oldValue = input.readObject(); newValue = input.readObject(); metadata = (Metadata) input.readObject(); valueMatcher = MarshallUtil.unmarshallEnum(input, ValueMatcher::valueOf); setFlagsBitSet(input.readLong()); commandInvocationId = CommandInvocationId.readFrom(input); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; ReplaceCommand that = (ReplaceCommand) o; if (metadata != null ? !metadata.equals(that.metadata) : that.metadata != null) return false; if (newValue != null ? !newValue.equals(that.newValue) : that.newValue != null) return false; return oldValue != null ? oldValue.equals(that.oldValue) : that.oldValue == null; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (oldValue != null ? oldValue.hashCode() : 0); result = 31 * result + (newValue != null ? newValue.hashCode() : 0); result = 31 * result + (metadata != null ? metadata.hashCode() : 0); return result; } @Override public boolean isSuccessful() { return successful; } @Override public boolean isConditional() { return true; } @Override public Metadata getMetadata() { return metadata; } @Override public void setMetadata(Metadata metadata) { this.metadata = metadata; } public Object getOldValue() { return oldValue; } public void setOldValue(Object oldValue) { this.oldValue = oldValue; } public Object getNewValue() { return newValue; } public void setNewValue(Object newValue) { this.newValue = newValue; } @Override public ValueMatcher getValueMatcher() { return valueMatcher; } @Override public void setValueMatcher(ValueMatcher valueMatcher) { this.valueMatcher = valueMatcher; } @Override public void fail() { successful = false; } @Override public final boolean isReturnValueExpected() { return true; } @Override public String toString() { return "ReplaceCommand{" + "key=" + toStr(key) + ", oldValue=" + toStr(oldValue) + ", newValue=" + toStr(newValue) + ", metadata=" + metadata + ", flags=" + printFlags() + ", commandInvocationId=" + CommandInvocationId.show(commandInvocationId) + ", successful=" + successful + ", valueMatcher=" + valueMatcher + ", topologyId=" + getTopologyId() + '}'; } @Override public void initBackupWriteRpcCommand(BackupWriteRpcCommand command) { command.setReplace(commandInvocationId, key, newValue, metadata, getFlagsBitSet(), getTopologyId()); } }