/*
* Bitronix Transaction Manager
*
* Copyright (c) 2010, Bitronix Software.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program 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 distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package bitronix.tm.resource.common;
import bitronix.tm.BitronixTransaction;
import bitronix.tm.BitronixXid;
import bitronix.tm.internal.XAResourceHolderState;
import bitronix.tm.utils.Uid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Implementation of all services required by a {@link XAResourceHolder}. This class keeps a list of all
* {@link XAResourceHolderState}s of the {@link XAResourceHolder} plus the currently active one. There is
* one per transaction in which this {@link XAResourceHolder} is enlisted plus all the suspended transactions in which
* it is enlisted as well.
*
* @author lorban
*/
public abstract class AbstractXAResourceHolder extends AbstractXAStatefulHolder implements XAResourceHolder {
private final static Logger log = LoggerFactory.getLogger(AbstractXAResourceHolder.class);
private final Map<Uid, Map<Uid, XAResourceHolderState>> xaResourceHolderStates = new HashMap<Uid, Map<Uid, XAResourceHolderState>>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// This method is only used by tests. It is (and always was) potentially thread-unsafe depending on what callers do with the returned map.
protected Map<Uid, XAResourceHolderState> getXAResourceHolderStatesForGtrid(Uid gtrid) {
rwLock.readLock().lock();
try {
return xaResourceHolderStates.get(gtrid);
} finally {
rwLock.readLock().unlock();
}
}
public boolean isExistXAResourceHolderStatesForGtrid(Uid gtrid) {
rwLock.readLock().lock();
try {
return xaResourceHolderStates.containsKey(gtrid);
} finally {
rwLock.readLock().unlock();
}
}
public int getXAResourceHolderStateCountForGtrid(Uid gtrid) {
rwLock.readLock().lock();
try {
Map<Uid, XAResourceHolderState> statesForGtrid = xaResourceHolderStates.get(gtrid);
if (statesForGtrid != null) {
return statesForGtrid.size();
}
return 0;
} finally {
rwLock.readLock().unlock();
}
}
public void acceptVisitorForXAResourceHolderStates(Uid gtrid, XAResourceHolderStateVisitor visitor) {
rwLock.readLock().lock();
try {
Map<Uid, XAResourceHolderState> statesForGtrid = xaResourceHolderStates.get(gtrid);
if (statesForGtrid != null) {
for (XAResourceHolderState xaResourceHolderState : statesForGtrid.values()) {
if (!visitor.visit(xaResourceHolderState)) {
break;
}
}
}
} finally {
rwLock.readLock().unlock();
}
}
public void putXAResourceHolderState(BitronixXid xid, XAResourceHolderState xaResourceHolderState) {
Uid gtrid = xid.getGlobalTransactionIdUid();
Uid bqual = xid.getBranchQualifierUid();
rwLock.writeLock().lock();
try {
if (log.isDebugEnabled()) { log.debug("putting XAResourceHolderState [" + xaResourceHolderState + "] on " + this); }
if (!xaResourceHolderStates.containsKey(gtrid)) {
if (log.isDebugEnabled()) { log.debug("GTRID [" + gtrid + "] previously unknown to " + this + ", adding it to the resource's transactions list"); }
// use a LinkedHashMap as iteration order must be guaranteed
Map<Uid, XAResourceHolderState> statesForGtrid = new LinkedHashMap<Uid, XAResourceHolderState>(4);
statesForGtrid.put(bqual, xaResourceHolderState);
xaResourceHolderStates.put(gtrid, statesForGtrid);
}
else {
if (log.isDebugEnabled()) { log.debug("GTRID [" + gtrid + "] previously known to " + this + ", adding it to the resource's transactions list"); }
Map<Uid, XAResourceHolderState> statesForGtrid = xaResourceHolderStates.get(gtrid);
statesForGtrid.put(bqual, xaResourceHolderState);
}
}
finally {
rwLock.writeLock().unlock();
}
}
public void removeXAResourceHolderState(BitronixXid xid) {
Uid gtrid = xid.getGlobalTransactionIdUid();
Uid bqual = xid.getBranchQualifierUid();
rwLock.writeLock().lock();
try {
if (log.isDebugEnabled()) { log.debug("removing XAResourceHolderState of xid " + xid + " from " + this); }
Map<Uid, XAResourceHolderState> statesForGtrid = xaResourceHolderStates.get(gtrid);
if (statesForGtrid == null) {
log.warn("tried to remove unknown GTRID [" + gtrid + "] from " + this + " - Bug?");
return;
}
XAResourceHolderState removed = statesForGtrid.remove(bqual);
if (removed == null) {
log.warn("tried to remove unknown BQUAL [" + bqual + "] from " + this + " - Bug?");
return;
}
if (statesForGtrid.isEmpty()) {
xaResourceHolderStates.remove(gtrid);
}
}
finally {
rwLock.writeLock().unlock();
}
}
public boolean hasStateForXAResource(XAResourceHolder xaResourceHolder) {
rwLock.readLock().lock();
try {
for (Map<Uid, XAResourceHolderState> statesForGtrid : xaResourceHolderStates.values()) {
for (XAResourceHolderState otherXaResourceHolderState : statesForGtrid.values()) {
if (otherXaResourceHolderState.getXAResource() == xaResourceHolder.getXAResource()) {
if (log.isDebugEnabled()) { log.debug("resource " + xaResourceHolder + " is enlisted in another transaction with " + otherXaResourceHolderState.getXid().toString()); }
return true;
}
}
}
if (log.isDebugEnabled()) { log.debug("resource not enlisted in any transaction: " + xaResourceHolder); }
return false;
}
finally {
rwLock.readLock().unlock();
}
}
/**
* If this method returns false, then local transaction calls like Connection.commit() can be made.
* @return true if start() has been successfully called but not end() yet <i>and</i> the transaction is not suspended.
*/
public boolean isParticipatingInActiveGlobalTransaction() {
rwLock.readLock().lock();
try {
BitronixTransaction currentTransaction = TransactionContextHelper.currentTransaction();
Uid gtrid = currentTransaction == null ? null : currentTransaction.getResourceManager().getGtrid();
if (gtrid == null)
return false;
Map<Uid, XAResourceHolderState> statesForGtrid = xaResourceHolderStates.get(gtrid);
if (statesForGtrid == null)
return false;
for (XAResourceHolderState xaResourceHolderState : statesForGtrid.values()) {
if (xaResourceHolderState != null &&
xaResourceHolderState.isStarted() &&
!xaResourceHolderState.isSuspended() &&
!xaResourceHolderState.isEnded())
return true;
}
return false;
}
finally {
rwLock.readLock().unlock();
}
}
/**
* Simple helper method which returns a set of GTRIDs of transactions in which
* this resource is enlisted. Useful for monitoring.
* @return a set of String-encoded GTRIDs of transactions in which this resource is enlisted.
*/
public Set<String> getXAResourceHolderStateGtrids() {
rwLock.readLock().lock();
try {
HashSet<String> gtridsAsStrings = new HashSet<String>();
for (Uid uid : xaResourceHolderStates.keySet()) {
gtridsAsStrings.add(uid.toString());
}
return gtridsAsStrings;
}
finally {
rwLock.readLock().unlock();
}
}
}