
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 Show documentation
Show all versions of ehcache Show documentation
Ehcache single jar, containing all modules
/*
* 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());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy