/*
* 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.service.DiskResourceService;
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.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.service.Service;
import org.ehcache.spi.service.ServiceConfiguration;
import org.ehcache.spi.service.ServiceDependencies;
import org.ehcache.transactions.xa.XACacheException;
import org.ehcache.transactions.xa.internal.commands.StoreEvictCommand;
import org.ehcache.transactions.xa.internal.commands.StorePutCommand;
import org.ehcache.transactions.xa.internal.commands.StoreRemoveCommand;
import org.ehcache.transactions.xa.configuration.XAStoreConfiguration;
import org.ehcache.transactions.xa.internal.journal.Journal;
import org.ehcache.transactions.xa.internal.journal.JournalProvider;
import org.ehcache.transactions.xa.txmgr.TransactionManagerWrapper;
import org.ehcache.transactions.xa.txmgr.provider.TransactionManagerProvider;
import org.ehcache.core.collections.ConcurrentWeakIdentityHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terracotta.context.ContextManager;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
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.service.ServiceUtils.findAmongst;
import static org.ehcache.core.spi.service.ServiceUtils.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<K, V> implements Store<K, V> {
private static final Logger LOGGER = LoggerFactory.getLogger(XAStore.class);
private final Class<K> keyType;
private final Class<V> valueType;
private final Store<K, SoftLock<V>> underlyingStore;
private final TransactionManagerWrapper transactionManagerWrapper;
private final Map<Transaction, EhcacheXAResource<K, V>> xaResources = new ConcurrentHashMap<Transaction, EhcacheXAResource<K, V>>();
private final TimeSource timeSource;
private final Journal<K> journal;
private final String uniqueXAResourceId;
private final XATransactionContextFactory<K, V> transactionContextFactory;
private final EhcacheXAResource recoveryXaResource;
private final StoreEventSourceWrapper<K, V> eventSourceWrapper;
public XAStore(Class<K> keyType, Class<V> valueType, Store<K, SoftLock<V>> underlyingStore, TransactionManagerWrapper transactionManagerWrapper,
TimeSource timeSource, Journal<K> 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<K, V>(timeSource);
this.recoveryXaResource = new EhcacheXAResource<K, V>(underlyingStore, journal, transactionContextFactory);
this.eventSourceWrapper = new StoreEventSourceWrapper<K, V>(underlyingStore.getStoreEventSource());
ContextManager.associate(underlyingStore).withParent(this);
}
private static boolean isInDoubt(SoftLock<?> softLock) {
return softLock.getTransactionId() != null;
}
private ValueHolder<SoftLock<V>> getSoftLockValueHolderFromUnderlyingStore(K key) throws StoreAccessException {
return underlyingStore.get(key);
}
private XATransactionContext<K, V> getCurrentContext() {
try {
final Transaction transaction = transactionManagerWrapper.getTransactionManager().getTransaction();
if (transaction == null) {
throw new XACacheException("Cannot access XA cache outside of XA transaction scope");
}
EhcacheXAResource<K, V> xaResource = xaResources.get(transaction);
if (xaResource == null) {
xaResource = new EhcacheXAResource<K, V>(underlyingStore, journal, transactionContextFactory);
transactionManagerWrapper.registerXAResource(uniqueXAResourceId, xaResource);
transactionManagerWrapper.getTransactionManager().getTransaction().enlistResource(xaResource);
xaResources.put(transaction, xaResource);
final EhcacheXAResource<K, V> finalXaResource = xaResource;
transaction.registerSynchronization(new Synchronization() {
@Override
public void beforeCompletion() {
}
@Override
public void afterCompletion(int status) {
transactionManagerWrapper.unregisterXAResource(uniqueXAResourceId, finalXaResource);
xaResources.remove(transaction);
}
});
}
XATransactionContext<K, V> 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<Boolean> REPLACE_EQUALS_TRUE = new NullaryFunction<Boolean>() {
@Override
public Boolean apply() {
return Boolean.TRUE;
}
};
@Override
public ValueHolder<V> get(K key) throws StoreAccessException {
checkKey(key);
XATransactionContext<K, V> currentContext = getCurrentContext();
if (currentContext.removed(key)) {
return null;
}
XAValueHolder<V> newValueHolder = currentContext.newValueHolderOf(key);
if (newValueHolder != null) {
return newValueHolder;
}
ValueHolder<SoftLock<V>> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder == null) {
return null;
}
SoftLock<V> softLock = softLockValueHolder.value();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<V>(softLock.getOldValue()));
return null;
}
return new XAValueHolder<V>(softLockValueHolder, softLock.getOldValue());
}
@Override
public boolean containsKey(K key) throws StoreAccessException {
checkKey(key);
if (getCurrentContext().touched(key)) {
return getCurrentContext().newValueHolderOf(key) != null;
}
ValueHolder<SoftLock<V>> 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<K, V> currentContext = getCurrentContext();
if (currentContext.touched(key)) {
V oldValue = currentContext.oldValueOf(key);
V newValue = currentContext.newValueOf(key);
currentContext.addCommand(key, new StorePutCommand<V>(oldValue, new XAValueHolder<V>(value, timeSource.getTimeMillis())));
return newValue == null ? PutStatus.PUT : PutStatus.UPDATE;
}
ValueHolder<SoftLock<V>> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
PutStatus status = PutStatus.NOOP;
if (softLockValueHolder != null) {
SoftLock<V> softLock = softLockValueHolder.value();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<V>(softLock.getOldValue()));
} else {
if (currentContext.addCommand(key, new StorePutCommand<V>(softLock.getOldValue(), new XAValueHolder<V>(value, timeSource.getTimeMillis())))) {
status = PutStatus.UPDATE;
}
}
} else {
if (currentContext.addCommand(key, new StorePutCommand<V>(null, new XAValueHolder<V>(value, timeSource.getTimeMillis())))) {
status = PutStatus.PUT;
}
}
return status;
}
@Override
public boolean remove(K key) throws StoreAccessException {
checkKey(key);
XATransactionContext<K, V> currentContext = getCurrentContext();
if (currentContext.touched(key)) {
V oldValue = currentContext.oldValueOf(key);
V newValue = currentContext.newValueOf(key);
currentContext.addCommand(key, new StoreRemoveCommand<V>(oldValue));
return newValue != null;
}
ValueHolder<SoftLock<V>> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
boolean status = false;
if (softLockValueHolder != null) {
SoftLock<V> softLock = softLockValueHolder.value();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<V>(softLock.getOldValue()));
} else {
status = currentContext.addCommand(key, new StoreRemoveCommand<V>(softLock.getOldValue()));
}
}
return status;
}
@Override
public ValueHolder<V> putIfAbsent(K key, V value) throws StoreAccessException {
checkKey(key);
checkValue(value);
XATransactionContext<K, V> currentContext = getCurrentContext();
if (currentContext.touched(key)) {
V oldValue = currentContext.oldValueOf(key);
V newValue = currentContext.newValueOf(key);
if (newValue == null) {
currentContext.addCommand(key, new StorePutCommand<V>(oldValue, new XAValueHolder<V>(value, timeSource.getTimeMillis())));
return null;
} else {
return currentContext.newValueHolderOf(key);
}
}
ValueHolder<SoftLock<V>> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder != null) {
SoftLock<V> softLock = softLockValueHolder.value();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<V>(softLock.getOldValue()));
return null;
} else {
return new XAValueHolder<V>(softLockValueHolder, softLock.getOldValue());
}
} else {
currentContext.addCommand(key, new StorePutCommand<V>(null, new XAValueHolder<V>(value, timeSource.getTimeMillis())));
return null;
}
}
@Override
public RemoveStatus remove(K key, V value) throws StoreAccessException {
checkKey(key);
checkValue(value);
XATransactionContext<K, V> 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<V>(oldValue));
return RemoveStatus.REMOVED;
}
}
ValueHolder<SoftLock<V>> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder != null) {
SoftLock<V> softLock = softLockValueHolder.value();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<V>(softLock.getOldValue()));
return RemoveStatus.KEY_MISSING;
} else if (!softLock.getOldValue().equals(value)) {
return RemoveStatus.KEY_PRESENT;
} else {
currentContext.addCommand(key, new StoreRemoveCommand<V>(softLock.getOldValue()));
return RemoveStatus.REMOVED;
}
} else {
return RemoveStatus.KEY_MISSING;
}
}
@Override
public ValueHolder<V> replace(K key, V value) throws StoreAccessException {
checkKey(key);
checkValue(value);
XATransactionContext<K, V> currentContext = getCurrentContext();
if (currentContext.touched(key)) {
V newValue = currentContext.newValueOf(key);
if (newValue == null) {
return null;
} else {
V oldValue = currentContext.oldValueOf(key);
XAValueHolder<V> newValueHolder = currentContext.newValueHolderOf(key);
currentContext.addCommand(key, new StorePutCommand<V>(oldValue, new XAValueHolder<V>(value, timeSource.getTimeMillis())));
return newValueHolder;
}
}
ValueHolder<SoftLock<V>> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder != null) {
SoftLock<V> softLock = softLockValueHolder.value();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<V>(softLock.getOldValue()));
return null;
} else {
V oldValue = softLock.getOldValue();
currentContext.addCommand(key, new StorePutCommand<V>(oldValue, new XAValueHolder<V>(value, timeSource.getTimeMillis())));
return new XAValueHolder<V>(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<K, V> 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<V>(previousValue, new XAValueHolder<V>(newValue, timeSource.getTimeMillis())));
return ReplaceStatus.HIT;
}
}
ValueHolder<SoftLock<V>> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder != null) {
SoftLock<V> softLock = softLockValueHolder.value();
V previousValue = softLock.getOldValue();
if (isInDoubt(softLock)) {
currentContext.addCommand(key, new StoreEvictCommand<V>(previousValue));
return ReplaceStatus.MISS_NOT_PRESENT;
} else if (!previousValue.equals(oldValue)) {
return ReplaceStatus.MISS_PRESENT;
} else {
currentContext.addCommand(key, new StorePutCommand<V>(previousValue, new XAValueHolder<V>(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<K, V> getStoreEventSource() {
return eventSourceWrapper;
}
@Override
public Iterator<Cache.Entry<K, ValueHolder<V>>> iterator() {
XATransactionContext<K, V> currentContext = getCurrentContext();
Map<K, XAValueHolder<V>> valueHolderMap = transactionContextFactory.listPuts(currentContext.getTransactionId());
return new XAIterator(valueHolderMap, underlyingStore.iterator(), currentContext.getTransactionId());
}
class XAIterator implements Iterator<Cache.Entry<K, ValueHolder<V>>> {
private final java.util.Iterator<Map.Entry<K, XAValueHolder<V>>> iterator;
private final Iterator<Cache.Entry<K, ValueHolder<SoftLock<V>>>> underlyingIterator;
private final TransactionId transactionId;
private Cache.Entry<K, ValueHolder<V>> next;
private StoreAccessException prefetchFailure = null;
XAIterator(Map<K, XAValueHolder<V>> valueHolderMap, Iterator<Cache.Entry<K, ValueHolder<SoftLock<V>>>> 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<K, XAValueHolder<V>> entry = iterator.next();
this.next = new Cache.Entry<K, ValueHolder<V>>() {
@Override
public K getKey() {
return entry.getKey();
}
@Override
public ValueHolder<V> getValue() {
return entry.getValue();
}
};
return;
}
while (underlyingIterator.hasNext()) {
final Cache.Entry<K, ValueHolder<SoftLock<V>>> next;
try {
next = underlyingIterator.next();
} catch (StoreAccessException e) {
prefetchFailure = e;
break;
}
if (!transactionContextFactory.isTouched(transactionId, next.getKey())) {
ValueHolder<SoftLock<V>> valueHolder = next.getValue();
SoftLock<V> softLock = valueHolder.value();
final XAValueHolder<V> xaValueHolder;
if (softLock.getTransactionId() == transactionId) {
xaValueHolder = new XAValueHolder<V>(valueHolder, softLock.getNewValueHolder().value());
} else if (isInDoubt(softLock)) {
continue;
} else {
xaValueHolder = new XAValueHolder<V>(valueHolder, softLock.getOldValue());
}
this.next = new Cache.Entry<K, ValueHolder<V>>() {
@Override
public K getKey() {
return next.getKey();
}
@Override
public ValueHolder<V> 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<K, ValueHolder<V>> 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<K, ValueHolder<V>> rc = next;
advance();
return rc;
}
}
@Override
public ValueHolder<V> compute(K key, BiFunction<? super K, ? super V, ? extends V> mappingFunction, NullaryFunction<Boolean> replaceEqual) throws StoreAccessException {
checkKey(key);
XATransactionContext<K, V> currentContext = getCurrentContext();
if (currentContext.touched(key)) {
return updateCommandForKey(key, mappingFunction, replaceEqual, currentContext);
}
ValueHolder<SoftLock<V>> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
SoftLock<V> softLock = softLockValueHolder == null ? null : softLockValueHolder.value();
V oldValue = softLock == null ? null : softLock.getOldValue();
V newValue = mappingFunction.apply(key, oldValue);
XAValueHolder<V> xaValueHolder = newValue == null ? null : new XAValueHolder<V>(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<V>(oldValue));
} else {
if (xaValueHolder == null) {
if (oldValue != null) {
currentContext.addCommand(key, new StoreRemoveCommand<V>(oldValue));
}
} else {
currentContext.addCommand(key, new StorePutCommand<V>(oldValue, xaValueHolder));
}
}
return xaValueHolder;
}
@Override
public ValueHolder<V> compute(K key, BiFunction<? super K, ? super V, ? extends V> mappingFunction) throws StoreAccessException {
return compute(key, mappingFunction, REPLACE_EQUALS_TRUE);
}
@Override
public ValueHolder<V> computeIfAbsent(K key, final Function<? super K, ? extends V> mappingFunction) throws StoreAccessException {
checkKey(key);
XATransactionContext<K, V> currentContext = getCurrentContext();
if (currentContext.removed(key)) {
return updateCommandForKey(key, mappingFunction, currentContext);
}
if (currentContext.evicted(key)) {
return new XAValueHolder<V>(currentContext.oldValueOf(key), timeSource.getTimeMillis());
}
boolean updated = currentContext.touched(key);
XAValueHolder<V> xaValueHolder;
ValueHolder<SoftLock<V>> softLockValueHolder = getSoftLockValueHolderFromUnderlyingStore(key);
if (softLockValueHolder == null) {
if (updated) {
xaValueHolder = currentContext.newValueHolderOf(key);
} else {
V computed = mappingFunction.apply(key);
if (computed != null) {
xaValueHolder = new XAValueHolder<V>(computed, timeSource.getTimeMillis());
currentContext.addCommand(key, new StorePutCommand<V>(null, xaValueHolder));
} else {
xaValueHolder = null;
}
}
} else if (isInDoubt(softLockValueHolder.value())) {
currentContext.addCommand(key, new StoreEvictCommand<V>(softLockValueHolder.value().getOldValue()));
xaValueHolder = new XAValueHolder<V>(softLockValueHolder, softLockValueHolder.value().getNewValueHolder().value());
} else {
if (updated) {
xaValueHolder = currentContext.newValueHolderOf(key);
} else {
xaValueHolder = new XAValueHolder<V>(softLockValueHolder, softLockValueHolder.value().getOldValue());
}
}
return xaValueHolder;
}
private ValueHolder<V> updateCommandForKey(K key, BiFunction<? super K, ? super V, ? extends V> mappingFunction, NullaryFunction<Boolean> replaceEqual, XATransactionContext<K, V> currentContext) {
V newValue = mappingFunction.apply(key, currentContext.newValueOf(key));
XAValueHolder<V> xaValueHolder = null;
V oldValue = currentContext.oldValueOf(key);
if (newValue == null) {
if (!(oldValue == null && !replaceEqual.apply())) {
currentContext.addCommand(key, new StoreRemoveCommand<V>(oldValue));
} else {
currentContext.removeCommand(key);
}
} else {
checkValue(newValue);
xaValueHolder = new XAValueHolder<V>(newValue, timeSource.getTimeMillis());
if (!(eq(oldValue, newValue) && !replaceEqual.apply())) {
currentContext.addCommand(key, new StorePutCommand<V>(oldValue, xaValueHolder));
}
}
return xaValueHolder;
}
private ValueHolder<V> updateCommandForKey(K key, Function<? super K, ? extends V> mappingFunction, XATransactionContext<K, V> currentContext) {
V computed = mappingFunction.apply(key);
XAValueHolder<V> xaValueHolder = null;
if (computed != null) {
checkValue(computed);
xaValueHolder = new XAValueHolder<V>(computed, timeSource.getTimeMillis());
V oldValue = currentContext.oldValueOf(key);
currentContext.addCommand(key, new StorePutCommand<V>(oldValue, xaValueHolder));
} // else do nothing
return xaValueHolder;
}
@Override
public Map<K, ValueHolder<V>> bulkCompute(Set<? extends K> keys, Function<Iterable<? extends Map.Entry<? extends K, ? extends V>>, Iterable<? extends Map.Entry<? extends K, ? extends V>>> remappingFunction) throws StoreAccessException {
return bulkCompute(keys, remappingFunction, REPLACE_EQUALS_TRUE);
}
@Override
public Map<K, ValueHolder<V>> bulkCompute(Set<? extends K> keys, final Function<Iterable<? extends Map.Entry<? extends K, ? extends V>>, Iterable<? extends Map.Entry<? extends K, ? extends V>>> remappingFunction, NullaryFunction<Boolean> replaceEqual) throws StoreAccessException {
Map<K, ValueHolder<V>> result = new HashMap<K, ValueHolder<V>>();
for (K key : keys) {
checkKey(key);
final ValueHolder<V> newValue = compute(key, new BiFunction<K, V, V>() {
@Override
public V apply(final K k, final V oldValue) {
final Set<Map.Entry<K, V>> 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<K, ValueHolder<V>> bulkComputeIfAbsent(Set<? extends K> keys, final Function<Iterable<? extends K>, Iterable<? extends Map.Entry<? extends K, ? extends V>>> mappingFunction) throws StoreAccessException {
Map<K, ValueHolder<V>> result = new HashMap<K, ValueHolder<V>>();
for (final K key : keys) {
final ValueHolder<V> newValue = computeIfAbsent(key, new Function<K, V>() {
@Override
public V apply(final K k) {
final Iterable<K> 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<CacheConfigurationChangeListener> getConfigurationChangeListeners() {
return underlyingStore.getConfigurationChangeListeners();
}
private static final class SoftLockValueCombinedSerializerLifecycleHelper<T> {
final AtomicReference<SoftLockSerializer<T>> softLockSerializerRef;
final ClassLoader classLoader;
SoftLockValueCombinedSerializerLifecycleHelper(AtomicReference<SoftLockSerializer<T>> softLockSerializerRef, ClassLoader classLoader) {
this.softLockSerializerRef = softLockSerializerRef;
this.classLoader = classLoader;
}
}
private static final class CreatedStoreRef {
final Store.Provider storeProvider;
final SoftLockValueCombinedSerializerLifecycleHelper lifecycleHelper;
public CreatedStoreRef(final Store.Provider storeProvider, final SoftLockValueCombinedSerializerLifecycleHelper lifecycleHelper) {
this.storeProvider = storeProvider;
this.lifecycleHelper = lifecycleHelper;
}
}
@ServiceDependencies({TimeSourceService.class, JournalProvider.class, CopyProvider.class, TransactionManagerProvider.class})
public static class Provider implements Store.Provider {
private volatile ServiceProvider<Service> serviceProvider;
private volatile TransactionManagerProvider transactionManagerProvider;
private final Map<Store<?, ?>, CreatedStoreRef> createdStores = new ConcurrentWeakIdentityHashMap<Store<?, ?>, CreatedStoreRef>();
@Override
public int rank(final Set<ResourceType<?>> resourceTypes, final Collection<ServiceConfiguration<?>> 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 <K, V> Store<K, V> createStore(Configuration<K, V> storeConfig, ServiceConfiguration<?>... serviceConfigs) {
Set<ResourceType.Core> supportedTypes = EnumSet.allOf(ResourceType.Core.class);
Set<ResourceType<?>> configuredTypes = storeConfig.getResourcePools().getResourceTypeSet();
for (ResourceType<?> type: configuredTypes) {
if (!supportedTypes.contains(type)) {
throw new IllegalStateException("Unsupported resource type : " + type.getResourcePoolClass());
}
}
XAStoreConfiguration xaServiceConfiguration = findSingletonAmongst(XAStoreConfiguration.class, (Object[]) serviceConfigs);
if (xaServiceConfiguration == null) {
throw new IllegalStateException("XAStore.Provider.createStore called without XAStoreConfiguration");
}
final Store.Provider underlyingStoreProvider =
selectProvider(configuredTypes, Arrays.asList(serviceConfigs), xaServiceConfiguration);
String uniqueXAResourceId = xaServiceConfiguration.getUniqueXAResourceId();
List<ServiceConfiguration<?>> underlyingServiceConfigs = new ArrayList<ServiceConfiguration<?>>();
underlyingServiceConfigs.addAll(Arrays.asList(serviceConfigs));
// eviction advisor
EvictionAdvisor<? super K, ? super V> realEvictionAdvisor = storeConfig.getEvictionAdvisor();
EvictionAdvisor<? super K, ? super SoftLock<V>> evictionAdvisor;
if (realEvictionAdvisor == null) {
evictionAdvisor = null;
} else {
evictionAdvisor = new XAEvictionAdvisor<K, V>(realEvictionAdvisor);
}
// expiry
final Expiry<? super K, ? super V> configuredExpiry = storeConfig.getExpiry();
Expiry<? super K, ? super SoftLock<V>> expiry = new Expiry<K, SoftLock<V>>() {
@Override
public Duration getExpiryForCreation(K key, SoftLock<V> 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<V>> 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<V>> oldSoftLockSupplier, SoftLock<V> newSoftLock) {
SoftLock<V> 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
DiskResourceService.PersistenceSpaceIdentifier persistenceSpaceId = findSingletonAmongst(DiskResourceService.PersistenceSpaceIdentifier.class, (Object[]) serviceConfigs);
// find the copiers
Collection<DefaultCopierConfiguration> 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<K>(SerializingCopier.<K>asCopierClass(), DefaultCopierConfiguration.Type.KEY));
} else {
underlyingServiceConfigs.add(keyCopierConfig);
}
// force-in a value copier if none is configured, or wrap the configured one in a soft lock copier
if (valueCopierConfig == null) {
underlyingServiceConfigs.add(new DefaultCopierConfiguration<V>(SerializingCopier.<V>asCopierClass(), DefaultCopierConfiguration.Type.VALUE));
} else {
CopyProvider copyProvider = serviceProvider.getService(CopyProvider.class);
Copier<V> valueCopier = copyProvider.createValueCopier(storeConfig.getValueType(), storeConfig.getValueSerializer(), valueCopierConfig);
Copier<SoftLock<V>> softLockValueCombinedCopier = new SoftLockValueCombinedCopier<V>(valueCopier);
underlyingServiceConfigs.add(new DefaultCopierConfiguration<SoftLock<V>>(softLockValueCombinedCopier, DefaultCopierConfiguration.Type.VALUE));
}
// lookup the required XAStore services
Journal<K> journal = serviceProvider.getService(JournalProvider.class).getJournal(persistenceSpaceId, storeConfig.getKeySerializer());
TimeSource timeSource = serviceProvider.getService(TimeSourceService.class).getTimeSource();
// create the soft lock serializer
AtomicReference<SoftLockSerializer<V>> softLockSerializerRef = new AtomicReference<SoftLockSerializer<V>>();
SoftLockValueCombinedSerializer<V> softLockValueCombinedSerializer = new SoftLockValueCombinedSerializer<V>(softLockSerializerRef, storeConfig.getValueSerializer());
// create the underlying store
@SuppressWarnings("unchecked")
Class<SoftLock<V>> softLockClass = (Class) SoftLock.class;
Store.Configuration<K, SoftLock<V>> underlyingStoreConfig = new StoreConfigurationImpl<K, SoftLock<V>>(storeConfig.getKeyType(), softLockClass, evictionAdvisor,
storeConfig.getClassLoader(), expiry, storeConfig.getResourcePools(), storeConfig.getDispatcherConcurrency(), storeConfig.getKeySerializer(), softLockValueCombinedSerializer);
Store<K, SoftLock<V>> underlyingStore = underlyingStoreProvider.createStore(underlyingStoreConfig, underlyingServiceConfigs.toArray(new ServiceConfiguration[0]));
// create the XA store
TransactionManagerWrapper transactionManagerWrapper = transactionManagerProvider.getTransactionManagerWrapper();
Store<K, V> store = new XAStore<K, V>(storeConfig.getKeyType(), storeConfig.getValueType(), underlyingStore,
transactionManagerWrapper, timeSource, journal, uniqueXAResourceId);
// create the softLockSerializer lifecycle helper
SoftLockValueCombinedSerializerLifecycleHelper<V> helper =
new SoftLockValueCombinedSerializerLifecycleHelper<V>(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
@SuppressWarnings("unchecked")
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<Service> 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<ResourceType<?>> resourceTypes,
final Collection<ServiceConfiguration<?>> serviceConfigs,
final XAStoreConfiguration xaConfig) {
List<ServiceConfiguration<?>> configsWithoutXA = new ArrayList<ServiceConfiguration<?>>(serviceConfigs);
configsWithoutXA.remove(xaConfig);
return StoreSupport.selectStoreProvider(serviceProvider, resourceTypes, configsWithoutXA);
}
}
private static class XAEvictionAdvisor<K, V> implements EvictionAdvisor<K, SoftLock<V>> {
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<V> softLock) {
return isInDoubt(softLock) || wrappedEvictionAdvisor.adviseAgainstEviction(key, softLock.getOldValue());
}
}
}