package org.infinispan.commands.control; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; import org.infinispan.commands.FlagAffectedCommand; import org.infinispan.commands.TopologyAffectedCommand; import org.infinispan.commands.Visitor; import org.infinispan.commands.tx.AbstractTransactionBoundaryCommand; 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.impl.FlagBitSets; import org.infinispan.context.impl.RemoteTxInvocationContext; import org.infinispan.context.impl.TxInvocationContext; import org.infinispan.transaction.impl.RemoteTransaction; import org.infinispan.transaction.xa.GlobalTransaction; import org.infinispan.util.ByteString; import org.infinispan.util.concurrent.CompletableFutures; import org.infinispan.util.concurrent.locks.TransactionalRemoteLockCommand; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; /** * LockControlCommand is a command that enables distributed locking across infinispan nodes. * <p/> * For more details refer to: https://jira.jboss.org/jira/browse/ISPN-70 https://jira.jboss.org/jira/browse/ISPN-48 * * @author Vladimir Blagojevic (<a href="mailto:vblagoje@redhat.com">vblagoje@redhat.com</a>) * @author Mircea.Markus@jboss.com * @since 4.0 */ public class LockControlCommand extends AbstractTransactionBoundaryCommand implements FlagAffectedCommand, TopologyAffectedCommand, TransactionalRemoteLockCommand { private static final Log log = LogFactory.getLog(LockControlCommand.class); public static final int COMMAND_ID = 3; private List<Object> keys; private boolean unlock = false; private long flags = EnumUtil.EMPTY_BIT_SET; private LockControlCommand() { super(null); // For command id uniqueness test } public LockControlCommand(ByteString cacheName) { super(cacheName); } public LockControlCommand(Collection<?> keys, ByteString cacheName, long flags, GlobalTransaction gtx) { super(cacheName); if (keys != null) { //building defensive copies is here in order to support replaceKey operation this.keys = new ArrayList<>(keys); } else { this.keys = Collections.emptyList(); } this.flags = flags; this.globalTx = gtx; } public LockControlCommand(Object key, ByteString cacheName, long flags, GlobalTransaction gtx) { this(cacheName); this.keys = new ArrayList<>(1); this.keys.add(key); this.flags = flags; this.globalTx = gtx; } public void setGlobalTransaction(GlobalTransaction gtx) { globalTx = gtx; } public Collection<Object> getKeys() { return keys; } public void replaceKey(Object oldKey, Object replacement) { int i = keys.indexOf(oldKey); if (i >= 0) { keys.set(i, replacement); } } public void replaceKeys(Map<Object, Object> replacements) { for (int i = 0; i < keys.size(); i++) { Object replacement = replacements.get(keys.get(i)); if (replacement != null) { keys.set(i, replacement); } } } public boolean multipleKeys() { return keys.size() > 1; } public Object getSingleKey() { if (keys.size() == 0) return null; return keys.get(0); } @Override public Object acceptVisitor(InvocationContext ctx, Visitor visitor) throws Throwable { return visitor.visitLockControlCommand((TxInvocationContext) ctx, this); } @Override public CompletableFuture<Object> invokeAsync() throws Throwable { RemoteTxInvocationContext ctx = createContext(); if (ctx == null) { return CompletableFutures.completedNull(); } return invoker.invokeAsync(ctx, this); } @Override public RemoteTxInvocationContext createContext() { RemoteTransaction transaction = txTable.getRemoteTransaction(globalTx); if (transaction == null) { if (unlock) { log.tracef("Unlock for missing transaction %s. Not doing anything.", globalTx); return null; } //create a remote tx without any modifications (we do not know modifications ahead of time) transaction = txTable.getOrCreateRemoteTransaction(globalTx, null); } return icf.createRemoteTxInvocationContext(transaction, getOrigin()); } @Override public byte getCommandId() { return COMMAND_ID; } @Override public void writeTo(ObjectOutput output) throws IOException { super.writeTo(output); output.writeBoolean(unlock); MarshallUtil.marshallCollection(keys, output); output.writeLong(FlagBitSets.copyWithoutRemotableFlags(flags)); } @Override @SuppressWarnings("unchecked") public void readFrom(ObjectInput input) throws IOException, ClassNotFoundException { super.readFrom(input); unlock = input.readBoolean(); keys = MarshallUtil.unmarshallCollection(input, ArrayList::new); flags = input.readLong(); } public boolean isUnlock() { return unlock; } public void setUnlock(boolean unlock) { this.unlock = unlock; } @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; LockControlCommand that = (LockControlCommand) o; if (unlock != that.unlock) return false; if (flags != that.flags) return false; if (!keys.equals(that.keys)) return false; return true; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + keys.hashCode(); result = 31 * result + (unlock ? 1 : 0); result = 31 * result + (int) (flags ^ (flags >>> 32)); return result; } @Override public String toString() { return new StringBuilder() .append("LockControlCommand{cache=").append(cacheName) .append(", keys=").append(keys) .append(", flags=").append(EnumUtil.prettyPrintBitSet(flags, Flag.class)) .append(", unlock=").append(unlock) .append(", gtx=").append(globalTx) .append("}") .toString(); } @Override public long getFlagsBitSet() { return flags; } @Override public void setFlagsBitSet(long bitSet) { this.flags = bitSet; } @Override public Collection<?> getKeysToLock() { return unlock ? Collections.emptyList() : Collections.unmodifiableCollection(keys); } @Override public Object getKeyLockOwner() { return globalTx; } @Override public boolean hasZeroLockAcquisition() { return hasAnyFlag(FlagBitSets.ZERO_LOCK_ACQUISITION_TIMEOUT); } @Override public boolean hasSkipLocking() { return hasAnyFlag(FlagBitSets.SKIP_LOCKING); //is it possible?? } }