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
/*
* 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.ValueSupplier;
import org.ehcache.config.ResourceType;
import org.ehcache.core.CacheConfigurationChangeListener;
import org.ehcache.config.EvictionAdvisor;
import org.ehcache.core.internal.store.StoreConfigurationImpl;
import org.ehcache.core.internal.store.StoreSupport;
import org.ehcache.core.spi.store.StoreAccessException;
import org.ehcache.impl.config.copy.DefaultCopierConfiguration;
import org.ehcache.expiry.Duration;
import org.ehcache.expiry.Expiry;
import org.ehcache.core.spi.function.BiFunction;
import org.ehcache.core.spi.function.Function;
import org.ehcache.core.spi.function.NullaryFunction;
import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import org.ehcache.impl.copy.SerializingCopier;
import org.ehcache.core.spi.time.TimeSource;
import org.ehcache.core.spi.time.TimeSourceService;
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.serialization.Serializer;
import org.ehcache.core.spi.service.LocalPersistenceService;
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.internal.util.ConcurrentWeakIdentityHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import javax.transaction.RollbackException;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import static org.ehcache.core.internal.service.ServiceLocator.findAmongst;
import static org.ehcache.core.internal.service.ServiceLocator.findSingletonAmongst;
import static org.ehcache.core.internal.util.ValueSuppliers.supplierOf;
/**
* 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 implements Store {
private static final Logger LOGGER = LoggerFactory.getLogger(XAStore.class);
private final Class keyType;
private final Class valueType;
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) {
this.keyType = keyType;
this.valueType = valueType;
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);
}
}
private static boolean eq(Object o1, Object o2) {
return (o1 == o2) || (o1 != null && o1.equals(o2));
}
private void checkKey(K keyObject) {
if (keyObject == null) {
throw new NullPointerException();
}
if (!keyType.isAssignableFrom(keyObject.getClass())) {
throw new ClassCastException("Invalid key type, expected : " + keyType.getName() + " but was : " + keyObject.getClass().getName());
}
}
private void checkValue(V valueObject) {
if (valueObject == null) {
throw new NullPointerException();
}
if (!valueType.isAssignableFrom(valueObject.getClass())) {
throw new ClassCastException("Invalid value type, expected : " + valueType.getName() + " but was : " + valueObject.getClass().getName());
}
}
private static final NullaryFunction REPLACE_EQUALS_TRUE = new NullaryFunction() {
@Override
public Boolean apply() {
return Boolean.TRUE;
}
};
@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.value();
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.value().getTransactionId() == null && softLockValueHolder.value().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);
V newValue = currentContext.newValueOf(key);
currentContext.addCommand(key, new StorePutCommand(oldValue, new XAValueHolder(value, timeSource.getTimeMillis())));
return newValue == null ? PutStatus.PUT : PutStatus.UPDATE;
}
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
PutStatus status = PutStatus.NOOP;
if (softLockValueHolder != null) {
SoftLock softLock = softLockValueHolder.value();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand(softLock.getOldValue()));
} else {
if (currentContext.addCommand(key, new StorePutCommand(softLock.getOldValue(), new XAValueHolder(value, timeSource.getTimeMillis())))) {
status = PutStatus.UPDATE;
}
}
} else {
if (currentContext.addCommand(key, new StorePutCommand(null, new XAValueHolder(value, timeSource.getTimeMillis())))) {
status = PutStatus.PUT;
}
}
return status;
}
@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.value();
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) 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.value();
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.value();
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.value();
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(XAValueHolder.NATIVE_TIME_UNIT));
}
} 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.value();
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());
}
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.value();
final XAValueHolder xaValueHolder;
if (softLock.getTransactionId() == transactionId) {
xaValueHolder = new XAValueHolder(valueHolder, softLock.getNewValueHolder().value());
} 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 compute(K key, BiFunction super K, ? super V, ? extends V> mappingFunction, NullaryFunction replaceEqual) 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.value();
V oldValue = softLock == null ? null : softLock.getOldValue();
V newValue = mappingFunction.apply(key, oldValue);
XAValueHolder xaValueHolder = newValue == null ? null : new XAValueHolder(newValue, timeSource.getTimeMillis());
if (eq(oldValue, newValue) && !replaceEqual.apply()) {
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 compute(K key, BiFunction super K, ? super V, ? extends V> mappingFunction) throws StoreAccessException {
return compute(key, mappingFunction, REPLACE_EQUALS_TRUE);
}
@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.value())) {
currentContext.addCommand(key, new StoreEvictCommand(softLockValueHolder.value().getOldValue()));
xaValueHolder = new XAValueHolder(softLockValueHolder, softLockValueHolder.value().getNewValueHolder().value());
} else {
if (updated) {
xaValueHolder = currentContext.newValueHolderOf(key);
} else {
xaValueHolder = new XAValueHolder(softLockValueHolder, softLockValueHolder.value().getOldValue());
}
}
return xaValueHolder;
}
private ValueHolder updateCommandForKey(K key, BiFunction super K, ? super V, ? extends V> mappingFunction, NullaryFunction 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.apply())) {
currentContext.addCommand(key, new StoreRemoveCommand(oldValue));
} else {
currentContext.removeCommand(key);
}
} else {
checkValue(newValue);
xaValueHolder = new XAValueHolder(newValue, timeSource.getTimeMillis());
if (!(eq(oldValue, newValue) && !replaceEqual.apply())) {
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, NullaryFunction replaceEqual) throws StoreAccessException {
Map> result = new HashMap>();
for (K key : keys) {
checkKey(key);
final ValueHolder newValue = compute(key, new BiFunction() {
@Override
public V apply(final K k, final V 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 key = next.getKey();
V value = next.getValue();
checkKey(key);
if (value != null) {
checkValue(value);
}
return value;
}
}, replaceEqual);
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, new Function() {
@Override
public V apply(final K k) {
final Iterable keySet = Collections.singleton(k);
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})
public static class Provider implements Store.Provider {
private volatile ServiceProvider serviceProvider;
private volatile TransactionManagerProvider transactionManagerProvider;
private final Map, CreatedStoreRef> createdStores = new ConcurrentWeakIdentityHashMap, CreatedStoreRef>();
@Override
public int rank(final Set> resourceTypes, final Collection> serviceConfigs) {
final 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");
}
}
final Store.Provider candidateUnderlyingProvider = selectProvider(resourceTypes, serviceConfigs, xaServiceConfiguration);
return 1000 + candidateUnderlyingProvider.rank(resourceTypes, serviceConfigs);
}
@Override
public Store createStore(Configuration storeConfig, ServiceConfiguration>... serviceConfigs) {
XAStoreConfiguration xaServiceConfiguration = findSingletonAmongst(XAStoreConfiguration.class, (Object[]) serviceConfigs);
if (xaServiceConfiguration == null) {
throw new IllegalStateException("XAStore.Provider.createStore called without XAStoreConfiguration");
}
final Store.Provider underlyingStoreProvider =
selectProvider(storeConfig.getResourcePools().getResourceTypeSet(), Arrays.asList(serviceConfigs), xaServiceConfiguration);
String uniqueXAResourceId = xaServiceConfiguration.getUniqueXAResourceId();
List> underlyingServiceConfigs = new ArrayList>();
underlyingServiceConfigs.addAll(Arrays.asList(serviceConfigs));
// 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 Expiry super K, ? super V> configuredExpiry = storeConfig.getExpiry();
Expiry super K, ? super SoftLock> expiry = new Expiry>() {
@Override
public Duration getExpiryForCreation(K key, SoftLock softLock) {
if (softLock.getTransactionId() != null) {
// phase 1 prepare, create -> forever
return Duration.INFINITE;
} else {
// phase 2 commit, or during a TX's lifetime, create -> some time
Duration duration;
try {
duration = configuredExpiry.getExpiryForCreation(key, (V) 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, final ValueSupplier extends SoftLock> softLock) {
if (softLock.value().getTransactionId() != null) {
// phase 1 prepare, access -> forever
return Duration.INFINITE;
} else {
// phase 2 commit, or during a TX's lifetime, access -> some time
Duration duration;
try {
duration = configuredExpiry.getExpiryForAccess(key, supplierOf(softLock.value().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, ValueSupplier extends SoftLock> oldSoftLockSupplier, SoftLock newSoftLock) {
SoftLock oldSoftLock = oldSoftLockSupplier.value();
if (oldSoftLock.getTransactionId() == null) {
// phase 1 prepare, update -> forever
return Duration.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().value();
Duration duration;
try {
duration = configuredExpiry.getExpiryForUpdate(key, supplierOf(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
LocalPersistenceService.PersistenceSpaceIdentifier persistenceSpaceId = findSingletonAmongst(LocalPersistenceService.PersistenceSpaceIdentifier.class, serviceConfigs);
// find the copiers
Collection copierConfigs = findAmongst(DefaultCopierConfiguration.class, underlyingServiceConfigs);
DefaultCopierConfiguration keyCopierConfig = null;
DefaultCopierConfiguration valueCopierConfig = null;
for (DefaultCopierConfiguration copierConfig : copierConfigs) {
if (copierConfig.getType().equals(DefaultCopierConfiguration.Type.KEY)) {
keyCopierConfig = copierConfig;
} else if (copierConfig.getType().equals(DefaultCopierConfiguration.Type.VALUE)) {
valueCopierConfig = copierConfig;
}
underlyingServiceConfigs.remove(copierConfig);
}
// force-in a key copier if none is configured
if (keyCopierConfig == null) {
underlyingServiceConfigs.add(new DefaultCopierConfiguration((Class) SerializingCopier.class, 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((Class) SerializingCopier.class, DefaultCopierConfiguration.Type.VALUE));
} else {
CopyProvider copyProvider = serviceProvider.getService(CopyProvider.class);
Copier valueCopier = copyProvider.createValueCopier(storeConfig.getValueType(), storeConfig.getValueSerializer(), valueCopierConfig);
SoftLockValueCombinedCopier softLockValueCombinedCopier = new SoftLockValueCombinedCopier(valueCopier);
underlyingServiceConfigs.add(new DefaultCopierConfiguration((Copier) 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 = new SoftLockValueCombinedSerializer(softLockSerializerRef, storeConfig.getValueSerializer());
// create the underlying store
Store.Configuration> underlyingStoreConfig = new StoreConfigurationImpl>(storeConfig.getKeyType(), (Class) SoftLock.class, evictionAdvisor,
storeConfig.getClassLoader(), expiry, storeConfig.getResourcePools(), storeConfig.getDispatcherConcurrency(), storeConfig.getKeySerializer(), softLockValueCombinedSerializer);
Store> underlyingStore = (Store) underlyingStoreProvider.createStore(underlyingStoreConfig, underlyingServiceConfigs.toArray(new ServiceConfiguration[0]));
// create the XA store
TransactionManagerWrapper transactionManagerWrapper = transactionManagerProvider.getTransactionManagerWrapper();
Store store = new XAStore(storeConfig.getKeyType(), storeConfig.getValueType(), underlyingStore,
transactionManagerWrapper, timeSource, journal, uniqueXAResourceId);
// create the softLockSerializer lifecycle helper
SoftLockValueCombinedSerializerLifecycleHelper helper =
new SoftLockValueCombinedSerializerLifecycleHelper((AtomicReference)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 = serviceProvider.getService(TransactionManagerProvider.class);
}
@Override
public void stop() {
this.transactionManagerProvider = null;
this.serviceProvider = null;
}
private Store.Provider selectProvider(final Set> resourceTypes,
final Collection> serviceConfigs,
final XAStoreConfiguration xaConfig) {
List> configsWithoutXA = new ArrayList>(serviceConfigs);
configsWithoutXA.remove(xaConfig);
return StoreSupport.selectStoreProvider(serviceProvider, resourceTypes, configsWithoutXA);
}
}
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());
}
}
}