
net.sf.ehcache.transaction.xa.XATransactionStore Maven / Gradle / Ivy
The 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 net.sf.ehcache.transaction.xa;
import net.sf.ehcache.CacheEntry;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.statistics.StatisticBuilder;
import net.sf.ehcache.store.ElementValueComparator;
import net.sf.ehcache.store.Store;
import net.sf.ehcache.transaction.AbstractTransactionStore;
import net.sf.ehcache.transaction.SoftLock;
import net.sf.ehcache.transaction.SoftLockID;
import net.sf.ehcache.transaction.SoftLockManager;
import net.sf.ehcache.transaction.TransactionException;
import net.sf.ehcache.transaction.TransactionIDFactory;
import net.sf.ehcache.transaction.TransactionInterruptedException;
import net.sf.ehcache.transaction.TransactionTimeoutException;
import net.sf.ehcache.transaction.manager.TransactionManagerLookup;
import net.sf.ehcache.transaction.xa.commands.StorePutCommand;
import net.sf.ehcache.transaction.xa.commands.StoreRemoveCommand;
import net.sf.ehcache.util.LargeSet;
import net.sf.ehcache.util.SetAsList;
import net.sf.ehcache.writer.CacheWriterManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terracotta.statistics.observer.OperationObserver;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.transaction.RollbackException;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.xa.XAException;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
/**
* @author Ludovic Orban
*/
public class XATransactionStore extends AbstractTransactionStore {
private static final Logger LOG = LoggerFactory.getLogger(XATransactionStore.class.getName());
private final TransactionManagerLookup transactionManagerLookup;
private final TransactionIDFactory transactionIdFactory;
private final ElementValueComparator comparator;
private final SoftLockManager softLockManager;
private final Ehcache cache;
private final EhcacheXAResourceImpl recoveryResource;
private final ConcurrentHashMap transactionToXAResourceMap =
new ConcurrentHashMap();
private final ConcurrentHashMap transactionToTimeoutMap = new ConcurrentHashMap();
private final OperationObserver commitObserver = StatisticBuilder.operation(XaCommitOutcome.class)
.of(this).named("xa-commit").tag("xa-transactional").build();
private final OperationObserver rollbackObserver = StatisticBuilder.operation(XaRollbackOutcome.class)
.of(this).named("xa-rollback").tag("xa-transactional").build();
private final OperationObserver recoveryObserver = StatisticBuilder.operation(XaRecoveryOutcome.class)
.of(this).named("xa-recovery").tag("xa-transactional").build();
/**
* Constructor
* @param transactionManagerLookup the transaction manager lookup implementation
* @param softLockManager the soft lock manager
* @param transactionIdFactory the transaction ID factory
* @param cache the cache
* @param store the underlying store
*/
public XATransactionStore(TransactionManagerLookup transactionManagerLookup, SoftLockManager softLockManager,
TransactionIDFactory transactionIdFactory, Ehcache cache, Store store, ElementValueComparator comparator) {
super(store);
this.transactionManagerLookup = transactionManagerLookup;
this.transactionIdFactory = transactionIdFactory;
this.comparator = comparator;
if (transactionManagerLookup.getTransactionManager() == null) {
throw new TransactionException("no JTA transaction manager could be located, cannot bind twopc cache with JTA");
}
this.softLockManager = softLockManager;
this.cache = cache;
// this xaresource is for initial registration and recovery
this.recoveryResource = new EhcacheXAResourceImpl(cache, underlyingStore, transactionManagerLookup, softLockManager, transactionIdFactory,
comparator, commitObserver, rollbackObserver, recoveryObserver);
transactionManagerLookup.register(recoveryResource, true);
}
@Override
public void dispose() {
super.dispose();
transactionManagerLookup.unregister(recoveryResource, true);
}
private Transaction getCurrentTransaction() throws SystemException {
Transaction transaction = transactionManagerLookup.getTransactionManager().getTransaction();
if (transaction == null) {
throw new TransactionException("JTA transaction not started");
}
return transaction;
}
/**
* Get or create the XAResource of this XA store
* @return the EhcacheXAResource of this store
* @throws SystemException when something goes wrong with the transaction manager
*/
public EhcacheXAResourceImpl getOrCreateXAResource() throws SystemException {
Transaction transaction = getCurrentTransaction();
EhcacheXAResourceImpl xaResource = (EhcacheXAResourceImpl) transactionToXAResourceMap.get(transaction);
if (xaResource == null) {
LOG.debug("creating new XAResource");
xaResource = new EhcacheXAResourceImpl(cache, underlyingStore, transactionManagerLookup,
softLockManager, transactionIdFactory, comparator, commitObserver, rollbackObserver,
recoveryObserver);
transactionToXAResourceMap.put(transaction, xaResource);
xaResource.addTwoPcExecutionListener(new CleanupXAResource(getCurrentTransaction()));
}
return xaResource;
}
private XATransactionContext getTransactionContext() {
try {
Transaction transaction = getCurrentTransaction();
EhcacheXAResourceImpl xaResource = (EhcacheXAResourceImpl) transactionToXAResourceMap.get(transaction);
if (xaResource == null) {
return null;
}
XATransactionContext transactionContext = xaResource.getCurrentTransactionContext();
if (transactionContext == null) {
transactionManagerLookup.register(xaResource, false);
LOG.debug("creating new XA context");
transactionContext = xaResource.createTransactionContext();
xaResource.addTwoPcExecutionListener(new UnregisterXAResource());
} else {
transactionContext = xaResource.getCurrentTransactionContext();
}
LOG.debug("using XA context {}", transactionContext);
return transactionContext;
} catch (SystemException e) {
throw new TransactionException("cannot get the current transaction", e);
} catch (RollbackException e) {
throw new TransactionException("transaction rolled back", e);
}
}
private XATransactionContext getOrCreateTransactionContext() {
try {
EhcacheXAResourceImpl xaResource = getOrCreateXAResource();
XATransactionContext transactionContext = xaResource.getCurrentTransactionContext();
if (transactionContext == null) {
transactionManagerLookup.register(xaResource, false);
LOG.debug("creating new XA context");
transactionContext = xaResource.createTransactionContext();
xaResource.addTwoPcExecutionListener(new UnregisterXAResource());
} else {
transactionContext = xaResource.getCurrentTransactionContext();
}
LOG.debug("using XA context {}", transactionContext);
return transactionContext;
} catch (SystemException e) {
throw new TransactionException("cannot get the current transaction", e);
} catch (RollbackException e) {
throw new TransactionException("transaction rolled back", e);
}
}
/**
* This class is used to clean up the transactionToTimeoutMap after a transaction
* committed or rolled back.
*/
private final class CleanupTimeout implements Synchronization {
private final Transaction transaction;
private CleanupTimeout(final Transaction transaction) {
this.transaction = transaction;
}
public void beforeCompletion() {
}
public void afterCompletion(final int status) {
transactionToTimeoutMap.remove(transaction);
}
}
/**
* This class is used to clean up the transactionToXAResourceMap after a transaction
* committed or rolled back.
*/
private final class CleanupXAResource implements XAExecutionListener {
private final Transaction transaction;
private CleanupXAResource(Transaction transaction) {
this.transaction = transaction;
}
public void beforePrepare(EhcacheXAResource xaResource) {
}
public void afterCommitOrRollback(EhcacheXAResource xaResource) {
transactionToXAResourceMap.remove(transaction);
}
}
/**
* This class is used to unregister the XAResource after a transaction
* committed or rolled back.
*/
private final class UnregisterXAResource implements XAExecutionListener {
public void beforePrepare(EhcacheXAResource xaResource) {
}
public void afterCommitOrRollback(EhcacheXAResource xaResource) {
transactionManagerLookup.unregister(xaResource, false);
}
}
/**
* @return milliseconds left before timeout
*/
private long assertNotTimedOut() {
try {
if (Thread.interrupted()) {
throw new TransactionInterruptedException("transaction interrupted");
}
Transaction transaction = getCurrentTransaction();
Long timeoutTimestamp = transactionToTimeoutMap.get(transaction);
long now = MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS);
if (timeoutTimestamp == null) {
long timeout;
EhcacheXAResource xaResource = transactionToXAResourceMap.get(transaction);
if (xaResource != null) {
int xaResourceTimeout = xaResource.getTransactionTimeout();
timeout = MILLISECONDS.convert(xaResourceTimeout, TimeUnit.SECONDS);
} else {
int defaultTransactionTimeout = cache.getCacheManager().getTransactionController().getDefaultTransactionTimeout();
timeout = MILLISECONDS.convert(defaultTransactionTimeout, TimeUnit.SECONDS);
}
timeoutTimestamp = now + timeout;
transactionToTimeoutMap.put(transaction, timeoutTimestamp);
try {
transaction.registerSynchronization(new CleanupTimeout(transaction));
} catch (RollbackException e) {
throw new TransactionException("transaction has been marked as rollback only", e);
}
return timeout;
} else {
long timeToExpiry = timeoutTimestamp - now;
if (timeToExpiry <= 0) {
throw new TransactionTimeoutException("transaction timed out");
} else {
return timeToExpiry;
}
}
} catch (SystemException e) {
throw new TransactionException("cannot get the current transaction", e);
} catch (XAException e) {
throw new TransactionException("cannot get the XAResource transaction timeout", e);
}
}
/* transactional methods */
/**
* {@inheritDoc}
*/
public Element get(Object key) {
LOG.debug("cache {} get {}", cache.getName(), key);
XATransactionContext context = getTransactionContext();
Element element;
if (context == null) {
element = getFromUnderlyingStore(key);
} else {
element = context.get(key);
if (element == null && !context.isRemoved(key)) {
element = getFromUnderlyingStore(key);
}
}
return element;
}
/**
* {@inheritDoc}
*/
public Element getQuiet(Object key) {
LOG.debug("cache {} getQuiet {}", cache.getName(), key);
XATransactionContext context = getTransactionContext();
Element element;
if (context == null) {
element = getQuietFromUnderlyingStore(key);
} else {
element = context.get(key);
if (element == null && !context.isRemoved(key)) {
element = getQuietFromUnderlyingStore(key);
}
}
return element;
}
/**
* {@inheritDoc}
*/
public int getSize() {
LOG.debug("cache {} getSize", cache.getName());
XATransactionContext context = getOrCreateTransactionContext();
int size = underlyingStore.getSize();
return Math.max(0, size + context.getSizeModifier());
}
/**
* {@inheritDoc}
*/
public int getTerracottaClusteredSize() {
try {
Transaction transaction = transactionManagerLookup.getTransactionManager().getTransaction();
if (transaction == null) {
return underlyingStore.getTerracottaClusteredSize();
}
} catch (SystemException se) {
throw new TransactionException("cannot get the current transaction", se);
}
LOG.debug("cache {} getTerracottaClusteredSize", cache.getName());
XATransactionContext context = getOrCreateTransactionContext();
int size = underlyingStore.getTerracottaClusteredSize();
return size + context.getSizeModifier();
}
/**
* {@inheritDoc}
*/
public boolean containsKey(Object key) {
LOG.debug("cache {} containsKey", cache.getName(), key);
XATransactionContext context = getOrCreateTransactionContext();
return !context.isRemoved(key) && (context.getAddedKeys().contains(key) || underlyingStore.containsKey(key));
}
/**
* {@inheritDoc}
*/
public List getKeys() {
LOG.debug("cache {} getKeys", cache.getName());
XATransactionContext context = getOrCreateTransactionContext();
Set
© 2015 - 2025 Weber Informatics LLC | Privacy Policy