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

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

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

  @Override
  public ValueHolder computeIfPresent(K key, BiFunction 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 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 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 CacheAccessException {
    return bulkCompute(keys, remappingFunction, REPLACE_EQUALS_TRUE);
  }

  @Override
  public Map> bulkCompute(Set keys, final Function>, Iterable>> 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> 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 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> 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;
    }
  }

  @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 evictionVeto = new EvictionVeto() {
          @Override
          public boolean vetoes(K key, SoftLock lock) {
            return lock.getTransactionId() != null;
          }
        };

        // 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.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