
org.ehcache.transactions.xa.XAStore Maven / Gradle / Ivy
/*
* 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;
import org.ehcache.Cache;
import org.ehcache.core.CacheConfigurationChangeListener;
import org.ehcache.config.EvictionVeto;
import org.ehcache.core.config.store.StoreConfigurationImpl;
import org.ehcache.core.config.copy.CopierConfiguration;
import org.ehcache.impl.config.copy.DefaultCopierConfiguration;
import org.ehcache.exceptions.CacheAccessException;
import org.ehcache.expiry.Duration;
import org.ehcache.expiry.Expiry;
import org.ehcache.function.BiFunction;
import org.ehcache.function.Function;
import org.ehcache.function.NullaryFunction;
import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import org.ehcache.impl.copy.SerializingCopier;
import org.ehcache.impl.internal.store.DefaultStoreProvider;
import org.ehcache.core.spi.time.TimeSource;
import org.ehcache.core.spi.time.TimeSourceService;
import org.ehcache.spi.ServiceProvider;
import org.ehcache.core.spi.cache.Store;
import org.ehcache.core.spi.cache.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.ServiceConfiguration;
import org.ehcache.spi.service.ServiceDependencies;
import org.ehcache.transactions.xa.commands.StoreEvictCommand;
import org.ehcache.transactions.xa.commands.StorePutCommand;
import org.ehcache.transactions.xa.commands.StoreRemoveCommand;
import org.ehcache.transactions.xa.configuration.XAStoreConfiguration;
import org.ehcache.transactions.xa.journal.Journal;
import org.ehcache.transactions.xa.journal.JournalProvider;
import org.ehcache.transactions.xa.txmgr.TransactionManagerWrapper;
import org.ehcache.transactions.xa.txmgr.provider.TransactionManagerProvider;
import org.ehcache.core.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.spi.ServiceLocator.findAmongst;
import static org.ehcache.core.spi.ServiceLocator.findSingletonAmongst;
/**
* A {@link Store} implementation wrapping another {@link Store} driven by a JTA
* {@link javax.transaction.TransactionManager} using the XA 2-phase commit protocol.
*
* @author Ludovic Orban
*/
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 boolean isInDoubt(SoftLock softLock) {
return softLock.getTransactionId() != null;
}
private ValueHolder> getSoftLockValueHolderFromUnderlyingStore(K key) throws CacheAccessException {
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 CacheAccessException {
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 CacheAccessException {
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 void put(K key, V value) throws CacheAccessException {
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;
}
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder != null) {
SoftLock softLock = softLockValueHolder.value();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand(softLock.getOldValue()));
} else {
currentContext.addCommand(key, new StorePutCommand(softLock.getOldValue(), new XAValueHolder(value, timeSource.getTimeMillis())));
}
} else {
currentContext.addCommand(key, new StorePutCommand(null, new XAValueHolder(value, timeSource.getTimeMillis())));
}
}
@Override
public ValueHolder putIfAbsent(K key, V value) throws CacheAccessException {
throw new UnsupportedOperationException();
}
@Override
public void remove(K key) throws CacheAccessException {
checkKey(key);
XATransactionContext currentContext = getCurrentContext();
if (currentContext.touched(key)) {
V oldValue = currentContext.oldValueOf(key);
currentContext.addCommand(key, new StoreRemoveCommand(oldValue));
return;
}
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder != null) {
SoftLock softLock = softLockValueHolder.value();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand(softLock.getOldValue()));
} else {
currentContext.addCommand(key, new StoreRemoveCommand(softLock.getOldValue()));
}
}
}
@Override
public boolean remove(K key, V value) throws CacheAccessException {
throw new UnsupportedOperationException();
}
@Override
public ValueHolder replace(K key, V value) throws CacheAccessException {
throw new UnsupportedOperationException();
}
@Override
public boolean replace(K key, V oldValue, V newValue) throws CacheAccessException {
throw new UnsupportedOperationException();
}
@Override
public void clear() throws CacheAccessException {
// 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 CacheAccessException 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 (CacheAccessException 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 CacheAccessException {
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 CacheAccessException {
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 CacheAccessException {
return compute(key, mappingFunction, REPLACE_EQUALS_TRUE);
}
@Override
public ValueHolder computeIfAbsent(K key, final Function super K, ? extends V> mappingFunction) throws CacheAccessException {
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 = null;
} 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 {
xaValueHolder = new XAValueHolder(softLockValueHolder, softLockValueHolder.value().getOldValue());
}
return xaValueHolder;
}
@Override
public ValueHolder computeIfPresent(K key, BiFunction super K, ? super V, ? extends V> remappingFunction) throws CacheAccessException {
return computeIfPresent(key, remappingFunction, REPLACE_EQUALS_TRUE);
}
@Override
public ValueHolder computeIfPresent(K key, BiFunction super K, ? super V, ? extends V> remappingFunction, NullaryFunction replaceEqual) throws CacheAccessException {
checkKey(key);
XATransactionContext currentContext = getCurrentContext();
if (currentContext.updated(key)) {
return updateCommandForKey(key, remappingFunction, replaceEqual, currentContext);
}
if (currentContext.evicted(key)) {
return null;
}
boolean removed = currentContext.touched(key);
ValueHolder> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
XAValueHolder xaValueHolder;
SoftLock softLock = softLockValueHolder == null ? null : softLockValueHolder.value();
V oldValue = softLock == null ? null : softLock.getOldValue();
if (softLock != null && isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand(oldValue));
xaValueHolder = null;
} else if (softLock == null) {
xaValueHolder = null;
} else if (removed) {
xaValueHolder = null;
} else {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
checkValue(newValue);
xaValueHolder = new XAValueHolder(newValue, timeSource.getTimeMillis());
} else {
xaValueHolder = null;
}
currentContext.addCommand(key, new StorePutCommand(oldValue, xaValueHolder));
}
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 {
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 CacheAccessException {
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 CacheAccessException {
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 CacheAccessException {
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;
}
}
@ServiceDependencies({TransactionManagerProvider.class, TimeSourceService.class, JournalProvider.class, CopyProvider.class, DefaultStoreProvider.class})
public static class Provider implements Store.Provider {
private volatile ServiceProvider serviceProvider;
private volatile Store.Provider underlyingStoreProvider;
private volatile TransactionManagerProvider transactionManagerProvider;
private final Map, SoftLockValueCombinedSerializerLifecycleHelper> createdStores = new ConcurrentWeakIdentityHashMap, SoftLockValueCombinedSerializerLifecycleHelper>();
@Override
public Store createStore(Configuration storeConfig, ServiceConfiguration>... serviceConfigs) {
XAStoreConfiguration xaServiceConfiguration = findSingletonAmongst(XAStoreConfiguration.class, (Object[]) serviceConfigs);
Store store;
SoftLockValueCombinedSerializerLifecycleHelper helper;
if (xaServiceConfiguration == null) {
// non-tx cache
store = underlyingStoreProvider.createStore(storeConfig, serviceConfigs);
helper = new SoftLockValueCombinedSerializerLifecycleHelper(null, null);
} else {
String uniqueXAResourceId = xaServiceConfiguration.getUniqueXAResourceId();
List> underlyingServiceConfigs = new ArrayList>();
underlyingServiceConfigs.addAll(Arrays.asList(serviceConfigs));
// TODO: do we want to support pluggable veto?
// eviction veto
EvictionVeto super K, ? super SoftLock> evictionVeto = new EvictionVeto() {
@Override
public boolean vetoes(K key, SoftLock lock) {
return lock.getTransactionId() != null;
}
};
// 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.FOREVER;
} 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, SoftLock softLock) {
if (softLock.getTransactionId() != null) {
// phase 1 prepare, access -> forever
return Duration.FOREVER;
} else {
// phase 2 commit, or during a TX's lifetime, access -> some time
Duration duration;
try {
duration = configuredExpiry.getExpiryForAccess(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 getExpiryForUpdate(K key, SoftLock oldSoftLock, SoftLock newSoftLock) {
if (oldSoftLock.getTransactionId() == null) {
// phase 1 prepare, update -> forever
return Duration.FOREVER;
} 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, (V) 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 : (V) oldSoftLock.getNewValueHolder().value();
Duration duration;
try {
duration = configuredExpiry.getExpiryForUpdate(key, (V) 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(CopierConfiguration.Type.KEY)) {
keyCopierConfig = copierConfig;
} else if (copierConfig.getType().equals(CopierConfiguration.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, CopierConfiguration.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, CopierConfiguration.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, CopierConfiguration.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(), SoftLock.class, evictionVeto,
storeConfig.getClassLoader(), expiry, storeConfig.getResourcePools(), storeConfig.getOrderedEventParallelism(), storeConfig.getKeySerializer(), softLockValueCombinedSerializer);
Store> underlyingStore = (Store) underlyingStoreProvider.createStore(underlyingStoreConfig, underlyingServiceConfigs.toArray(new ServiceConfiguration[0]));
// create the XA store
TransactionManagerWrapper transactionManagerWrapper = transactionManagerProvider.getTransactionManagerWrapper();
store = new XAStore(storeConfig.getKeyType(), storeConfig.getValueType(), underlyingStore, transactionManagerWrapper, timeSource, journal, uniqueXAResourceId);
// create the softLockSerializer lifecycle helper
helper = new SoftLockValueCombinedSerializerLifecycleHelper((AtomicReference) softLockSerializerRef, storeConfig.getClassLoader());
}
createdStores.put(store, helper);
return store;
}
@Override
public void releaseStore(Store, ?> resource) {
SoftLockValueCombinedSerializerLifecycleHelper helper = createdStores.remove(resource);
if (helper == null) {
throw new IllegalArgumentException("Given store is not managed by this provider : " + resource);
}
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) {
SoftLockValueCombinedSerializerLifecycleHelper helper = createdStores.get(resource);
if (helper == null) {
throw new IllegalArgumentException("Given store is not managed by this provider : " + resource);
}
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.underlyingStoreProvider = serviceProvider.getService(DefaultStoreProvider.class);
this.transactionManagerProvider = serviceProvider.getService(TransactionManagerProvider.class);
}
@Override
public void stop() {
this.transactionManagerProvider = null;
this.underlyingStoreProvider = null;
this.serviceProvider = null;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy