/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2000-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.messaging.bridge.service.jms.tx;
import java.util.Map;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.logging.Logger;
import java.util.logging.Level;
import javax.transaction.*;
import javax.transaction.xa.XAResource;
import java.lang.IllegalStateException;
import java.lang.SecurityException;
import com.sun.messaging.bridge.service.jms.tx.log.GlobalXidDecision;
import com.sun.messaging.bridge.service.jms.tx.log.BranchXidDecision;
import com.sun.messaging.bridge.service.jms.tx.log.LogRecord;
import com.sun.messaging.bridge.service.jms.tx.log.TxLog;
/**
* Implements JTA Transaction interface
*
* @author amyk
*/
public class TransactionImpl implements Transaction {
private Logger _logger = null;
private int _status = Status.STATUS_NO_TRANSACTION;
private GlobalXid _gxid = null;
private TransactionManagerImpl _tm = null;
private ArrayList<BranchXid> _seenBranchXids = new ArrayList<BranchXid>();
private ArrayList<XAResource> _associatedXAResources = new ArrayList<XAResource>();
private Map<XAResource, XAParticipant> _participants =
new LinkedHashMap<XAResource, XAParticipant>();
private byte _branchCount = 0;
private int _maxBranches;
private TxLog _txlog = null;
public TransactionImpl(GlobalXid xid, TransactionManagerImpl tm) throws SystemException {
if (xid == null) {
throw new SystemException("null xid");
}
_tm = tm;
_logger = tm.getLogger();
_txlog = tm.getTxLog();
_gxid = xid;
_maxBranches = tm.getMaxBranches();
_branchCount = 0;
_status = Status.STATUS_ACTIVE;
}
/**
* 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 all 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 transaction in the
* target object is inactive.
*
* @exception SystemException Thrown if the transaction manager
* encounters an unexpected error condition.
*/
public void commit() throws RollbackException,
HeuristicMixedException, HeuristicRollbackException,
SecurityException, IllegalStateException, SystemException {
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, _tm+" commit "+this);
}
if (_status == Status.STATUS_MARKED_ROLLBACK) {
rollback();
throw new RollbackException(toString());
}
if (_status != Status.STATUS_ACTIVE) {
throw new IllegalStateException(toString());
}
if (_associatedXAResources.size() != 0) {
throw new IllegalStateException(
"There are undelisted XAResources "+_associatedXAResources+" in "+this);
}
boolean onePhase = false;
if (_participants.size() == 1) {
onePhase = true;
}
if (!onePhase) {
_status = Status.STATUS_PREPARING;
XAParticipant party = null;
boolean preparedOne = false;
for (Map.Entry<XAResource, XAParticipant> pair: _participants.entrySet()) {
party = pair.getValue();
try {
party.prepare();
preparedOne = true;
} catch (IllegalStateException e) {
if (!preparedOne) throw e;
setRollbackOnly();
rollback();
RollbackException ex = new RollbackException(toString()+": "+e.getMessage());
ex.initCause(e);
throw ex;
} catch (Throwable t) {
setRollbackOnly();
if (!(t instanceof RollbackException) &&
!(t instanceof SystemException)) {
_logger.log(Level.SEVERE, "Unexpected exception on prepare from "+party, t);
}
rollback();
RollbackException ex = new RollbackException(t.getMessage());
ex.initCause(t);
throw ex;
}
}
_status = Status.STATUS_PREPARED;
}
if (_status == Status.STATUS_MARKED_ROLLBACK) {
rollback();
throw new RollbackException(toString());
}
if (!onePhase) {
try {
LogRecord lr = new LogRecord(_gxid, _participants.values(), GlobalXidDecision.COMMIT);
_txlog.logGlobalDecision(lr);
} catch (Throwable t) {
setRollbackOnly();
_logger.log(Level.SEVERE, "Unable to log commit decision "+this, t);
rollback();
RollbackException ex = new RollbackException(toString()+": "+t.getMessage());
ex.initCause(t);
throw ex;
}
_status = Status.STATUS_PREPARED;
}
_status = Status.STATUS_COMMITTING;
Exception ex = null;
XAParticipant party = null;
boolean committedOne = false;
for (Map.Entry<XAResource, XAParticipant> pair: _participants.entrySet()) {
party= pair.getValue();
try {
party.commit(onePhase);
committedOne = true;
} catch (IllegalStateException e) {
if (!committedOne) throw e;
ex = e;
continue;
} catch (RollbackException e) {
if (!onePhase) {
_logger.log(Level.SEVERE,
"Unexpected RollbackException on 2-phase commit from "+party, e);
ex = new HeuristicMixedException(e.getMessage());
ex.initCause(e);
continue;
} else {
throw e;
}
} catch (HeuristicCommitException e) {
try {
LogRecord lr = new LogRecord(_gxid, _participants.values(), GlobalXidDecision.COMMIT);
BranchXid bxid = party.getBranchXid();
lr.setBranchHeurCommit(bxid);
_txlog.logHeuristicBranch(bxid, lr);
} catch (Throwable t) {
_logger.log(Level.WARNING, "Unable to log heuristic commit from "+party, t);
}
continue;
} catch (HeuristicMixedException e) {
ex = e;
try {
LogRecord lr = new LogRecord(_gxid, _participants.values(), GlobalXidDecision.COMMIT);
BranchXid bxid = party.getBranchXid();
lr.setBranchHeurMixed(bxid);
_txlog.logHeuristicBranch(bxid, lr);
} catch (Throwable t) {
_logger.log(Level.WARNING, "Unable to log heuristic mixed from "+party, t);
}
continue;
} catch (HeuristicRollbackException e) {
ex = e;
try {
LogRecord lr = new LogRecord(_gxid, _participants.values(), GlobalXidDecision.COMMIT);
BranchXid bxid = party.getBranchXid();
lr.setBranchHeurRollback(bxid);
_txlog.logHeuristicBranch(bxid, lr);
} catch (Throwable t) {
_logger.log(Level.WARNING, "Unable to log heuristic rollback from "+party, t);
}
continue;
} catch (Throwable t) {
if (!(t instanceof SystemException)) {
_logger.log(Level.SEVERE, "Unexpected exception on commit from "+party, t);
ex = new SystemException(t.getMessage());
ex.initCause(t);
} else {
ex = (SystemException)t;
}
continue;
}
}
_status = Status.STATUS_COMMITTED;
if (ex == null) {
_status = Status.STATUS_NO_TRANSACTION;
if (!onePhase) {
try {
_txlog.remove(_gxid);
} catch (Throwable t) {
_logger.log(Level.WARNING, "Exception in removing comitted TM log record "+_gxid, t);
}
}
}
if (ex != null) {
if (ex instanceof SystemException) throw (SystemException)ex;
if (ex instanceof HeuristicMixedException) throw (HeuristicMixedException)ex;
if (ex instanceof IllegalStateException) throw (IllegalStateException)ex;
if (ex instanceof HeuristicRollbackException) throw (HeuristicRollbackException)ex;
SystemException uex = new SystemException(ex.getMessage()); //should not hapen
uex.initCause(ex);
throw uex;
}
}
/**
* Disassociate the resource specified from the transaction associated
* with the target Transaction object.
*
* @param xaRes The XAResource object associated with the resource
* (connection).
*
* @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.
*
* @return <i>true</i> if the resource was delisted successfully; otherwise
* <i>false</i>.
*
*/
public boolean delistResource(XAResource xaRes, int flag)
throws IllegalStateException, SystemException {
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, _tm+" dellistResource "+xaRes+" from "+this);
}
if (_status != Status.STATUS_ACTIVE &&
_status != Status.STATUS_MARKED_ROLLBACK) {
throw new IllegalStateException(toString());
}
XAParticipant party = _participants.get(xaRes);
if (party == null) {
throw new IllegalStateException(
"XAResource "+xaRes+" had not associated to "+this);
}
int flags = flag;
if (_status == Status.STATUS_MARKED_ROLLBACK) {
flags = XAResource.TMFAIL;
}
try {
party.end(flags);
_associatedXAResources.remove(xaRes);
return true;
} catch (IllegalStateException e) {
throw e;
} catch (RollbackException e) {
setRollbackOnly();
return true;
} catch (Throwable t) {
setRollbackOnly();
if (t instanceof SystemException) throw (SystemException)t;
_logger.log(Level.SEVERE, "Unexpected exception occurred on end from "+party, t);
SystemException ex = new SystemException(t.getMessage());
ex.initCause(t);
throw ex;
}
}
/**
* Enlist the resource specified with the transaction associated with the
* target Transaction object.
*
* @param xaRes The XAResource object associated with the resource
* (connection).
*
* @return <i>true</i> if the resource was enlisted successfully; otherwise
* <i>false</i>.
*
* @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 the 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 {
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, _tm+" enlistResource "+xaRes+" to "+this);
}
if (_status == Status.STATUS_MARKED_ROLLBACK) {
throw new RollbackException(toString());
}
if (_status != Status.STATUS_ACTIVE) {
throw new IllegalStateException(toString());
}
if (_participants.get(xaRes) != null) {
throw new IllegalStateException("XAResource "+xaRes+" already enlisted in "+this);
}
String rm = null;
try {
rm = _tm.getRM(xaRes);
} catch (Exception e) {
String emsg = "Enlist XAResource "+xaRes+" failed";
_logger.log(Level.SEVERE, emsg, e);
SystemException ex = new SystemException(emsg);
ex.initCause(e);
throw ex;
}
if (rm == null) {
setRollbackOnly();
throw new SystemException(
"No RM is registered for XAResource "+xaRes);
}
BranchXid bxid = _tm.genBranchXid(_gxid, rm, xaRes.getClass().getName(), getBranchCount());
if (_seenBranchXids.contains(bxid)) {
throw new SystemException(
"Unexpected duplicated branch "+bxid+" for RM "+rm+" in "+_gxid);
}
_seenBranchXids.add(bxid);
XAParticipant party = new XAParticipant(rm, xaRes, bxid);
party.setLogger(_logger);
_participants.put(xaRes, party);
_associatedXAResources.add(xaRes);
try {
party.start(XAResource.TMNOFLAGS);
return true;
} catch (IllegalStateException e) {
throw e;
} catch (Throwable t) {
setRollbackOnly();
if (t instanceof RollbackException) throw (RollbackException)t;
if (t instanceof SystemException) throw (SystemException)t;
_logger.log(Level.SEVERE, "Unexpected exception occurred on start from "+party, t);
SystemException ex = new SystemException(t.getMessage());
ex.initCause(t);
throw ex;
}
}
private synchronized byte getBranchCount() throws SystemException {
_branchCount++;
if (_branchCount > _maxBranches) {
throw new SystemException(
"Number of branches "+_branchCount+" exceeded max "+_maxBranches);
}
return _branchCount;
}
/**
* Obtain the status of the transaction associated with the target
* Transaction object.
*
* @return The transaction status. If no transaction is associated with
* the target object, this method returns the
* Status.NoTransaction value.
*
* @exception SystemException Thrown if the transaction manager
* encounters an unexpected error condition.
*
*/
public int getStatus() throws SystemException {
return _status;
}
/**
* Register a synchronization object for the transaction currently
* associated with the target object. The transction manager invokes
* the beforeCompletion method prior to starting the two-phase 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 the 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 {
throw new SystemException("operation not supported");
}
/**
* Rollback the transaction represented by this Transaction object.
*
* @exception IllegalStateException Thrown if the transaction in the
* target object is in the prepared state or the transaction is
* inactive.
*
* @exception SystemException Thrown if the transaction manager
* encounters an unexpected error condition.
*
*/
public void rollback() throws IllegalStateException, SystemException {
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, _tm+" rollback "+this);
}
if (_status != Status.STATUS_ACTIVE &&
_status != Status.STATUS_MARKED_ROLLBACK) {
throw new IllegalStateException(toString());
}
if (_associatedXAResources.size() > 0) {
XAResource[] xars = _associatedXAResources.toArray(new XAResource[]{});
for (int i = 0; i < xars.length; i++) {
try {
delistResource(xars[i], XAResource.TMFAIL);
} catch (IllegalStateException e) {
throw e;
} catch (Throwable t) {
_logger.log(Level.WARNING, "Unable to delist resource "+xars[i]+" for rollback", t);
}
}
}
_status = Status.STATUS_ROLLING_BACK;
Exception ex = null;
XAParticipant party = null;
boolean rolledbackOne = false;
for (Map.Entry<XAResource, XAParticipant> pair: _participants.entrySet()) {
party = pair.getValue();
try {
party.rollback();
rolledbackOne = true;
} catch (IllegalStateException e) {
if (!rolledbackOne) throw e;
ex = new SystemException(e.getMessage());
ex.initCause(e);
continue;
} catch (HeuristicCommitException e) {
ex = e;
try {
LogRecord lr = new LogRecord(_gxid, _participants.values(), GlobalXidDecision.ROLLBACK);
BranchXid bxid = party.getBranchXid();
lr.setBranchHeurCommit(bxid);
_txlog.logHeuristicBranch(bxid, lr);
} catch (Throwable t) {
_logger.log(Level.WARNING, "Unable to log heuristic commit from "+party, t);
}
continue;
} catch (HeuristicRollbackException e) {
try {
LogRecord lr = new LogRecord(_gxid, _participants.values(), GlobalXidDecision.ROLLBACK);
BranchXid bxid = party.getBranchXid();
lr.setBranchHeurRollback(bxid);
_txlog.logHeuristicBranch(bxid, lr);
} catch (Throwable t) {
_logger.log(Level.WARNING, "Unable to log heuristic rollback from "+party, t);
}
continue;
} catch (HeuristicMixedException e) {
ex = e;
try {
LogRecord lr = new LogRecord(_gxid, _participants.values(), GlobalXidDecision.ROLLBACK);
BranchXid bxid = party.getBranchXid();
lr.setBranchHeurMixed(bxid);
_txlog.logHeuristicBranch(bxid, lr);
} catch (Throwable t) {
_logger.log(Level.WARNING, "Unable to log heuristic mixed from "+party, t);
}
continue;
} catch (Throwable t) {
if (!(t instanceof SystemException)) {
ex = new SystemException(t.getMessage());
ex.initCause(t);
} else {
ex = (SystemException)t;
}
continue;
}
}
_status = Status.STATUS_ROLLEDBACK;
if (ex == null) _status = Status.STATUS_NO_TRANSACTION;
if (ex != null) {
if (ex instanceof SystemException) throw (SystemException)ex;
if (ex instanceof IllegalStateException) throw (IllegalStateException)ex;
SystemException uex = new SystemException(ex.getMessage());
uex.initCause(ex);
throw uex;
}
}
/**
* Modify the transaction associated with the target object such that
* the only possible outcome of the transaction is to roll back the
* transaction.
*
* @exception IllegalStateException Thrown if the target object is
* not associated with any transaction.
*
* @exception SystemException Thrown if the transaction manager
* encounters an unexpected error condition.
*
*/
public void setRollbackOnly() throws IllegalStateException, SystemException {
_status = Status.STATUS_MARKED_ROLLBACK;
}
private String statusString(int status) {
switch (status) {
case Status.STATUS_ACTIVE: return "STATUS_ACTIVE";
case Status.STATUS_COMMITTED: return "STATUS_COMMITTED";
case Status.STATUS_COMMITTING: return "STATUS_COMMITTING";
case Status.STATUS_MARKED_ROLLBACK: return "STATUS_MARKED_ROLLBACK";
case Status.STATUS_NO_TRANSACTION: return "STATUS_NO_TRANSACTION";
case Status.STATUS_PREPARED: return "STATUS_PREPARED";
case Status.STATUS_PREPARING: return "STATUS_PREPARING";
case Status.STATUS_ROLLEDBACK: return "STATUS_ROLLEDBACK";
case Status.STATUS_ROLLING_BACK: return "STATUS_ROLLING_BACK";
case Status.STATUS_UNKNOWN: return "STATUS_UNKNOWN";
default: return "STATUS_UNKNOWN";
}
}
public boolean equals(Object o) {
if (!(o instanceof TransactionImpl)) return false;
TransactionImpl that = (TransactionImpl)o;
if (this == that) return true;
return _gxid.equals(that._gxid);
}
public int hashCode() {
return _gxid.hashCode();
}
public String toString() {
return _gxid+"["+statusString(_status)+"]";
}
protected Logger getLogger() {
return _logger;
}
public String getGXidString() {
return _gxid.toString();
}
}