All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ehcache.transactions.xa.internal.XAStore Maven / Gradle / Ivy

There is a newer version: 3.10.8
Show newest version
/*
 * Copyright Terracotta, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.ehcache.transactions.xa.internal;

import org.ehcache.Cache;
import org.ehcache.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.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.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 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.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());

    ContextManager.associate(underlyingStore).withParent(this);
  }

  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 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 mappingFunction) throws StoreAccessException {
    return compute(key, mappingFunction, REPLACE_EQUALS_TRUE);
  }

  @Override
  public ValueHolder computeIfAbsent(K key, final Function 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 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 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 keys, Function>, Iterable>> remappingFunction) throws StoreAccessException {
    return bulkCompute(keys, remappingFunction, REPLACE_EQUALS_TRUE);
  }

  @Override
  public Map> bulkCompute(Set keys, final Function>, Iterable>> 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> entries = remappingFunction.apply(entrySet);
          final java.util.Iterator> iterator = entries.iterator();
          final Map.Entry 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 keys, final Function, Iterable>> 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> entries = mappingFunction.apply(keySet);
          final java.util.Iterator> iterator = entries.iterator();
          final Map.Entry next = iterator.next();

          K computedKey = next.getKey();
          V computedValue = next.getValue();
          checkKey(computedKey);
          if (computedValue == null) {
            return null;
          }

          checkValue(computedValue);
          return computedValue;
        }
      });
      result.put(key, newValue);
    }
    return result;
  }

  @Override
  public List getConfigurationChangeListeners() {
    return underlyingStore.getConfigurationChangeListeners();
  }

  private static final class SoftLockValueCombinedSerializerLifecycleHelper {
    final AtomicReference> softLockSerializerRef;
    final ClassLoader classLoader;

    SoftLockValueCombinedSerializerLifecycleHelper(AtomicReference> softLockSerializerRef, ClassLoader classLoader) {
      this.softLockSerializerRef = softLockSerializerRef;
      this.classLoader = classLoader;
    }
  }

  private static final class CreatedStoreRef {
    final Store.Provider storeProvider;
    final SoftLockValueCombinedSerializerLifecycleHelper lifecycleHelper;

    public CreatedStoreRef(final Store.Provider storeProvider, final SoftLockValueCombinedSerializerLifecycleHelper lifecycleHelper) {
      this.storeProvider = storeProvider;
      this.lifecycleHelper = lifecycleHelper;
    }
  }

  @ServiceDependencies({TimeSourceService.class, JournalProvider.class, CopyProvider.class, TransactionManagerProvider.class})
  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) {
      Set supportedTypes = EnumSet.allOf(ResourceType.Core.class);

      Set> configuredTypes = storeConfig.getResourcePools().getResourceTypeSet();

      for (ResourceType type: configuredTypes) {
        if (!supportedTypes.contains(type)) {
          throw new IllegalStateException("Unsupported resource type : " + type.getResourcePoolClass());
        }
      }

      XAStoreConfiguration xaServiceConfiguration = findSingletonAmongst(XAStoreConfiguration.class, (Object[]) serviceConfigs);
      if (xaServiceConfiguration == null) {
        throw new IllegalStateException("XAStore.Provider.createStore called without XAStoreConfiguration");
      }

      final Store.Provider underlyingStoreProvider =
          selectProvider(configuredTypes, Arrays.asList(serviceConfigs), xaServiceConfiguration);

      String uniqueXAResourceId = xaServiceConfiguration.getUniqueXAResourceId();
      List> underlyingServiceConfigs = new ArrayList>();
      underlyingServiceConfigs.addAll(Arrays.asList(serviceConfigs));

      // eviction advisor
      EvictionAdvisor realEvictionAdvisor = storeConfig.getEvictionAdvisor();
      EvictionAdvisor> evictionAdvisor;
      if (realEvictionAdvisor == null) {
        evictionAdvisor = null;
      } else {
        evictionAdvisor = new XAEvictionAdvisor(realEvictionAdvisor);
      }

      // expiry
      final Expiry configuredExpiry = storeConfig.getExpiry();
      Expiry> 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> 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> 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
      DiskResourceService.PersistenceSpaceIdentifier persistenceSpaceId = findSingletonAmongst(DiskResourceService.PersistenceSpaceIdentifier.class, (Object[]) 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(SerializingCopier.asCopierClass(), DefaultCopierConfiguration.Type.KEY));
      } else {
        underlyingServiceConfigs.add(keyCopierConfig);
      }

      // force-in a value copier if none is configured, or wrap the configured one in a soft lock copier
      if (valueCopierConfig == null) {
        underlyingServiceConfigs.add(new DefaultCopierConfiguration(SerializingCopier.asCopierClass(), DefaultCopierConfiguration.Type.VALUE));
      } else {
        CopyProvider copyProvider = serviceProvider.getService(CopyProvider.class);
        Copier valueCopier = copyProvider.createValueCopier(storeConfig.getValueType(), storeConfig.getValueSerializer(), valueCopierConfig);
        Copier> softLockValueCombinedCopier = new SoftLockValueCombinedCopier(valueCopier);
        underlyingServiceConfigs.add(new DefaultCopierConfiguration>(softLockValueCombinedCopier, DefaultCopierConfiguration.Type.VALUE));
      }

      // lookup the required XAStore services
      Journal journal = serviceProvider.getService(JournalProvider.class).getJournal(persistenceSpaceId, storeConfig.getKeySerializer());
      TimeSource timeSource = serviceProvider.getService(TimeSourceService.class).getTimeSource();

      // create the soft lock serializer
      AtomicReference> softLockSerializerRef = new AtomicReference>();
      SoftLockValueCombinedSerializer softLockValueCombinedSerializer = new SoftLockValueCombinedSerializer(softLockSerializerRef, storeConfig.getValueSerializer());

      // create the underlying store
      @SuppressWarnings("unchecked")
      Class> softLockClass = (Class) SoftLock.class;
      Store.Configuration> underlyingStoreConfig = new StoreConfigurationImpl>(storeConfig.getKeyType(), softLockClass, evictionAdvisor,
          storeConfig.getClassLoader(), expiry, storeConfig.getResourcePools(), storeConfig.getDispatcherConcurrency(), storeConfig.getKeySerializer(), softLockValueCombinedSerializer);
      Store> underlyingStore = underlyingStoreProvider.createStore(underlyingStoreConfig,  underlyingServiceConfigs.toArray(new ServiceConfiguration[0]));

      // create the XA store
      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(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 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 wrappedEvictionAdvisor;

    private XAEvictionAdvisor(EvictionAdvisor wrappedEvictionAdvisor) {
      this.wrappedEvictionAdvisor = wrappedEvictionAdvisor;
    }

    @Override
    public boolean adviseAgainstEviction(K key, SoftLock softLock) {
      return isInDoubt(softLock) || wrappedEvictionAdvisor.adviseAgainstEviction(key, softLock.getOldValue());
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy