
org.ehcache.transactions.xa.internal.XAStore Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache-transactions Show documentation
Show all versions of ehcache-transactions Show documentation
The transactions module of Ehcache 3
The newest version!
/*
* Copyright Terracotta, 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 org.ehcache.transactions.xa.internal;
import org.ehcache.Cache;
import org.ehcache.config.ResourceType;
import org.ehcache.core.CacheConfigurationChangeListener;
import org.ehcache.config.EvictionAdvisor;
import org.ehcache.core.spi.service.DiskResourceService;
import org.ehcache.core.spi.service.StatisticsService;
import org.ehcache.core.spi.store.WrapperStore;
import org.ehcache.core.store.StoreConfigurationImpl;
import org.ehcache.core.store.StoreSupport;
import org.ehcache.impl.store.BaseStore;
import org.ehcache.spi.resilience.StoreAccessException;
import org.ehcache.expiry.ExpiryPolicy;
import org.ehcache.impl.config.copy.DefaultCopierConfiguration;
import org.ehcache.impl.copy.SerializingCopier;
import org.ehcache.core.spi.time.TimeSource;
import org.ehcache.core.spi.time.TimeSourceService;
import org.ehcache.spi.serialization.StatefulSerializer;
import org.ehcache.spi.service.OptionalServiceDependencies;
import org.ehcache.spi.service.ServiceProvider;
import org.ehcache.core.spi.store.Store;
import org.ehcache.core.spi.store.events.StoreEventSource;
import org.ehcache.spi.copy.Copier;
import org.ehcache.spi.copy.CopyProvider;
import org.ehcache.spi.service.Service;
import org.ehcache.spi.service.ServiceConfiguration;
import org.ehcache.spi.service.ServiceDependencies;
import org.ehcache.transactions.xa.XACacheException;
import org.ehcache.transactions.xa.internal.commands.StoreEvictCommand;
import org.ehcache.transactions.xa.internal.commands.StorePutCommand;
import org.ehcache.transactions.xa.internal.commands.StoreRemoveCommand;
import org.ehcache.transactions.xa.configuration.XAStoreConfiguration;
import org.ehcache.transactions.xa.internal.journal.Journal;
import org.ehcache.transactions.xa.internal.journal.JournalProvider;
import org.ehcache.transactions.xa.txmgr.TransactionManagerWrapper;
import org.ehcache.transactions.xa.txmgr.provider.TransactionManagerProvider;
import org.ehcache.core.collections.ConcurrentWeakIdentityHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.transaction.RollbackException;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import static org.ehcache.core.spi.service.ServiceUtils.findAmongst;
import static org.ehcache.core.spi.service.ServiceUtils.findSingletonAmongst;
import static org.ehcache.transactions.xa.internal.TypeUtil.uncheckedCast;
/**
* A {@link Store} implementation wrapping another {@link Store} driven by a JTA
* {@link javax.transaction.TransactionManager} using the XA 2-phase commit protocol.
*/
public class XAStore extends BaseStore implements WrapperStore {
private static final Logger LOGGER = LoggerFactory.getLogger(XAStore.class);
private static final Supplier REPLACE_EQUALS_TRUE = () -> Boolean.TRUE;
private final Store> underlyingStore;
private final TransactionManagerWrapper transactionManagerWrapper;
private final Map> xaResources = new ConcurrentHashMap<>();
private final TimeSource timeSource;
private final Journal journal;
private final String uniqueXAResourceId;
private final XATransactionContextFactory transactionContextFactory;
private final EhcacheXAResource recoveryXaResource;
private final StoreEventSourceWrapper eventSourceWrapper;
public XAStore(Class keyType, Class valueType, Store> underlyingStore, TransactionManagerWrapper transactionManagerWrapper,
TimeSource timeSource, Journal journal, String uniqueXAResourceId, StatisticsService statisticsService) {
super(keyType, valueType, true, statisticsService);
this.underlyingStore = underlyingStore;
this.transactionManagerWrapper = transactionManagerWrapper;
this.timeSource = timeSource;
this.journal = journal;
this.uniqueXAResourceId = uniqueXAResourceId;
this.transactionContextFactory = new XATransactionContextFactory<>(timeSource);
this.recoveryXaResource = new EhcacheXAResource<>(underlyingStore, journal, transactionContextFactory);
this.eventSourceWrapper = new StoreEventSourceWrapper<>(underlyingStore.getStoreEventSource());
}
private static boolean isInDoubt(SoftLock> softLock) {
return softLock.getTransactionId() != null;
}
private ValueHolder> getSoftLockValueHolderFromUnderlyingStore(K key) throws StoreAccessException {
return underlyingStore.get(key);
}
private XATransactionContext getCurrentContext() {
try {
final Transaction transaction = transactionManagerWrapper.getTransactionManager().getTransaction();
if (transaction == null) {
throw new XACacheException("Cannot access XA cache outside of XA transaction scope");
}
EhcacheXAResource xaResource = xaResources.get(transaction);
if (xaResource == null) {
xaResource = new EhcacheXAResource<>(underlyingStore, journal, transactionContextFactory);
transactionManagerWrapper.registerXAResource(uniqueXAResourceId, xaResource);
transactionManagerWrapper.getTransactionManager().getTransaction().enlistResource(xaResource);
xaResources.put(transaction, xaResource);
final EhcacheXAResource finalXaResource = xaResource;
transaction.registerSynchronization(new Synchronization() {
@Override
public void beforeCompletion() {
}
@Override
public void afterCompletion(int status) {
transactionManagerWrapper.unregisterXAResource(uniqueXAResourceId, finalXaResource);
xaResources.remove(transaction);
}
});
}
XATransactionContext currentContext = xaResource.getCurrentContext();
if (currentContext.hasTimedOut()) {
throw new XACacheException("Current XA transaction has timed out");
}
return currentContext;
} catch (SystemException se) {
throw new XACacheException("Cannot get current XA transaction", se);
} catch (RollbackException re) {
throw new XACacheException("XA Transaction has been marked for rollback only", re);
}
}
@Override
public ValueHolder get(K key) throws StoreAccessException {
checkKey(key);
XATransactionContext currentContext = getCurrentContext();
if (currentContext.removed(key)) {
return null;
}
XAValueHolder newValueHolder = currentContext.newValueHolderOf(key);
if (newValueHolder != null) {
return newValueHolder;
}
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder == null) {
return null;
}
SoftLock softLock = softLockValueHolder.get();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<>(softLock.getOldValue()));
return null;
}
return new XAValueHolder<>(softLockValueHolder, softLock.getOldValue());
}
@Override
public boolean containsKey(K key) throws StoreAccessException {
checkKey(key);
if (getCurrentContext().touched(key)) {
return getCurrentContext().newValueHolderOf(key) != null;
}
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
return softLockValueHolder != null && softLockValueHolder.get().getTransactionId() == null && softLockValueHolder.get().getOldValue() != null;
}
@Override
public PutStatus put(K key, V value) throws StoreAccessException {
checkKey(key);
checkValue(value);
XATransactionContext currentContext = getCurrentContext();
if (currentContext.touched(key)) {
V oldValue = currentContext.oldValueOf(key);
currentContext.addCommand(key, new StorePutCommand<>(oldValue, new XAValueHolder<>(value, timeSource.getTimeMillis())));
return PutStatus.PUT;
}
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder != null) {
SoftLock softLock = softLockValueHolder.get();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<>(softLock.getOldValue()));
} else {
if (currentContext.addCommand(key, new StorePutCommand<>(softLock.getOldValue(), new XAValueHolder<>(value, timeSource
.getTimeMillis())))) {
return PutStatus.PUT;
}
}
} else {
if (currentContext.addCommand(key, new StorePutCommand<>(null, new XAValueHolder<>(value, timeSource.getTimeMillis())))) {
return PutStatus.PUT;
}
}
return PutStatus.NOOP;
}
@Override
public boolean remove(K key) throws StoreAccessException {
checkKey(key);
XATransactionContext currentContext = getCurrentContext();
if (currentContext.touched(key)) {
V oldValue = currentContext.oldValueOf(key);
V newValue = currentContext.newValueOf(key);
currentContext.addCommand(key, new StoreRemoveCommand<>(oldValue));
return newValue != null;
}
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
boolean status = false;
if (softLockValueHolder != null) {
SoftLock softLock = softLockValueHolder.get();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<>(softLock.getOldValue()));
} else {
status = currentContext.addCommand(key, new StoreRemoveCommand<>(softLock.getOldValue()));
}
}
return status;
}
@Override
public ValueHolder putIfAbsent(K key, V value, Consumer put) throws StoreAccessException {
checkKey(key);
checkValue(value);
XATransactionContext currentContext = getCurrentContext();
if (currentContext.touched(key)) {
V oldValue = currentContext.oldValueOf(key);
V newValue = currentContext.newValueOf(key);
if (newValue == null) {
currentContext.addCommand(key, new StorePutCommand<>(oldValue, new XAValueHolder<>(value, timeSource.getTimeMillis())));
return null;
} else {
return currentContext.newValueHolderOf(key);
}
}
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder != null) {
SoftLock softLock = softLockValueHolder.get();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<>(softLock.getOldValue()));
return null;
} else {
return new XAValueHolder<>(softLockValueHolder, softLock.getOldValue());
}
} else {
currentContext.addCommand(key, new StorePutCommand<>(null, new XAValueHolder<>(value, timeSource.getTimeMillis())));
return null;
}
}
@Override
public RemoveStatus remove(K key, V value) throws StoreAccessException {
checkKey(key);
checkValue(value);
XATransactionContext currentContext = getCurrentContext();
if (currentContext.touched(key)) {
V oldValue = currentContext.oldValueOf(key);
V newValue = currentContext.newValueOf(key);
if (newValue == null) {
return RemoveStatus.KEY_MISSING;
} else if (!newValue.equals(value)) {
return RemoveStatus.KEY_PRESENT;
} else {
currentContext.addCommand(key, new StoreRemoveCommand<>(oldValue));
return RemoveStatus.REMOVED;
}
}
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder != null) {
SoftLock softLock = softLockValueHolder.get();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<>(softLock.getOldValue()));
return RemoveStatus.KEY_MISSING;
} else if (!softLock.getOldValue().equals(value)) {
return RemoveStatus.KEY_PRESENT;
} else {
currentContext.addCommand(key, new StoreRemoveCommand<>(softLock.getOldValue()));
return RemoveStatus.REMOVED;
}
} else {
return RemoveStatus.KEY_MISSING;
}
}
@Override
public ValueHolder replace(K key, V value) throws StoreAccessException {
checkKey(key);
checkValue(value);
XATransactionContext currentContext = getCurrentContext();
if (currentContext.touched(key)) {
V newValue = currentContext.newValueOf(key);
if (newValue == null) {
return null;
} else {
V oldValue = currentContext.oldValueOf(key);
XAValueHolder newValueHolder = currentContext.newValueHolderOf(key);
currentContext.addCommand(key, new StorePutCommand<>(oldValue, new XAValueHolder<>(value, timeSource.getTimeMillis())));
return newValueHolder;
}
}
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder != null) {
SoftLock softLock = softLockValueHolder.get();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<>(softLock.getOldValue()));
return null;
} else {
V oldValue = softLock.getOldValue();
currentContext.addCommand(key, new StorePutCommand<>(oldValue, new XAValueHolder<>(value, timeSource.getTimeMillis())));
return new XAValueHolder<>(oldValue, softLockValueHolder.creationTime());
}
} else {
return null;
}
}
@Override
public ReplaceStatus replace(K key, V oldValue, V newValue) throws StoreAccessException {
checkKey(key);
checkValue(oldValue);
checkValue(newValue);
XATransactionContext currentContext = getCurrentContext();
if (currentContext.touched(key)) {
V modifiedValue = currentContext.newValueOf(key);
if (modifiedValue == null) {
return ReplaceStatus.MISS_NOT_PRESENT;
} else if (!modifiedValue.equals(oldValue)) {
return ReplaceStatus.MISS_PRESENT;
} else {
V previousValue = currentContext.oldValueOf(key);
currentContext.addCommand(key, new StorePutCommand<>(previousValue, new XAValueHolder<>(newValue, timeSource.getTimeMillis())));
return ReplaceStatus.HIT;
}
}
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder != null) {
SoftLock softLock = softLockValueHolder.get();
V previousValue = softLock.getOldValue();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<>(previousValue));
return ReplaceStatus.MISS_NOT_PRESENT;
} else if (!previousValue.equals(oldValue)) {
return ReplaceStatus.MISS_PRESENT;
} else {
currentContext.addCommand(key, new StorePutCommand<>(previousValue, new XAValueHolder<>(newValue, timeSource.getTimeMillis())));
return ReplaceStatus.HIT;
}
} else {
return ReplaceStatus.MISS_NOT_PRESENT;
}
}
@Override
public void clear() throws StoreAccessException {
// we don't want that to be transactional
underlyingStore.clear();
}
@Override
public StoreEventSource getStoreEventSource() {
return eventSourceWrapper;
}
@Override
public Iterator>> iterator() {
XATransactionContext currentContext = getCurrentContext();
Map> valueHolderMap = transactionContextFactory.listPuts(currentContext.getTransactionId());
return new XAIterator(valueHolderMap, underlyingStore.iterator(), currentContext.getTransactionId());
}
@Override
protected String getStatisticsTag() {
return "XaStore";
}
class XAIterator implements Iterator>> {
private final java.util.Iterator>> iterator;
private final Iterator>>> underlyingIterator;
private final TransactionId transactionId;
private Cache.Entry> next;
private StoreAccessException prefetchFailure = null;
XAIterator(Map> valueHolderMap, Iterator>>> underlyingIterator, TransactionId transactionId) {
this.transactionId = transactionId;
this.iterator = valueHolderMap.entrySet().iterator();
this.underlyingIterator = underlyingIterator;
advance();
}
void advance() {
next = null;
if (iterator.hasNext()) {
final Map.Entry> entry = iterator.next();
this.next = new Cache.Entry>() {
@Override
public K getKey() {
return entry.getKey();
}
@Override
public ValueHolder getValue() {
return entry.getValue();
}
};
return;
}
while (underlyingIterator.hasNext()) {
final Cache.Entry>> next;
try {
next = underlyingIterator.next();
} catch (StoreAccessException e) {
prefetchFailure = e;
break;
}
if (!transactionContextFactory.isTouched(transactionId, next.getKey())) {
ValueHolder> valueHolder = next.getValue();
SoftLock softLock = valueHolder.get();
final XAValueHolder xaValueHolder;
if (softLock.getTransactionId() == transactionId) {
xaValueHolder = new XAValueHolder<>(valueHolder, softLock.getNewValueHolder().get());
} else if (isInDoubt(softLock)) {
continue;
} else {
xaValueHolder = new XAValueHolder<>(valueHolder, softLock.getOldValue());
}
this.next = new Cache.Entry>() {
@Override
public K getKey() {
return next.getKey();
}
@Override
public ValueHolder getValue() {
return xaValueHolder;
}
};
break;
}
}
}
@Override
public boolean hasNext() {
if (!getCurrentContext().getTransactionId().equals(transactionId)) {
throw new IllegalStateException("Iterator has been created in another transaction, it can only be used in the transaction it has been created in.");
}
return next != null | prefetchFailure != null;
}
@Override
public Cache.Entry> next() throws StoreAccessException {
if(prefetchFailure != null) {
throw prefetchFailure;
}
if (!getCurrentContext().getTransactionId().equals(transactionId)) {
throw new IllegalStateException("Iterator has been created in another transaction, it can only be used in the transaction it has been created in.");
}
if (next == null) {
throw new NoSuchElementException();
}
Cache.Entry> rc = next;
advance();
return rc;
}
}
@Override
public ValueHolder computeAndGet(K key, BiFunction super K, ? super V, ? extends V> mappingFunction, Supplier replaceEqual, Supplier invokeWriter) throws StoreAccessException {
checkKey(key);
XATransactionContext currentContext = getCurrentContext();
if (currentContext.touched(key)) {
return updateCommandForKey(key, mappingFunction, replaceEqual, currentContext);
}
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
SoftLock softLock = softLockValueHolder == null ? null : softLockValueHolder.get();
V oldValue = softLock == null ? null : softLock.getOldValue();
V newValue = mappingFunction.apply(key, oldValue);
XAValueHolder xaValueHolder = newValue == null ? null : new XAValueHolder<>(newValue, timeSource.getTimeMillis());
if (Objects.equals(oldValue, newValue) && !replaceEqual.get()) {
return xaValueHolder;
}
if (newValue != null) {
checkValue(newValue);
}
if (softLock != null && isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<>(oldValue));
} else {
if (xaValueHolder == null) {
if (oldValue != null) {
currentContext.addCommand(key, new StoreRemoveCommand<>(oldValue));
}
} else {
currentContext.addCommand(key, new StorePutCommand<>(oldValue, xaValueHolder));
}
}
return xaValueHolder;
}
@Override
public ValueHolder getAndCompute(K key, BiFunction super K, ? super V, ? extends V> mappingFunction) throws StoreAccessException {
checkKey(key);
XATransactionContext currentContext = getCurrentContext();
if (currentContext.touched(key)) {
V computed = mappingFunction.apply(key, currentContext.newValueOf(key));
XAValueHolder returnValueholder = null;
if (computed != null) {
checkValue(computed);
XAValueHolder xaValueHolder = new XAValueHolder<>(computed, timeSource.getTimeMillis());
V returnValue = currentContext.newValueOf(key);
V oldValue = currentContext.oldValueOf(key);
if (returnValue != null) {
returnValueholder = new XAValueHolder<>(returnValue, timeSource.getTimeMillis());
}
currentContext.addCommand(key, new StorePutCommand<>(oldValue, xaValueHolder));
} else {
V returnValue = currentContext.newValueOf(key);
V oldValue = currentContext.oldValueOf(key);
if (returnValue != null) {
returnValueholder = new XAValueHolder<>(returnValue, timeSource.getTimeMillis());
}
if (oldValue != null) {
currentContext.addCommand(key, new StoreRemoveCommand<>(oldValue));
} else {
currentContext.removeCommand(key);
}
}
return returnValueholder;
}
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
XAValueHolder oldValueHolder = null;
SoftLock softLock = softLockValueHolder == null ? null : softLockValueHolder.get();
V oldValue = softLock == null ? null : softLock.getOldValue();
V newValue = mappingFunction.apply(key, oldValue);
XAValueHolder xaValueHolder = newValue == null ? null : new XAValueHolder<>(newValue, timeSource.getTimeMillis());
if (newValue != null) {
checkValue(newValue);
}
if (softLock != null && isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<>(oldValue));
} else {
if (xaValueHolder == null) {
if (oldValue != null) {
currentContext.addCommand(key, new StoreRemoveCommand<>(oldValue));
}
} else {
currentContext.addCommand(key, new StorePutCommand<>(oldValue, xaValueHolder));
}
}
if (oldValue != null) {
oldValueHolder = new XAValueHolder<>(oldValue, timeSource.getTimeMillis());
}
return oldValueHolder;
}
@Override
public ValueHolder computeIfAbsent(K key, final Function super K, ? extends V> mappingFunction) throws StoreAccessException {
checkKey(key);
XATransactionContext currentContext = getCurrentContext();
if (currentContext.removed(key)) {
return updateCommandForKey(key, mappingFunction, currentContext);
}
if (currentContext.evicted(key)) {
return new XAValueHolder<>(currentContext.oldValueOf(key), timeSource.getTimeMillis());
}
boolean updated = currentContext.touched(key);
XAValueHolder xaValueHolder;
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder == null) {
if (updated) {
xaValueHolder = currentContext.newValueHolderOf(key);
} else {
V computed = mappingFunction.apply(key);
if (computed != null) {
xaValueHolder = new XAValueHolder<>(computed, timeSource.getTimeMillis());
currentContext.addCommand(key, new StorePutCommand<>(null, xaValueHolder));
} else {
xaValueHolder = null;
}
}
} else if (isInDoubt(softLockValueHolder.get())) {
currentContext.addCommand(key, new StoreEvictCommand<>(softLockValueHolder.get().getOldValue()));
xaValueHolder = new XAValueHolder<>(softLockValueHolder, softLockValueHolder.get().getNewValueHolder().get());
} else {
if (updated) {
xaValueHolder = currentContext.newValueHolderOf(key);
} else {
xaValueHolder = new XAValueHolder<>(softLockValueHolder, softLockValueHolder.get().getOldValue());
}
}
return xaValueHolder;
}
private ValueHolder updateCommandForKey(K key, BiFunction super K, ? super V, ? extends V> mappingFunction, Supplier replaceEqual, XATransactionContext currentContext) {
V newValue = mappingFunction.apply(key, currentContext.newValueOf(key));
XAValueHolder xaValueHolder = null;
V oldValue = currentContext.oldValueOf(key);
if (newValue == null) {
if (!(oldValue == null && !replaceEqual.get())) {
currentContext.addCommand(key, new StoreRemoveCommand<>(oldValue));
} else {
currentContext.removeCommand(key);
}
} else {
checkValue(newValue);
xaValueHolder = new XAValueHolder<>(newValue, timeSource.getTimeMillis());
if (!(Objects.equals(oldValue, newValue) && !replaceEqual.get())) {
currentContext.addCommand(key, new StorePutCommand<>(oldValue, xaValueHolder));
}
}
return xaValueHolder;
}
private ValueHolder updateCommandForKey(K key, Function super K, ? extends V> mappingFunction, XATransactionContext currentContext) {
V computed = mappingFunction.apply(key);
XAValueHolder xaValueHolder = null;
if (computed != null) {
checkValue(computed);
xaValueHolder = new XAValueHolder<>(computed, timeSource.getTimeMillis());
V oldValue = currentContext.oldValueOf(key);
currentContext.addCommand(key, new StorePutCommand<>(oldValue, xaValueHolder));
} // else do nothing
return xaValueHolder;
}
@Override
public Map> bulkCompute(Set extends K> keys, Function>, Iterable extends Map.Entry extends K, ? extends V>>> remappingFunction) throws StoreAccessException {
return bulkCompute(keys, remappingFunction, REPLACE_EQUALS_TRUE);
}
@Override
public Map> bulkCompute(Set extends K> keys, final Function>, Iterable extends Map.Entry extends K, ? extends V>>> remappingFunction, Supplier replaceEqual) throws StoreAccessException {
Map> result = new HashMap<>();
for (K key : keys) {
checkKey(key);
final ValueHolder newValue = computeAndGet(key, (k, oldValue) -> {
final Set> entrySet = Collections.singletonMap(k, oldValue).entrySet();
final Iterable extends Map.Entry extends K, ? extends V>> entries = remappingFunction.apply(entrySet);
final java.util.Iterator extends Map.Entry extends K, ? extends V>> iterator = entries.iterator();
final Map.Entry extends K, ? extends V> next = iterator.next();
K key1 = next.getKey();
V value = next.getValue();
checkKey(key1);
if (value != null) {
checkValue(value);
}
return value;
}, replaceEqual, () -> false);
result.put(key, newValue);
}
return result;
}
@Override
public Map> bulkComputeIfAbsent(Set extends K> keys, final Function, Iterable extends Map.Entry extends K, ? extends V>>> mappingFunction) throws StoreAccessException {
Map> result = new HashMap<>();
for (final K key : keys) {
final ValueHolder newValue = computeIfAbsent(key, keyParam -> {
final Iterable keySet = Collections.singleton(keyParam);
final Iterable extends Map.Entry extends K, ? extends V>> entries = mappingFunction.apply(keySet);
final java.util.Iterator extends Map.Entry extends K, ? extends V>> iterator = entries.iterator();
final Map.Entry extends K, ? extends V> next = iterator.next();
K computedKey = next.getKey();
V computedValue = next.getValue();
checkKey(computedKey);
if (computedValue == null) {
return null;
}
checkValue(computedValue);
return computedValue;
});
result.put(key, newValue);
}
return result;
}
@Override
public List getConfigurationChangeListeners() {
return underlyingStore.getConfigurationChangeListeners();
}
private static final class SoftLockValueCombinedSerializerLifecycleHelper {
final AtomicReference> softLockSerializerRef;
final ClassLoader classLoader;
SoftLockValueCombinedSerializerLifecycleHelper(AtomicReference> softLockSerializerRef, ClassLoader classLoader) {
this.softLockSerializerRef = softLockSerializerRef;
this.classLoader = classLoader;
}
}
private static final class CreatedStoreRef {
final Store.Provider storeProvider;
final SoftLockValueCombinedSerializerLifecycleHelper> lifecycleHelper;
public CreatedStoreRef(final Store.Provider storeProvider, final SoftLockValueCombinedSerializerLifecycleHelper> lifecycleHelper) {
this.storeProvider = storeProvider;
this.lifecycleHelper = lifecycleHelper;
}
}
@ServiceDependencies({TimeSourceService.class, JournalProvider.class, CopyProvider.class, TransactionManagerProvider.class})
@OptionalServiceDependencies("org.ehcache.core.spi.service.StatisticsService")
public static class Provider implements WrapperStore.Provider {
private volatile ServiceProvider serviceProvider;
private volatile TransactionManagerProvider transactionManagerProvider;
private final Map, CreatedStoreRef> createdStores = new ConcurrentWeakIdentityHashMap<>();
@Override
public int rank(final Set> resourceTypes, final Collection> serviceConfigs) {
throw new UnsupportedOperationException("Its a Wrapper store provider, does not support regular ranking");
}
@Override
public Store createStore(Configuration storeConfig, ServiceConfiguration, ?>... serviceConfigs) {
Set supportedTypes = EnumSet.allOf(ResourceType.Core.class);
Set> configuredTypes = storeConfig.getResourcePools().getResourceTypeSet();
for (ResourceType> type: configuredTypes) {
if (!supportedTypes.contains(type)) {
throw new IllegalStateException("Unsupported resource type : " + type.getResourcePoolClass());
}
}
XAStoreConfiguration xaServiceConfiguration = findSingletonAmongst(XAStoreConfiguration.class, (Object[]) serviceConfigs);
if (xaServiceConfiguration == null) {
throw new IllegalStateException("XAStore.Provider.createStore called without XAStoreConfiguration");
}
List> serviceConfigList = Arrays.asList(serviceConfigs);
Store.Provider underlyingStoreProvider = StoreSupport.selectStoreProvider(serviceProvider,
storeConfig.getResourcePools().getResourceTypeSet(), serviceConfigList);
String uniqueXAResourceId = xaServiceConfiguration.getUniqueXAResourceId();
List> underlyingServiceConfigs = new ArrayList<>(serviceConfigList.size() + 5); // pad a bit because we add stuff
underlyingServiceConfigs.addAll(serviceConfigList);
// eviction advisor
EvictionAdvisor super K, ? super V> realEvictionAdvisor = storeConfig.getEvictionAdvisor();
EvictionAdvisor super K, ? super SoftLock> evictionAdvisor;
if (realEvictionAdvisor == null) {
evictionAdvisor = null;
} else {
evictionAdvisor = new XAEvictionAdvisor<>(realEvictionAdvisor);
}
// expiry
final ExpiryPolicy super K, ? super V> configuredExpiry = storeConfig.getExpiry();
ExpiryPolicy super K, ? super SoftLock> expiry = new ExpiryPolicy>() {
@Override
public Duration getExpiryForCreation(K key, SoftLock softLock) {
if (softLock.getTransactionId() != null) {
// phase 1 prepare, create -> forever
return ExpiryPolicy.INFINITE;
} else {
// phase 2 commit, or during a TX's lifetime, create -> some time
Duration duration;
try {
duration = configuredExpiry.getExpiryForCreation(key, softLock.getOldValue());
} catch (RuntimeException re) {
LOGGER.error("Expiry computation caused an exception - Expiry duration will be 0 ", re);
return Duration.ZERO;
}
return duration;
}
}
@Override
public Duration getExpiryForAccess(K key, Supplier extends SoftLock> softLock) {
if (softLock.get().getTransactionId() != null) {
// phase 1 prepare, access -> forever
return ExpiryPolicy.INFINITE;
} else {
// phase 2 commit, or during a TX's lifetime, access -> some time
Duration duration;
try {
duration = configuredExpiry.getExpiryForAccess(key, () -> softLock.get().getOldValue());
} catch (RuntimeException re) {
LOGGER.error("Expiry computation caused an exception - Expiry duration will be 0 ", re);
return Duration.ZERO;
}
return duration;
}
}
@Override
public Duration getExpiryForUpdate(K key, Supplier extends SoftLock> oldSoftLockSupplier, SoftLock newSoftLock) {
SoftLock oldSoftLock = oldSoftLockSupplier.get();
if (oldSoftLock.getTransactionId() == null) {
// phase 1 prepare, update -> forever
return ExpiryPolicy.INFINITE;
} else {
// phase 2 commit, or during a TX's lifetime
if (oldSoftLock.getOldValue() == null) {
// there is no old value -> it's a CREATE, update -> create -> some time
Duration duration;
try {
duration = configuredExpiry.getExpiryForCreation(key, oldSoftLock.getOldValue());
} catch (RuntimeException re) {
LOGGER.error("Expiry computation caused an exception - Expiry duration will be 0 ", re);
return Duration.ZERO;
}
return duration;
} else {
// there is an old value -> it's an UPDATE, update -> some time
V value = oldSoftLock.getNewValueHolder() == null ? null : oldSoftLock
.getNewValueHolder().get();
Duration duration;
try {
duration = configuredExpiry.getExpiryForUpdate(key, oldSoftLock::getOldValue, value);
} catch (RuntimeException re) {
LOGGER.error("Expiry computation caused an exception - Expiry duration will be 0 ", re);
return Duration.ZERO;
}
return duration;
}
}
}
};
// get the PersistenceSpaceIdentifier if the cache is persistent, null otherwise
DiskResourceService.PersistenceSpaceIdentifier> persistenceSpaceId = findSingletonAmongst(DiskResourceService.PersistenceSpaceIdentifier.class, (Object[]) serviceConfigs);
// find the copiers
Collection> copierConfigs = uncheckedCast(findAmongst(DefaultCopierConfiguration.class, underlyingServiceConfigs));
DefaultCopierConfiguration keyCopierConfig = null;
DefaultCopierConfiguration> valueCopierConfig = null;
for (DefaultCopierConfiguration> copierConfig : copierConfigs) {
if (copierConfig.getType().equals(DefaultCopierConfiguration.Type.KEY)) {
keyCopierConfig = uncheckedCast(copierConfig);
} else if (copierConfig.getType().equals(DefaultCopierConfiguration.Type.VALUE)) {
valueCopierConfig = uncheckedCast(copierConfig);
}
underlyingServiceConfigs.remove(copierConfig);
}
// force-in a key copier if none is configured
if (keyCopierConfig == null) {
underlyingServiceConfigs.add(new DefaultCopierConfiguration<>(SerializingCopier.asCopierClass(), DefaultCopierConfiguration.Type.KEY));
} else {
underlyingServiceConfigs.add(keyCopierConfig);
}
// force-in a value copier if none is configured, or wrap the configured one in a soft lock copier
if (valueCopierConfig == null) {
underlyingServiceConfigs.add(new DefaultCopierConfiguration<>(SerializingCopier.asCopierClass(), DefaultCopierConfiguration.Type.VALUE));
} else {
CopyProvider copyProvider = serviceProvider.getService(CopyProvider.class);
Copier valueCopier = copyProvider.createValueCopier(storeConfig.getValueType(), storeConfig.getValueSerializer(), valueCopierConfig);
Copier> softLockValueCombinedCopier = new SoftLockValueCombinedCopier<>(valueCopier);
underlyingServiceConfigs.add(new DefaultCopierConfiguration<>(softLockValueCombinedCopier, DefaultCopierConfiguration.Type.VALUE));
}
// lookup the required XAStore services
Journal journal = serviceProvider.getService(JournalProvider.class).getJournal(persistenceSpaceId, storeConfig.getKeySerializer());
TimeSource timeSource = serviceProvider.getService(TimeSourceService.class).getTimeSource();
// create the soft lock serializer
AtomicReference> softLockSerializerRef = new AtomicReference<>();
SoftLockValueCombinedSerializer softLockValueCombinedSerializer;
if (storeConfig.getValueSerializer() instanceof StatefulSerializer) {
softLockValueCombinedSerializer = new StatefulSoftLockValueCombinedSerializer(softLockSerializerRef, storeConfig
.getValueSerializer());
} else {
softLockValueCombinedSerializer = new SoftLockValueCombinedSerializer(softLockSerializerRef, storeConfig
.getValueSerializer());
}
// create the underlying store
Class> softLockClass = uncheckedCast(SoftLock.class);
Store.Configuration> underlyingStoreConfig = new StoreConfigurationImpl<>(storeConfig.getKeyType(), softLockClass, evictionAdvisor,
storeConfig.getClassLoader(), expiry, storeConfig.getResourcePools(), storeConfig.getDispatcherConcurrency(), storeConfig
.getKeySerializer(), softLockValueCombinedSerializer);
Store> underlyingStore = underlyingStoreProvider.createStore(underlyingStoreConfig, underlyingServiceConfigs.toArray(new ServiceConfiguration, ?>[0]));
// create the XA store
StatisticsService statisticsService = serviceProvider.getService(StatisticsService.class);
TransactionManagerWrapper transactionManagerWrapper = transactionManagerProvider.getTransactionManagerWrapper();
Store store = new XAStore<>(storeConfig.getKeyType(), storeConfig.getValueType(), underlyingStore,
transactionManagerWrapper, timeSource, journal, uniqueXAResourceId, statisticsService);
if (statisticsService != null) {
statisticsService.registerWithParent(underlyingStore, store);
}
// create the softLockSerializer lifecycle helper
SoftLockValueCombinedSerializerLifecycleHelper helper =
new SoftLockValueCombinedSerializerLifecycleHelper(softLockSerializerRef, storeConfig.getClassLoader());
createdStores.put(store, new CreatedStoreRef(underlyingStoreProvider, helper));
return store;
}
@Override
public void releaseStore(Store, ?> resource) {
CreatedStoreRef createdStoreRef = createdStores.remove(resource);
if (createdStoreRef == null) {
throw new IllegalArgumentException("Given store is not managed by this provider : " + resource);
}
Store.Provider underlyingStoreProvider = createdStoreRef.storeProvider;
SoftLockValueCombinedSerializerLifecycleHelper> helper = createdStoreRef.lifecycleHelper;
if (resource instanceof XAStore) {
XAStore, ?> xaStore = (XAStore, ?>) resource;
xaStore.transactionManagerWrapper.unregisterXAResource(xaStore.uniqueXAResourceId, xaStore.recoveryXaResource);
// release the underlying store first, as it may still need the serializer to flush down to lower tiers
underlyingStoreProvider.releaseStore(xaStore.underlyingStore);
helper.softLockSerializerRef.set(null);
try {
xaStore.journal.close();
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
} else {
underlyingStoreProvider.releaseStore(resource);
}
}
@Override
public void initStore(Store, ?> resource) {
CreatedStoreRef createdStoreRef = createdStores.get(resource);
if (createdStoreRef == null) {
throw new IllegalArgumentException("Given store is not managed by this provider : " + resource);
}
Store.Provider underlyingStoreProvider = createdStoreRef.storeProvider;
SoftLockValueCombinedSerializerLifecycleHelper> helper = createdStoreRef.lifecycleHelper;
if (resource instanceof XAStore) {
XAStore, ?> xaStore = (XAStore, ?>) resource;
underlyingStoreProvider.initStore(xaStore.underlyingStore);
helper.softLockSerializerRef.set(new SoftLockSerializer<>(helper.classLoader));
try {
xaStore.journal.open();
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
xaStore.transactionManagerWrapper.registerXAResource(xaStore.uniqueXAResourceId, xaStore.recoveryXaResource);
} else {
underlyingStoreProvider.initStore(resource);
}
}
@Override
public void start(ServiceProvider serviceProvider) {
this.serviceProvider = serviceProvider;
this.transactionManagerProvider = uncheckedCast(serviceProvider.getService(TransactionManagerProvider.class));
}
@Override
public void stop() {
this.transactionManagerProvider = null;
this.serviceProvider = null;
}
@Override
public int wrapperStoreRank(Collection> serviceConfigs) {
XAStoreConfiguration xaServiceConfiguration = findSingletonAmongst(XAStoreConfiguration.class, serviceConfigs);
if (xaServiceConfiguration == null) {
// An XAStore must be configured for use
return 0;
} else {
if (this.transactionManagerProvider == null) {
throw new IllegalStateException("A TransactionManagerProvider is mandatory to use XA caches");
}
}
return 1;
}
}
private static class XAEvictionAdvisor implements EvictionAdvisor> {
private final EvictionAdvisor super K, ? super V> wrappedEvictionAdvisor;
private XAEvictionAdvisor(EvictionAdvisor super K, ? super V> wrappedEvictionAdvisor) {
this.wrappedEvictionAdvisor = wrappedEvictionAdvisor;
}
@Override
public boolean adviseAgainstEviction(K key, SoftLock softLock) {
return isInDoubt(softLock) || wrappedEvictionAdvisor.adviseAgainstEviction(key, softLock.getOldValue());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy