All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.infinispan.client.hotrod.impl.transaction.TransactionContext Maven / Gradle / Ivy

There is a newer version: 15.1.0.Dev04
Show newest version
package org.infinispan.client.hotrod.impl.transaction;

import static org.infinispan.client.hotrod.logging.Log.HOTROD;
import static org.infinispan.commons.util.Util.toStr;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import jakarta.transaction.Transaction;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import org.infinispan.client.hotrod.MetadataValue;
import org.infinispan.client.hotrod.impl.operations.OperationsFactory;
import org.infinispan.client.hotrod.impl.transaction.entry.Modification;
import org.infinispan.client.hotrod.impl.transaction.entry.TransactionEntry;
import org.infinispan.client.hotrod.impl.transaction.operations.PrepareTransactionOperation;
import org.infinispan.client.hotrod.logging.Log;
import org.infinispan.client.hotrod.logging.LogFactory;
import org.infinispan.commons.util.ByRef;
import org.infinispan.commons.util.CloseableIterator;
import org.infinispan.commons.util.CloseableIteratorSet;

/**
 * A context with the keys involved in a {@link Transaction}.
 * 

* There is a single context for each ({@link TransactionalRemoteCacheImpl}, {@link Transaction}) pair. *

* It keeps the keys read and written in order to maintain the transactions isolated. * * @author Pedro Ruivo * @since 9.3 */ public class TransactionContext { private static final Log log = LogFactory.getLog(TransactionContext.class, Log.class); private final Map, TransactionEntry> entries; private final Function keyMarshaller; private final Function valueMarshaller; private final OperationsFactory operationsFactory; private final String cacheName; private final boolean recoverable; TransactionContext(Function keyMarshaller, Function valueMarshaller, OperationsFactory operationsFactory, String cacheName, boolean recoveryEnabled) { this.keyMarshaller = keyMarshaller; this.valueMarshaller = valueMarshaller; this.operationsFactory = operationsFactory; this.cacheName = cacheName; this.recoverable = recoveryEnabled; entries = new ConcurrentHashMap<>(); } @Override public String toString() { return "TransactionContext{" + "cacheName='" + cacheName + '\'' + ", context-size=" + entries.size() + " (entries)" + '}'; } CompletableFuture containsKey(Object key, Function> remoteValueSupplier) { CompletableFuture result = new CompletableFuture<>(); //noinspection unchecked entries.compute(wrap((K) key), (wKey, entry) -> { if (entry == null) { entry = createEntryFromRemote(wKey.key, remoteValueSupplier); } result.complete(!entry.isNonExists()); return entry; }); return result; } boolean containsValue(Object value, Supplier>> iteratorSupplier, Function> remoteValueSupplier) { boolean found = entries.values().stream() .map(TransactionEntry::getValue) .filter(Objects::nonNull) .anyMatch(v -> Objects.deepEquals(v, value)); return found || searchValue(value, iteratorSupplier.get(), remoteValueSupplier); } CompletableFuture compute(K key, Function, T> function) { CompletableFuture future = new CompletableFuture<>(); entries.compute(wrap(key), (wKey, entry) -> { if (entry == null) { entry = TransactionEntry.notReadEntry(wKey.key); } if (log.isTraceEnabled()) { log.tracef("Compute key (%s). Before=%s", wKey, entry); } T result = function.apply(entry); future.complete(result); if (log.isTraceEnabled()) { log.tracef("Compute key (%s). After=%s (result=%s)", wKey, entry, result); } return entry; }); return future; } CompletableFuture compute(K key, Function, T> function, Function> remoteValueSupplier) { CompletableFuture future = new CompletableFuture<>(); entries.compute(wrap(key), (wKey, entry) -> { if (entry == null) { entry = createEntryFromRemote(wKey.key, remoteValueSupplier); if (log.isTraceEnabled()) { log.tracef("Fetched key (%s) from remote. Entry=%s", wKey, entry); } } if (log.isTraceEnabled()) { log.tracef("Compute key (%s). Before=%s", wKey, entry); } T result = function.apply(entry); future.complete(result); if (log.isTraceEnabled()) { log.tracef("Compute key (%s). After=%s (result=%s)", wKey, entry, result); } return entry; }); return future; } boolean isReadWrite() { return entries.values().stream().anyMatch(TransactionEntry::isModified); } T computeSync(K key, Function, T> function, Function> remoteValueSupplier) { ByRef ref = new ByRef<>(null); entries.compute(wrap(key), (wKey, entry) -> { if (entry == null) { entry = createEntryFromRemote(wKey.key, remoteValueSupplier); if (log.isTraceEnabled()) { log.tracef("Fetched key (%s) from remote. Entry=%s", wKey, entry); } } if (log.isTraceEnabled()) { log.tracef("Compute key (%s). Before=%s", wKey, entry); } T result = function.apply(entry); ref.set(result); if (log.isTraceEnabled()) { log.tracef("Compute key (%s). After=%s (result=%s)", wKey, entry, result); } return entry; }); return ref.get(); } /** * Prepares the {@link Transaction} in the server and returns the {@link XAResource} code. *

* A special value {@link Integer#MIN_VALUE} is used to signal an error before contacting the server (for example, it * wasn't able to marshall the key/value) */ int prepareContext(Xid xid, boolean onePhaseCommit, long timeout) { PrepareTransactionOperation operation; List modifications; try { modifications = toModification(); if (log.isTraceEnabled()) { log.tracef("Preparing transaction xid=%s, remote-cache=%s, modification-size=%d", xid, cacheName, modifications.size()); } if (modifications.isEmpty()) { return XAResource.XA_RDONLY; } } catch (Exception e) { return Integer.MIN_VALUE; } try { int xaReturnCode; do { operation = operationsFactory .newPrepareTransactionOperation(xid, onePhaseCommit, modifications, recoverable, timeout); xaReturnCode = operation.execute().get(); } while (operation.shouldRetry()); return xaReturnCode; } catch (Exception e) { HOTROD.exceptionDuringPrepare(xid, e); return XAException.XA_RBROLLBACK; } } void cleanupEntries() { entries.clear(); } private List toModification() { return entries.values().stream() .filter(TransactionEntry::isModified) .map(entry -> entry.toModification(keyMarshaller, valueMarshaller)) .collect(Collectors.toList()); } private TransactionEntry createEntryFromRemote(K key, Function> remoteValueSupplier) { MetadataValue remoteValue = remoteValueSupplier.apply(key); return remoteValue == null ? TransactionEntry.nonExistingEntry(key) : TransactionEntry.read(key, remoteValue); } private boolean searchValue(Object value, CloseableIteratorSet> iterator, Function> remoteValueSupplier) { try (CloseableIterator> it = iterator.iterator()) { while (it.hasNext()) { Map.Entry entry = it.next(); if (!entries.containsKey(wrap(entry.getKey())) && Objects.deepEquals(entry.getValue(), value)) { ByRef.Boolean result = new ByRef.Boolean(false); entries.computeIfAbsent(wrap(entry.getKey()), wrappedKey -> { MetadataValue remoteValue = remoteValueSupplier.apply(wrappedKey.key); if (Objects.deepEquals(remoteValue.getValue(), value)) { //value didn't change. store it locally. result.set(true); return TransactionEntry.read(wrappedKey.key, remoteValue); } else { return null; } }); if (result.get()) { return true; } } } } //we iterated over all keys. return false; } private WrappedKey wrap(K key) { return new WrappedKey<>(key); } private static class WrappedKey { private final K key; private WrappedKey(K key) { this.key = key; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } WrappedKey that = (WrappedKey) o; return Objects.deepEquals(key, that.key); } @Override public int hashCode() { if (key instanceof Object[]) { return Arrays.deepHashCode((Object[]) key); } else if (key instanceof byte[]) { return Arrays.hashCode((byte[]) key); } else if (key instanceof short[]) { return Arrays.hashCode((short[]) key); } else if (key instanceof int[]) { return Arrays.hashCode((int[]) key); } else if (key instanceof long[]) { return Arrays.hashCode((long[]) key); } else if (key instanceof char[]) { return Arrays.hashCode((char[]) key); } else if (key instanceof float[]) { return Arrays.hashCode((float[]) key); } else if (key instanceof double[]) { return Arrays.hashCode((double[]) key); } else if (key instanceof boolean[]) { return Arrays.hashCode((boolean[]) key); } else { return Objects.hashCode(key); } } @Override public String toString() { return "WrappedKey{" + "key=" + toStr(key) + '}'; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy