/*
* $Header: /home/cvs/jakarta-slide/src/share/org/apache/slide/transaction/SlideTransaction.java,v 1.20.2.2 2004/02/19 17:01:33 ozeigermann Exp $
* $Revision: 1.20.2.2 $
* $Date: 2004/02/19 17:01:33 $
*
* ====================================================================
*
* Copyright 1999-2002 The Apache Software Foundation
*
* 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.apache.slide.transaction;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.apache.slide.util.Messages;
import org.apache.slide.util.logger.Logger;
/**
* JTA Transaction implementation.
*
* @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
* @version $Revision: 1.20.2.2 $
*/
public final class SlideTransaction implements Transaction {
// -------------------------------------------------------------- Constants
protected static final String LOG_CHANNEL =
SlideTransaction.class.getName();
// ------------------------------------------------------------ Constructor
/**
* Constructor.
*/
public SlideTransaction(SlideTransactionManager transactionManager) {
// Generate the transaction id
globalCreatedTransactions++;
currentTransactionNumber = globalCreatedTransactions;
currentThreadName = Thread.currentThread().getName();
xid = new SlideXid
((currentThreadName + "-" + System.currentTimeMillis() + "-"
+ currentTransactionNumber).getBytes(),
0, new byte[0]);
this.transactionManager = transactionManager;
}
// ----------------------------------------------------- Instance Variables
/**
* Global transaction id.
*/
private SlideXid xid;
/**
* Branches.
* Keyed : branch xid -> resource manager.
*/
private Hashtable branches = new Hashtable();
/**
* Active branches.
* Keyed : resource manager -> branches xid.
*/
private Hashtable activeBranches = new Hashtable();
/**
* Enlisted resources.
*/
private Vector enlistedResources = new Vector();
/**
* Suspended resources.
* Keyed : resource manager -> branches xid.
*/
private Hashtable suspendedResources = new Hashtable();
/**
* Transaction status.
*/
private int status = Status.STATUS_ACTIVE;
/**
* Synchronization objects.
*/
private Vector synchronizationObjects = new Vector();
/**
* Branch counter.
*/
private int branchCounter = 1;
/**
* Number of transactions created.
*/
private static int globalCreatedTransactions = 0;
/**
* Transaction number.
*/
private int currentTransactionNumber = 0;
/**
* Name of the thread bound to the transaction.
*/
private String currentThreadName = null;
/**
* Associated transaction manager.
*/
private SlideTransactionManager transactionManager;
// ------------------------------------------------------------- Properties
// ---------------------------------------------------- Transaction Methods
/**
* Complete the transaction represented by this Transaction object.
*
* @exception RollbackException Thrown to indicate that the transaction
* has been rolled back rather than committed.
* @exception HeuristicMixedException Thrown to indicate that a heuristic
* decision was made and that some relevant updates have been committed
* while others have been rolled back.
* @exception HeuristicRollbackException Thrown to indicate that a
* heuristic decision was made and that some relevant updates have been
* rolled back.
* @exception SecurityException Thrown to indicate that the thread is not
* allowed to commit the transaction.
* @exception IllegalStateException Thrown if the current thread is not
* associated with a transaction.
* @exception SystemException Thrown if the transaction manager encounters
* an unexpected error condition.
*/
public void commit()
throws RollbackException, HeuristicMixedException,
HeuristicRollbackException, SecurityException, IllegalStateException,
SystemException {
//System.out.println(this + " COMMIT ");
if (status == Status.STATUS_MARKED_ROLLBACK) {
rollback();
return;
}
// Check status ACTIVE
if (status != Status.STATUS_ACTIVE)
throw new IllegalStateException();
// Call synchronized objects beforeCompletion
Enumeration syncList = synchronizationObjects.elements();
while (syncList.hasMoreElements()) {
Synchronization sync = (Synchronization) syncList.nextElement();
sync.beforeCompletion();
}
Vector exceptions = new Vector();
boolean fail = false;
Enumeration enum = branches.keys();
if (enlistedResources.size() == 1) {
// One phase commit
status = Status.STATUS_COMMITTING;
while (enum.hasMoreElements()) {
Object key = enum.nextElement();
XAResource resourceManager =
(XAResource) branches.get(key);
try {
if (!fail)
resourceManager.commit(xid, true);
else
resourceManager.rollback(xid);
} catch (Throwable e) {
// Adding the exception to the error code list
String logMessage = Messages.format
(SlideTransaction.class.getName() + ".commitFail",
resourceManager, getXAErrorCode(e), toString());
transactionManager.getLogger().log
(logMessage, e, LOG_CHANNEL, Logger.WARNING);
exceptions.addElement(e);
fail = true;
status = Status.STATUS_MARKED_ROLLBACK;
}
}
if (!fail) {
status = Status.STATUS_COMMITTED;
} else {
status = Status.STATUS_ROLLEDBACK;
}
} else if (enlistedResources.size() != 0) {
// Prepare each enlisted resource
status = Status.STATUS_PREPARING;
while ((!fail) && (enum.hasMoreElements())) {
Object key = enum.nextElement();
XAResource resourceManager =
(XAResource) branches.get(key);
try {
// Preparing the resource manager using its branch xid
resourceManager.prepare((Xid) key);
} catch (Throwable e) {
String logMessage = Messages.format
(SlideTransaction.class.getName() + ".prepareFail",
resourceManager, getXAErrorCode(e), toString());
transactionManager.getLogger().log
(logMessage, e, LOG_CHANNEL, Logger.WARNING);
// Adding the exception to the error code list
exceptions.addElement(e);
fail = true;
status = Status.STATUS_MARKED_ROLLBACK;
}
}
if (!fail)
status = Status.STATUS_PREPARED;
// If fail, rollback
if (fail) {
status = Status.STATUS_ROLLING_BACK;
fail = false;
// Rolling back all the prepared (and unprepared) branches
enum = branches.keys();
while (enum.hasMoreElements()) {
Object key = enum.nextElement();
XAResource resourceManager =
(XAResource) branches.get(key);
try {
resourceManager.rollback((Xid) key);
} catch(Throwable e) {
String logMessage = Messages.format
(SlideTransaction.class.getName() + ".rollbackFail",
resourceManager, getXAErrorCode(e), toString());
transactionManager.getLogger().log
(logMessage, e, LOG_CHANNEL, Logger.WARNING);
exceptions.addElement(e);
fail = true;
}
}
status = Status.STATUS_ROLLEDBACK;
} else {
status = Status.STATUS_COMMITTING;
// Commit each enlisted resource
enum = branches.keys();
while (enum.hasMoreElements()) {
Object key = enum.nextElement();
XAResource resourceManager =
(XAResource) branches.get(key);
try {
resourceManager.commit((Xid) key, false);
} catch(Throwable e) {
String logMessage = Messages.format
(SlideTransaction.class.getName() + ".commitFail",
resourceManager, getXAErrorCode(e), toString());
transactionManager.getLogger().log
(logMessage, e, LOG_CHANNEL, Logger.WARNING);
exceptions.addElement(e);
fail = true;
}
}
status = Status.STATUS_COMMITTED;
}
}
// Call synchronized objects afterCompletion
syncList = synchronizationObjects.elements();
while (syncList.hasMoreElements()) {
Synchronization sync =
(Synchronization) syncList.nextElement();
sync.afterCompletion(status);
}
// Parsing exception and throwing an appropriate exception
enum = exceptions.elements();
if (enum.hasMoreElements()) {
if ((status == Status.STATUS_ROLLEDBACK) && (!fail))
throw new RollbackException();
if (status == Status.STATUS_ROLLEDBACK)
throw new HeuristicRollbackException();
if ((status == Status.STATUS_COMMITTED) && (fail))
throw new HeuristicMixedException();
}
}
/**
* Delist the resource specified from the current transaction associated
* with the calling thread.
*
* @param xaRes The XAResource object representing the resource to delist
* @param flag One of the values of TMSUCCESS, TMSUSPEND, or TMFAIL
* @exception IllegalStateException Thrown if the transaction in the
* target object is inactive.
* @exception SystemException Thrown if the transaction manager encounters
* an unexpected error condition.
*/
public boolean delistResource(XAResource xaRes, int flag)
throws IllegalStateException, SystemException {
//System.out.println(this + " DELIST " + xaRes);
// Check status ACTIVE
if (status != Status.STATUS_ACTIVE)
throw new IllegalStateException();
Xid xid = (Xid) activeBranches.get(xaRes);
if (xid == null)
throw new IllegalStateException();
activeBranches.remove(xaRes);
if (transactionManager.getLogger().isEnabled
(LOG_CHANNEL, Logger.DEBUG)) {
String logMessage = Messages.format
(SlideTransaction.class.getName() + ".delist", xaRes,
getXAFlag(flag), toString());
transactionManager.getLogger().log
(logMessage, LOG_CHANNEL, Logger.DEBUG);
}
XAException exception = null;
try {
xaRes.end(xid, flag);
} catch (XAException e) {
exception = e;
}
if (exception != null) {
String logMessage = Messages.format
(SlideTransaction.class.getName() + ".delistFail",
xaRes, getXAErrorCode(exception), toString());
transactionManager.getLogger().log
(logMessage, LOG_CHANNEL, Logger.WARNING);
return false;
}
if (flag == XAResource.TMSUSPEND)
suspendedResources.put(xaRes, xid);
//System.out.println("Delisted ok(" + this + ") = " + xaRes + " xid: " + xid);
return true;
}
/**
* Enlist the resource specified with the current transaction context of
* the calling thread.
*
* @param xaRes The XAResource object representing the resource to delist
* @return true if the resource was enlisted successfully; otherwise false.
* @exception RollbackException Thrown to indicate that the transaction
* has been marked for rollback only.
* @exception IllegalStateException Thrown if the transaction in the
* target object is in prepared state or the transaction is inactive.
* @exception SystemException Thrown if the transaction manager
* encounters an unexpected error condition.
*/
public boolean enlistResource(XAResource xaRes)
throws RollbackException, IllegalStateException, SystemException {
//System.out.println(this + " ENLIST " + xaRes);
if (status == Status.STATUS_MARKED_ROLLBACK)
throw new RollbackException();
// Check status ACTIVE
if (status != Status.STATUS_ACTIVE)
throw new IllegalStateException();
// Preventing two branches from being active at the same time on the
// same resource manager
Xid activeXid = (Xid) activeBranches.get(xaRes);
if (activeXid != null)
return false;
boolean alreadyEnlisted = false;
int flag = XAResource.TMNOFLAGS;
Xid branchXid = (Xid) suspendedResources.get(xaRes);
if (branchXid == null) {
Enumeration enum = enlistedResources.elements();
while ((!alreadyEnlisted) && (enum.hasMoreElements())) {
XAResource resourceManager = (XAResource) enum.nextElement();
try {
if (resourceManager.isSameRM(xaRes)) {
flag = XAResource.TMJOIN;
alreadyEnlisted = true;
}
} catch (XAException e) {
}
}
branchXid = this.xid.newBranch(branchCounter++);
//System.out.println(this + " Creating new branch for " + xaRes);
} else {
alreadyEnlisted = true;
flag = XAResource.TMRESUME;
suspendedResources.remove(xaRes);
}
if (transactionManager.getLogger().isEnabled
(LOG_CHANNEL, Logger.DEBUG)) {
String logMessage = Messages.format
(SlideTransaction.class.getName() + ".enlist", xaRes,
getXAFlag(flag), toString());
transactionManager.getLogger().log
(logMessage, LOG_CHANNEL, Logger.DEBUG);
}
try {
//System.out.println("Starting(" + this + ") = " + xaRes + " Branch: " + branchXid + " Flag: " + flag);
xaRes.start(branchXid, flag);
} catch (XAException e) {
String logMessage = Messages.format
(SlideTransaction.class.getName() + ".enlistFail",
xaRes, getXAErrorCode(e), toString());
transactionManager.getLogger().log
(logMessage, LOG_CHANNEL, Logger.WARNING);
return false;
}
if (!alreadyEnlisted) {
enlistedResources.addElement(xaRes);
}
branches.put(branchXid, xaRes);
activeBranches.put(xaRes, branchXid);
return true;
}
/**
* Roll back the transaction associated with the current thread. When
* this method completes, the thread becomes associated with no
* transaction.
*
* @exception SecurityException Thrown to indicate that the thread is not
* allowed to commit the transaction.
* @exception IllegalStateException Thrown if the current thread is not
* associated with a transaction.
* @exception SystemException Thrown if the transaction manager encounters
* an unexpected error condition.
*/
public void rollback()
throws SecurityException, IllegalStateException, SystemException {
//System.out.println(this + " ROLLBACK ");
// Check status ACTIVE
if (status != Status.STATUS_ACTIVE && status != Status.STATUS_MARKED_ROLLBACK)
throw new IllegalStateException();
Vector exceptions = new Vector();
Enumeration enum = branches.keys();
status = Status.STATUS_ROLLING_BACK;
while (enum.hasMoreElements()) {
Xid xid = (Xid) enum.nextElement();
XAResource resourceManager = (XAResource) branches.get(xid);
try {
resourceManager.rollback(xid);
} catch (Throwable e) {
exceptions.addElement(e);
String logMessage = Messages.format
(SlideTransaction.class.getName() + ".rollbackFail",
resourceManager, getXAErrorCode(e), toString());
transactionManager.getLogger().log
(logMessage, LOG_CHANNEL, Logger.WARNING);
}
}
status = Status.STATUS_ROLLEDBACK;
}
/**
* Modify the transaction associated with the current thread such that
* the only possible outcome of the transaction is to roll back the
* transaction.
*
* @exception IllegalStateException Thrown if the current thread is not
* associated with a transaction.
* @exception SystemException Thrown if the transaction manager encounters
* an unexpected error condition.
*/
public void setRollbackOnly()
throws IllegalStateException, SystemException {
status = Status.STATUS_MARKED_ROLLBACK;
}
/**
* Obtain the status of the transaction associated with the current thread.
*
* @exception SystemException Thrown if the transaction manager encounters
* an unexpected error condition.
* @return The transaction status. If no transaction is associated with
* the current thread, this method returns the Status.NoTransaction value.
*/
public int getStatus()
throws SystemException {
return status;
}
/**
* Register a synchronization object for the transaction currently
* associated with the calling thread. The transction manager invokes the
* beforeCompletion method prior to starting the transaction commit
* process. After the transaction is completed, the transaction manager
* invokes the afterCompletion method.
*
* @param sync The Synchronization object for the transaction associated
* with the target object.
* @exception RollbackException Thrown to indicate that the transaction
* has been marked for rollback only.
* @exception IllegalStateException Thrown if the transaction in the
* target object is in prepared state or the transaction is inactive.
* @exception SystemException Thrown if the transaction manager encounters
* an unexpected error condition.
*/
public void registerSynchronization(Synchronization sync)
throws RollbackException, IllegalStateException, SystemException {
if (status == Status.STATUS_MARKED_ROLLBACK)
throw new RollbackException();
if (status != Status.STATUS_ACTIVE)
throw new IllegalStateException();
synchronizationObjects.addElement(sync);
}
// --------------------------------------------------------- Public Methods
/**
* Return a String representation of the error code contained in a
* XAException.
*/
public static String getXAErrorCode(Throwable throww) {
String result = null;
if (throww instanceof XAException)
result = getXAErrorCode((XAException)throww);
else {
StringWriter sw = new StringWriter();
throww.printStackTrace( new PrintWriter(sw, true) ); //autoFlush=true
result = sw.toString();
}
return result;
}
/**
* Return a String representation of the error code contained in a
* XAException.
*/
public static String getXAErrorCode(XAException xae) {
switch (xae.errorCode) {
case XAException.XA_HEURCOM:
return "XA_HEURCOM";
case XAException.XA_HEURHAZ:
return "XA_HEURHAZ";
case XAException.XA_HEURMIX:
return "XA_HEURMIX";
case XAException.XA_HEURRB:
return "XA_HEURRB";
case XAException.XA_NOMIGRATE:
return "XA_NOMIGRATE";
case XAException.XA_RBBASE:
return "XA_RBBASE";
case XAException.XA_RBCOMMFAIL:
return "XA_RBCOMMFAIL";
case XAException.XA_RBDEADLOCK:
return "XA_RBBEADLOCK";
case XAException.XA_RBEND:
return "XA_RBEND";
case XAException.XA_RBINTEGRITY:
return "XA_RBINTEGRITY";
case XAException.XA_RBOTHER:
return "XA_RBOTHER";
case XAException.XA_RBPROTO:
return "XA_RBPROTO";
case XAException.XA_RBTIMEOUT:
return "XA_RBTIMEOUT";
case XAException.XA_RDONLY:
return "XA_RDONLY";
case XAException.XA_RETRY:
return "XA_RETRY";
case XAException.XAER_ASYNC:
return "XAER_ASYNC";
case XAException.XAER_DUPID:
return "XAER_DUPID";
case XAException.XAER_INVAL:
return "XAER_INVAL";
case XAException.XAER_NOTA:
return "XAER_NOTA";
case XAException.XAER_OUTSIDE:
return "XAER_OUTSIDE";
case XAException.XAER_PROTO:
return "XAER_PROTO";
case XAException.XAER_RMERR:
return "XAER_RMERR";
case XAException.XAER_RMFAIL:
return "XAER_RMFAIL";
default:
return "UNKNOWN";
}
}
/**
* Return a String representation of a flag.
*/
public static String getXAFlag(int flag) {
switch (flag) {
case XAResource.TMENDRSCAN:
return "TMENDRSCAN";
case XAResource.TMFAIL:
return "TMFAIL";
case XAResource.TMJOIN:
return "TMJOIN";
case XAResource.TMNOFLAGS:
return "TMNOFLAGS";
case XAResource.TMONEPHASE:
return "TMONEPHASE";
case XAResource.TMRESUME:
return "TMRESUME";
case XAResource.TMSTARTRSCAN:
return "TMSTARTRSCAN";
case XAResource.TMSUCCESS:
return "TMSUCCESS";
case XAResource.TMSUSPEND:
return "TMSUSPEND";
default:
return "UNKNOWN";
}
}
/**
* Print the Transaction object in a debugger friendly manner
*/
public String toString() {
return "Transaction " + currentTransactionNumber
+ " xid " + xid + " in thread " + currentThreadName +
(currentThreadName.equals(Thread.currentThread().getName())?"":
" current= " + Thread.currentThread().getName());
}
}