/*
* JBoss, Home of Professional Open Source.
* Copyright 2015, 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.jboss.as.txn.service.internal.tsr;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import org.jboss.as.txn.logging.TransactionLogger;
/**
* This class was added to:
*
* 1. workaround an issue discussed in https://java.net/jira/browse/JTA_SPEC-4 whereby the JCA Synchronization(s) need to be
* called after the JPA Synchronization(s). Currently the implementation orders JCA relative to all interposed Synchronizations,
* if this is not desirable it would be possible to modify this class to store just the JPA and JCA syncs and the other syncs
* can simply be passed to a delegate (would need the reference to this in the constructor).
*
* 2. During afterCompletion the JCA synchronizations should be called last as that allows JCA to detect connection leaks from
* frameworks that have not closed the JCA managed resources. This is described in (for example)
* http://docs.oracle.com/javaee/5/api/javax/transaction/TransactionSynchronizationRegistry
* .html#registerInterposedSynchronization(javax.transaction.Synchronization) where it says that during afterCompletion
* "Resources can be closed but no transactional work can be performed with them"
*/
public class JCAOrderedLastSynchronizationList implements Synchronization {
private final com.arjuna.ats.jta.transaction.Transaction tx;
private final Map<Transaction, JCAOrderedLastSynchronizationList> jcaOrderedLastSynchronizations;
private final List<Synchronization> preJcaSyncs = new ArrayList<Synchronization>();
private final List<Synchronization> jcaSyncs = new ArrayList<Synchronization>();
public JCAOrderedLastSynchronizationList(com.arjuna.ats.jta.transaction.Transaction tx,
Map<Transaction, JCAOrderedLastSynchronizationList> jcaOrderedLastSynchronizations) {
this.tx = tx;
this.jcaOrderedLastSynchronizations = jcaOrderedLastSynchronizations;
}
/**
* This is only allowed at various points of the transaction lifecycle.
*
* @param synchronization The synchronization to register
* @throws IllegalStateException In case the transaction was in a state that was not valid to register under
* @throws SystemException In case the transaction status was not known
*/
public void registerInterposedSynchronization(Synchronization synchronization) throws IllegalStateException, SystemException {
int status = tx.getStatus();
switch (status) {
case javax.transaction.Status.STATUS_ACTIVE:
case javax.transaction.Status.STATUS_PREPARING:
break;
default:
throw TransactionLogger.ROOT_LOGGER.syncsnotallowed(status);
}
if (synchronization.getClass().getName().startsWith("org.jboss.jca")) {
if (TransactionLogger.ROOT_LOGGER.isTraceEnabled()) {
TransactionLogger.ROOT_LOGGER.trace("JCAOrderedLastSynchronizationList.jcaSyncs.add - Class: " + synchronization.getClass() + " HashCode: "
+ synchronization.hashCode() + " toString: " + synchronization);
}
jcaSyncs.add(synchronization);
} else {
if (TransactionLogger.ROOT_LOGGER.isTraceEnabled()) {
TransactionLogger.ROOT_LOGGER.trace("JCAOrderedLastSynchronizationList.preJcaSyncs.add - Class: " + synchronization.getClass() + " HashCode: "
+ synchronization.hashCode() + " toString: " + synchronization);
}
preJcaSyncs.add(synchronization);
}
}
/**
* Exceptions from Synchronizations that are registered with this TSR are not trapped for before completion. This is because
* an error in a Sync here should result in the transaction rolling back.
*
* You can see that in effect in these classes:
* https://github.com/jbosstm/narayana/blob/5.0.4.Final/ArjunaCore/arjuna/classes
* /com/arjuna/ats/arjuna/coordinator/TwoPhaseCoordinator.java#L91
* https://github.com/jbosstm/narayana/blob/5.0.4.Final/ArjunaJTA
* /jta/classes/com/arjuna/ats/internal/jta/resources/arjunacore/SynchronizationImple.java#L76
*/
@Override
public void beforeCompletion() {
// This is needed to guard against syncs being registered during the run, otherwise we could have used an iterator
int lastIndexProcessed = 0;
while ((lastIndexProcessed < preJcaSyncs.size())) {
Synchronization preJcaSync = preJcaSyncs.get(lastIndexProcessed);
if (TransactionLogger.ROOT_LOGGER.isTraceEnabled()) {
TransactionLogger.ROOT_LOGGER.trace("JCAOrderedLastSynchronizationList.preJcaSyncs.before_completion - Class: " + preJcaSync.getClass() + " HashCode: "
+ preJcaSync.hashCode()
+ " toString: "
+ preJcaSync);
}
preJcaSync.beforeCompletion();
lastIndexProcessed = lastIndexProcessed + 1;
}
// Do the same for the jca syncs
lastIndexProcessed = 0;
while ((lastIndexProcessed < jcaSyncs.size())) {
Synchronization jcaSync = jcaSyncs.get(lastIndexProcessed);
if (TransactionLogger.ROOT_LOGGER.isTraceEnabled()) {
TransactionLogger.ROOT_LOGGER.trace("JCAOrderedLastSynchronizationList.jcaSyncs.before_completion - Class: " + jcaSync.getClass() + " HashCode: "
+ jcaSync.hashCode()
+ " toString: "
+ jcaSync);
}
jcaSync.beforeCompletion();
lastIndexProcessed = lastIndexProcessed + 1;
}
}
@Override
public void afterCompletion(int status) {
// The list should be iterated in reverse order - has issues with EJB3 if not
// https://github.com/jbosstm/narayana/blob/master/ArjunaCore/arjuna/classes/com/arjuna/ats/arjuna/coordinator/TwoPhaseCoordinator.java#L509
for (int i = preJcaSyncs.size() - 1; i>= 0; --i) {
Synchronization preJcaSync = preJcaSyncs.get(i);
if (TransactionLogger.ROOT_LOGGER.isTraceEnabled()) {
TransactionLogger.ROOT_LOGGER.trace("JCAOrderedLastSynchronizationList.preJcaSyncs.afterCompletion - Class: " + preJcaSync.getClass() + " HashCode: "
+ preJcaSync.hashCode()
+ " toString: " + preJcaSync);
}
try {
preJcaSync.afterCompletion(status);
} catch (Exception e) {
// Trap these exceptions so the rest of the synchronizations get the chance to complete
// https://github.com/jbosstm/narayana/blob/5.0.4.Final/ArjunaJTA/jta/classes/com/arjuna/ats/internal/jta/resources/arjunacore/SynchronizationImple.java#L102
TransactionLogger.ROOT_LOGGER.preJcaSyncAfterCompletionFailed(preJcaSync, tx, e);
}
}
for (int i = jcaSyncs.size() - 1; i>= 0; --i) {
Synchronization jcaSync = jcaSyncs.get(i);
if (TransactionLogger.ROOT_LOGGER.isTraceEnabled()) {
TransactionLogger.ROOT_LOGGER.trace("JCAOrderedLastSynchronizationList.jcaSyncs.afterCompletion - Class: " + jcaSync.getClass() + " HashCode: "
+ jcaSync.hashCode()
+ " toString: "
+ jcaSync);
}
try {
jcaSync.afterCompletion(status);
} catch (Exception e) {
// Trap these exceptions so the rest of the synchronizations get the chance to complete
// https://github.com/jbosstm/narayana/blob/5.0.4.Final/ArjunaJTA/jta/classes/com/arjuna/ats/internal/jta/resources/arjunacore/SynchronizationImple.java#L102
TransactionLogger.ROOT_LOGGER.jcaSyncAfterCompletionFailed(jcaSync, tx, e);
}
}
if (jcaOrderedLastSynchronizations.remove(tx) == null) {
// The identifier wasn't stable - scan for it - this can happen in JTS propagation when the UID needs retrieving
// from the parent and the parent has been deactivated
Transaction altKey = null;
Iterator<Entry<Transaction, JCAOrderedLastSynchronizationList>> iterator = jcaOrderedLastSynchronizations.entrySet().iterator();
while (altKey == null && iterator.hasNext()) {
Map.Entry<Transaction, JCAOrderedLastSynchronizationList> next = iterator.next();
if (next.getValue().equals(this)) {
altKey = next.getKey();
iterator.remove();
if (TransactionLogger.ROOT_LOGGER.isTraceEnabled()) {
TransactionLogger.ROOT_LOGGER.tracef("Removed: %s [%s]", System.identityHashCode(tx), tx.toString());
}
break;
}
}
if (altKey == null) {
TransactionLogger.ROOT_LOGGER.transactionNotFound(tx);
}
}
}
}