/* * JBoss, Home of Professional Open Source. * Copyright 2014, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.wildfly.clustering.ee.infinispan; import javax.transaction.InvalidTransactionException; import javax.transaction.NotSupportedException; import javax.transaction.RollbackException; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import org.infinispan.Cache; import org.infinispan.commons.CacheException; import org.wildfly.clustering.ee.BatchContext; import org.wildfly.clustering.ee.Batcher; /** * A {@link Batcher} implementation based on Infinispan's {@link org.infinispan.batch.BatchContainer}, except that its transaction reference * is stored within the returned Batch object instead of a ThreadLocal. This also allows the user to call {@link Batch#close()} from a * different thread than the one that created the {@link Batch}. In this case, however, the user must first resume the batch * via {@link #resumeBatch(TransactionBatch)}. * @author Paul Ferraro */ public class InfinispanBatcher implements Batcher<TransactionBatch> { private static final BatchContext PASSIVE_BATCH_CONTEXT = () -> { // Do nothing }; private static final TransactionBatch NON_TX_BATCH = new TransactionBatch() { @Override public void close() { // No-op } @Override public void discard() { // No-op } @Override public State getState() { // A non-tx batch is always active return State.ACTIVE; } @Override public Transaction getTransaction() { return null; } @Override public TransactionBatch interpose() { return this; } }; // Used to coalesce interposed transactions static final ThreadLocal<TransactionBatch> CURRENT_BATCH = new ThreadLocal<>(); private static final Synchronization CURRENT_BATCH_REMOVER = new Synchronization() { @Override public void beforeCompletion() { } @Override public void afterCompletion(int status) { CURRENT_BATCH.remove(); } }; private final TransactionManager tm; public InfinispanBatcher(Cache<?, ?> cache) { this(cache.getAdvancedCache().getTransactionManager()); } public InfinispanBatcher(TransactionManager tm) { this.tm = tm; } @Override public TransactionBatch createBatch() { if (this.tm == null) return NON_TX_BATCH; TransactionBatch batch = CURRENT_BATCH.get(); if (batch != null) { return batch.interpose(); } try { this.tm.suspend(); this.tm.begin(); Transaction tx = this.tm.getTransaction(); tx.registerSynchronization(CURRENT_BATCH_REMOVER); batch = new InfinispanBatch(tx); CURRENT_BATCH.set(batch); return batch; } catch (RollbackException | SystemException | NotSupportedException e) { throw new CacheException(e); } } @Override public BatchContext resumeBatch(TransactionBatch batch) { TransactionBatch existingBatch = CURRENT_BATCH.get(); // Trivial case - nothing to suspend/resume if (batch == existingBatch) return PASSIVE_BATCH_CONTEXT; Transaction tx = (batch != null) ? batch.getTransaction() : null; // Non-tx case, just swap thread local if ((batch == null) || (tx == null)) { CURRENT_BATCH.set(batch); return () -> { CURRENT_BATCH.set(existingBatch); }; } try { if (existingBatch != null) { Transaction existingTx = this.tm.suspend(); if (existingBatch.getTransaction() != existingTx) { throw new IllegalStateException(); } } this.tm.resume(tx); CURRENT_BATCH.set(batch); return () -> { try { this.tm.suspend(); if (existingBatch != null) { try { this.tm.resume(existingBatch.getTransaction()); CURRENT_BATCH.set(existingBatch); } catch (InvalidTransactionException e) { throw new CacheException(e); } } else { CURRENT_BATCH.remove(); } } catch (SystemException e) { throw new CacheException(e); } }; } catch (SystemException | InvalidTransactionException e) { throw new CacheException(e); } } @Override public TransactionBatch suspendBatch() { if (this.tm == null) return NON_TX_BATCH; TransactionBatch batch = CURRENT_BATCH.get(); if (batch != null) { try { Transaction tx = this.tm.suspend(); if (batch.getTransaction() != tx) { throw new IllegalStateException(); } } catch (SystemException e) { throw new CacheException(e); } finally { CURRENT_BATCH.remove(); } } return batch; } }