/**
*
* Copyright 2003-2004 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.geronimo.transaction.context;
import java.util.Iterator;
import java.util.Map;
import java.util.ArrayList;
import javax.transaction.SystemException;
import javax.transaction.Status;
import javax.transaction.Transaction;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.RollbackException;
import javax.transaction.Synchronization;
import javax.transaction.xa.XAResource;
import org.apache.geronimo.transaction.ExtendedTransactionManager;
import org.apache.geronimo.transaction.ConnectionReleaser;
import org.apache.geronimo.transaction.InstanceContext;
/**
* @version $Rev: 355877 $ $Date: 2005-12-10 18:48:27 -0800 (Sat, 10 Dec 2005) $
*/
abstract class InheritableTransactionContext extends AbstractTransactionContext {
private final ExtendedTransactionManager txnManager;
private Transaction transaction;
private boolean threadAssociated = false;
protected InheritableTransactionContext(ExtendedTransactionManager txnManager) {
this.txnManager = txnManager;
}
protected InheritableTransactionContext(ExtendedTransactionManager txnManager, Transaction transaction) {
this.txnManager = txnManager;
this.transaction = transaction;
}
void begin(long transactionTimeoutMilliseconds) throws SystemException, NotSupportedException {
if (transaction != null) {
throw new SystemException("Context is already associated with a transaction");
}
transaction = txnManager.begin(transactionTimeoutMilliseconds);
threadAssociated = true;
}
boolean isThreadAssociated() {
return threadAssociated;
}
public Transaction getTransaction() {
return transaction;
}
public boolean isInheritable() {
return true;
}
public boolean isActive() {
if (transaction == null) {
return false;
}
try {
int status = transaction.getStatus();
return status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK;
} catch (SystemException e) {
return false;
}
}
public boolean enlistResource(XAResource xaResource) throws RollbackException, SystemException {
if (transaction == null) {
throw new IllegalStateException("There is no transaction in progress.");
}
return transaction.enlistResource(xaResource);
}
public boolean delistResource(XAResource xaResource, int flag) throws SystemException {
if (transaction == null) {
throw new IllegalStateException("There is no transaction in progress.");
}
boolean success = transaction.delistResource(xaResource, flag);
if (!success) {
transaction.setRollbackOnly();
}
return success;
}
public void registerSynchronization(Synchronization synchronization) throws RollbackException, SystemException {
if (transaction == null) {
throw new IllegalStateException("There is no transaction in progress.");
}
transaction.registerSynchronization(synchronization);
}
public boolean getRollbackOnly() throws SystemException {
if (transaction == null) {
throw new IllegalStateException("There is no transaction in progress.");
}
int status = transaction.getStatus();
return (status == Status.STATUS_MARKED_ROLLBACK ||
status == Status.STATUS_ROLLEDBACK ||
status == Status.STATUS_ROLLING_BACK);
}
public void setRollbackOnly() throws IllegalStateException, SystemException {
if (transaction == null) {
throw new IllegalStateException("There is no transaction in progress.");
}
transaction.setRollbackOnly();
}
public void suspend() throws SystemException {
Transaction suspendedTransaction = txnManager.suspend();
if (transaction != suspendedTransaction) {
throw new SystemException("Suspend did not return our transaction: expectedTx=" + transaction + ", suspendedTx=" + suspendedTransaction);
}
threadAssociated = false;
}
public void resume() throws SystemException, InvalidTransactionException {
txnManager.resume(transaction);
threadAssociated = true;
}
public boolean commit() throws HeuristicMixedException, HeuristicRollbackException, SystemException, RollbackException {
return complete();
}
public void rollback() throws SystemException {
setRollbackOnly();
try {
complete();
} catch (SystemException e) {
throw e;
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
throw (SystemException) new SystemException("After commit of container transaction failed").initCause(e);
}
}
private boolean complete() throws HeuristicMixedException, HeuristicRollbackException, SystemException, RollbackException {
if (transaction == null) {
throw new IllegalStateException("There is no transaction in progress.");
}
boolean wasCommitted = false;
try {
if (isRolledback()) {
return false;
}
flushState();
if (isRolledback()) {
return false;
}
// todo we need to flush anyone enrolled during before and then call before on any flushed...
beforeCommit();
if (isRolledback()) {
return false;
}
// verify our tx is the current tx associated with the thread
// this is really only an error case and should never happen, but just to be sure double check
// immedately before committing using the transaction manager
Transaction currentTransaction = txnManager.getTransaction();
if (currentTransaction != transaction) {
throw new SystemException("An unknown transaction is currently associated with the thread: expectedTx=" + transaction + ", currentTx=" + currentTransaction);
}
txnManager.commit();
wasCommitted = true;
} catch (Throwable t) {
rollbackAndThrow("Unable to commit container transaction", t);
} finally {
transaction = null;
try {
afterCommit(wasCommitted);
} catch (Throwable e) {
rollbackAndThrow("After commit of container transaction failed", e);
} finally {
unassociateAll();
connectorAfterCommit();
threadAssociated = false;
}
}
return wasCommitted;
}
private void beforeCommit() throws Throwable {
// @todo allow for enrollment during pre-commit
ArrayList toFlush = getAssociatedContexts();
for (Iterator i = toFlush.iterator(); i.hasNext();) {
InstanceContext context = (InstanceContext) i.next();
if (!context.isDead()) {
context.beforeCommit();
}
}
}
private void afterCommit(boolean status) throws Throwable {
Throwable firstThrowable = null;
ArrayList toFlush = getAssociatedContexts();
for (Iterator i = toFlush.iterator(); i.hasNext();) {
InstanceContext context = (InstanceContext) i.next();
if (!context.isDead()) {
try {
context.afterCommit(status);
} catch (Throwable e) {
if (firstThrowable == null) {
firstThrowable = e;
}
}
}
}
if (firstThrowable instanceof Error) {
throw (Error) firstThrowable;
} else if (firstThrowable instanceof Exception) {
throw (Exception) firstThrowable;
} else if (firstThrowable != null) {
throw (SystemException) new SystemException().initCause(firstThrowable);
}
}
private void connectorAfterCommit() {
if (managedConnections != null) {
for (Iterator entries = managedConnections.entrySet().iterator(); entries.hasNext();) {
Map.Entry entry = (Map.Entry) entries.next();
ConnectionReleaser key = (ConnectionReleaser) entry.getKey();
key.afterCompletion(entry.getValue());
}
//If BeanTransactionContext never reuses the same instance for sequential BMT, this
//clearing is unnecessary.
managedConnections.clear();
}
}
private boolean isRolledback() throws SystemException {
int status;
try {
status = transaction.getStatus();
} catch (SystemException e) {
transaction.rollback();
throw e;
}
if (status == Status.STATUS_MARKED_ROLLBACK) {
// verify our tx is the current tx associated with the thread
// this is really only an error case and should never happen, but just to be sure double check
// immedately before committing using the transaction manager
Transaction currentTransaction = txnManager.getTransaction();
if (currentTransaction != transaction) {
throw new SystemException("An unknown transaction is currently associated with the thread: expectedTx=" + transaction + ", currentTx=" + currentTransaction);
}
// we need to rollback
txnManager.rollback();
return true;
} else if (status == Status.STATUS_ROLLEDBACK ||
status == Status.STATUS_ROLLING_BACK) {
// already rolled back
return true;
}
return false;
}
private void rollbackAndThrow(String message, Throwable throwable) throws HeuristicMixedException, HeuristicRollbackException, SystemException, RollbackException {
try {
if (txnManager.getStatus() != Status.STATUS_NO_TRANSACTION) {
txnManager.rollback();
}
} catch (Throwable t) {
log.error("Unable to roll back transaction", t);
}
try {
// make doubly sure our transaction was rolled back
// this can happen when there was a junk transaction on the thread
int status = transaction.getStatus();
if (status != Status.STATUS_ROLLEDBACK &&
status != Status.STATUS_ROLLING_BACK) {
transaction.rollback();
}
} catch (Throwable t) {
log.error("Unable to roll back transaction", t);
}
if (throwable instanceof HeuristicMixedException) {
throw (HeuristicMixedException) throwable;
} else if (throwable instanceof HeuristicRollbackException) {
throw (HeuristicRollbackException) throwable;
} else if (throwable instanceof RollbackException) {
throw (RollbackException) throwable;
} else if (throwable instanceof SystemException) {
throw (SystemException) throwable;
} else if (throwable instanceof Error) {
throw (Error) throwable;
} else if (throwable instanceof RuntimeException) {
throw (RuntimeException) throwable;
} else {
throw (SystemException) new SystemException(message).initCause(throwable);
}
}
}