mq5.0-source.main.mq-client.src.main.java.com.sun.messaging.jmq.jmsclient.XAResourceImpl Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2000-2012 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.
*/
/*
* @(#)XAResourceImpl.java 1.16 03/14/08
*/
package com.sun.messaging.jmq.jmsclient;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jms.*;
import javax.transaction.xa.*;
import com.sun.messaging.jmq.io.Status;
import com.sun.messaging.jmq.util.JMQXid;
import com.sun.messaging.jmq.jmsclient.resources.ClientResources;
/**
* JMQ implementation class for XAResource
*
* The XAResource interface is a Java mapping of the industry standard
* XA interface based on the X/Open CAE Specification (Distributed
* Transaction Processing: The XA Specification).
*
*
The XA interface defines the contract between a Resource Manager
* and a Transaction Manager in a distributed transaction processing
* (DTP) environment. A JDBC driver or a JMS provider implements
* this interface to support the association between a global transaction
* and a database or message service connection.
*
*
The XAResource interface can be supported by any transactional
* resource that is intended to be used by application programs in an
* environment where transactions are controlled by an external
* transaction manager. An example of such a resource is a database
* management system. An application may access data through multiple
* database connections. Each database connection is enlisted with
* the transaction manager as a transactional resource. The transaction
* manager obtains an XAResource for each connection participating
* in a global transaction. The transaction manager uses the
* start
method
* to associate the global transaction with the resource, and it uses the
* end
method to disassociate the transaction from
* the resource. The resource
* manager is responsible for associating the global transaction to all
* work performed on its data between the start and end method invocations.
*
*
At transaction commit time, the resource managers are informed by
* the transaction manager to prepare, commit, or rollback a transaction
* according to the two-phase commit protocol.
*
* @see javax.transaction.xa.XAResource
*/
public class XAResourceImpl implements XAResource, XAResourceForJMQ {
private SessionImpl _session;
private ConnectionImpl _connection;
private Transaction _transaction = null;
//Use Unique ID from broker
private long resourceManagerId = 0L;
private int transactionTimeout = 0; //transactions do not timeout
//HACC -- transaction state table
protected final static Hashtable xaTable = new Hashtable();
//transaction state (xaState) stored in the xaTable
public static final Integer XA_START = Integer.valueOf(Transaction.TRANSACTION_STARTED);
public static final Integer XA_END = Integer.valueOf(Transaction.TRANSACTION_ENDED);
public static final Integer XA_PREPARE = Integer.valueOf(Transaction.TRANSACTION_PREPARED);
public static final Integer XA_ROLLBACK_ONLY = Integer.valueOf(Transaction.TRANSACTION_ROLLBACK_ONLY);
/**
* Possible states of this XAResource
*/
public static final int CREATED = 0; // after first creation, or after commit() or rollback()
public static final int STARTED = 1; // after start() called
public static final int FAILED = 2; // after end(fail) called
public static final int INCOMPLETE = 3; // after end(suspend) called
public static final int COMPLETE = 4; // after end (success) called
public static final int PREPARED = 5; // after prepare() called
/**
* State of this XAresource
*/
private int resourceState = CREATED;
//use this property to turn off xa transaction tracking
public static boolean turnOffXATracking = Boolean.getBoolean("imq.ra.turnOffXATracking");
//set to true by default - track xa transaction state
public static boolean XATracking = !turnOffXATracking;
//end HACC
//cache last self rolled back txn during prepare() failure
private static Map lastInternalRBCache =
Collections.synchronizedMap(new LinkedHashMap());
//true if this XAResource has put entry into lastInternalRBCache
private boolean lastInternalRB = false;
private ConnectionConsumerImpl connectionConsumer = null;
JMQXid currentJMQXid = null;
/* Loggers */
private static transient final String _className = "com.sun.messaging.jmq.jmsclient.XAResourceImpl";
private static transient final String _lgrName = "com.sun.messaging.jmq.jmsclient.XAResourceImpl";
private static transient final Logger _logger = Logger.getLogger(_lgrName);
private static transient final String _lgrPrefix = "XAResourceImpl: ";
public XAResourceImpl(SessionImpl session) throws JMSException {
this._session = session;
this._connection = session.connection;
resourceManagerId = _connection.protocolHandler.generateUID();
if (Debug.debug) {
Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:new:RMId="+resourceManagerId);
}
}
protected void setConnectionConsumer(ConnectionConsumerImpl cc) {
connectionConsumer = cc;
}
protected ConnectionConsumerImpl getConnectionConsumer() {
return connectionConsumer;
}
/**
* Commits the global transaction specified by xid.
*
* @param foreignXid A global transaction identifier
*
* @param onePhase If true, the resource manager should use a one-phase
* commit protocol to commit the work done on behalf of xid.
*
* @exception XAException An error has occurred. Possible XAExceptions
* are XA_HEURHAZ, XA_HEURCOM, XA_HEURRB, XA_HEURMIX, XAER_RMERR,
* XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or XAER_PROTO.
*
* If the resource manager did not commit the transaction and the
* parameter onePhase is set to true, the resource manager may throw
* one of the XA_RB* exceptions. Upon return, the resource manager has
* rolled back the branch's work and has released all held resources.
*/
public void commit(Xid foreignXid, boolean onePhase) throws XAException {
boolean insyncstate = false;
if (_logger.isLoggable(Level.FINE)){
_logger.fine(_lgrPrefix+"("+this.hashCode()+") Commit "+printXid(foreignXid)+" (onePhase="+onePhase+")");
}
//convert to jmq xid
JMQXid jmqXid = new JMQXid(foreignXid);
if (Debug.debug) {
Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:commit:onePhase="+onePhase+"\txid=\n"+jmqXid.toString());
}
// HACC
if (this._session.isRollbackOnly) {
Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:prepare:forcing Rollback due to:" + this._session.rollbackCause.getMessage());
//Debug.printStackTrace(this._session.rollbackCause);
XAException xae = new XAException(XAException.XAER_RMFAIL);
xae.initCause(this._session.rollbackCause);
throw xae;
}
try {
try {
//Re-open if needed
//_connection.openConnection(true);
//Bug6664278 - must synced
_connection.openConnectionFromRA (true);
} catch (Exception oce) {
//XXX:RFE:Configuration needed
//For now-retry once after a sec
try {
Thread.sleep(1000);
} catch (Exception e) {}
//_connection.openConnection(true);
//Bug6664278 - must synced
_connection.openConnectionFromRA (true);
}
if (_transaction == null) {
if (Debug.debug) {
Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:commit:using 0 as txnID");
}
if ( _connection.isConnectedToHABroker) {
HACommit(foreignXid, jmqXid, onePhase, insyncstate);
} else {
_connection.getProtocolHandler().commit(0L, (onePhase ? XAResource.TMONEPHASE : XAResource.TMNOFLAGS), jmqXid);
}
} else {
if (Debug.debug) {
Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:commit:using real txnID");
}
//Setup protocol Handler
_transaction.setProtocolHandler(_connection.getProtocolHandler());
//Need to ack msgs if this session has not been closed.
if (!_session.isClosed) {
//set sync flag
_session.setInSyncState();
insyncstate = true;
//ack all messages received in this session
_session.receiveCommit();
}
//Perform XA commit
if ( this._connection.isConnectedToHABroker) {
this.HACommit(foreignXid, jmqXid, onePhase, insyncstate);
_session.clearUnackedMessageQ();
} else {
_transaction.commitXATransaction(jmqXid, onePhase);
}
}
//_connection.closeConnection();
//Bug6664278 - must synced
_connection.closeConnectionFromRA();
} catch (Exception jmse) {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:commitXAException");
Debug.printStackTrace(jmse);
if (jmse instanceof XAException) {
throw (XAException)jmse;
}
XAException xae = new XAException(XAException.XAER_RMFAIL);
xae.initCause(jmse);
throw xae;
} finally {
// finish up this resource and any others joined to it in this transaction
boolean throwExceptionIfNotFound = false;
XAResourceImpl[] resources = XAResourceMap.getXAResources(jmqXid,throwExceptionIfNotFound);
for (int i = 0; i < resources.length; i++) {
XAResourceImpl xari = resources[i];
xari.clearTransactionInfo();
xari.finishCommit();
}
XAResourceMap.unregister(jmqXid);
if (insyncstate) {
_session.releaseInSyncState();
}
}
}
protected void close(){
if (currentJMQXid!=null) {
SessionImpl.sessionLogger.log(Level.INFO, "Closing XA session with a transaction pending");
// unregister resource to avoid memory leak
XAResourceMap.unregisterResource(this, currentJMQXid);
clearTransactionInfo();
currentJMQXid=null;
}
connectionConsumer = null;
}
protected void finishCommit(){
_session.switchOffXATransaction();
currentJMQXid=null;
connectionConsumer = null;
}
protected void finishRollback(){
//HACC -- must clear this flag.
_session.failoverOccurred = false;
_session.switchOffXATransaction();
//HACC
_session.isRollbackOnly = false;
_session.rollbackCause = null;
currentJMQXid=null;
connectionConsumer = null;
}
/**
* Ends the work performed on behalf of a transaction branch.
* The resource manager disassociates the XA resource from the
* transaction branch specified and lets the transaction
* complete.
*
*
If TMSUSPEND is specified in the flags, the transaction branch
* is temporarily suspended in an incomplete state. The transaction
* context is in a suspended state and must be resumed via the
* start
method with TMRESUME specified.
*
* If TMFAIL is specified, the portion of work has failed.
* The resource manager may mark the transaction as rollback-only
*
* If TMSUCCESS is specified, the portion of work has completed
* successfully.
*
* @param foreignXid A global transaction identifier that is the same as
* the identifier used previously in the start
method.
*
* @param flags One of TMSUCCESS, TMFAIL, or TMSUSPEND.
*
* @exception XAException An error has occurred. Possible XAException
* values are XAER_RMERR, XAER_RMFAILED, XAER_NOTA, XAER_INVAL,
* XAER_PROTO, or XA_RB*.
*/
public void end(Xid foreignXid, int flags) throws XAException {
if (_logger.isLoggable(Level.FINE)){
_logger.fine(_lgrPrefix+"XAResourceImpl ("+this.hashCode()+") End "+printXid(foreignXid)+printFlags(flags));
}
//convert to jmq xid
JMQXid jmqXid = new JMQXid(foreignXid);
if (_connection._isClosed()) {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:end:XAException");
XAException xae = new XAException(XAException.XAER_RMFAIL);
throw xae;
}
// update the resource state
if (isFail(flags)){
resourceState=FAILED;
} else if (isSuspend(flags)){
resourceState=INCOMPLETE;
} else {
resourceState=COMPLETE;
}
// now decide based on the resource state whether to send a real
// END packet, a noop END packet or to ignore the END packet altogether.
if (resourceState==COMPLETE) {
// This XAResource is complete. Send a real END packet if all
// other resources joined to this txn are complete, otherwise
// send a noop END packet to ensure that work associated with
// this XAResource has completed on the broker. See bug 12364646.
boolean allComplete = true;
XAResourceImpl[] resources = XAResourceMap.getXAResources(jmqXid,false);
for (int i = 0; i < resources.length; i++) {
XAResourceImpl xari = resources[i];
if (!xari.isComplete()){
allComplete = false;
}
}
if (allComplete){
// All resources complete. Send real END packet.
sendEndToBroker(flags, false, jmqXid);
} else {
// One or more resources are not complete. Send a noop END
// packet to the broker.
sendEndToBroker(flags, true, jmqXid);
}
} else if (resourceState==FAILED){
// This resource has failed. Send a real END packet regardless
// of the state of any other joined resources.
sendEndToBroker(flags, false, jmqXid);
} else if (resourceState==INCOMPLETE){
// Don't send the END to the broker. See Glassfish issue 7118.
}
}
private void sendEndToBroker(int flags, boolean jmqnoop, JMQXid jmqXid) throws XAException {
try {
//System.out.println("MQRA:XAR4RA:end:sending 0L:tid="+transactionID+" xid="+jmqXid.toString());
_connection.getProtocolHandler().endTransaction(0L, jmqnoop, flags, jmqXid);
} catch (JMSException jmse) {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:end:XAException");
Debug.printStackTrace(jmse);
XAException xae = new XAException(XAException.XAER_RMFAIL);
xae.initCause(jmse);
throw xae;
}
}
/**
* Tells the resource manager to forget about a heuristically
* completed transaction branch.
*
* @param foreignXid A global transaction identifier.
*
* @exception XAException An error has occurred. Possible exception
* values are XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or
* XAER_PROTO.
*/
public void forget(Xid foreignXid) throws XAException {
//iMQ does not support heuristically completed transaction branches
//This is a NOP
//convert to jmq xid
//JMQXid jmqXid = new JMQXid(foreignXid);
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:forget:txid=\n"+jmqXid.toString());
// Despite the above, clean up currentJMQXid and the XAResourceMap
JMQXid jmqXid = new JMQXid(foreignXid);
XAResourceMap.unregister(jmqXid);
if (currentJMQXid!=null){
if (currentJMQXid.equals(jmqXid)){
currentJMQXid=null;
connectionConsumer = null;
clearTransactionInfo();
}
}
}
/**
* Obtains the current transaction timeout value set for this
* XAResource instance. If XAResource.setTransactionTimeout
* was not used prior to invoking this method, the return value
* is the default timeout set for the resource manager; otherwise,
* the value used in the previous setTransactionTimeout
* call is returned.
*
* @return the transaction timeout value in seconds.
*
* @exception XAException An error has occurred. Possible exception
* values are XAER_RMERR and XAER_RMFAIL.
*/
public int getTransactionTimeout() throws XAException {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:getTransactionTimeout");
return transactionTimeout;
}
/**
* This method is called to determine if the resource manager
* instance represented by the target object is the same as the
* resource manager instance represented by the parameter foreignXaRes.
*
* @param foreignXaRes An XAResource object whose resource manager instance
* is to be compared with the resource manager instance of the
* target object.
*
* @return true if it's the same RM instance; otherwise
* false.
*
* @exception XAException An error has occurred. Possible exception
* values are XAER_RMERR and XAER_RMFAIL.
*
*/
public boolean isSameRM(XAResource foreignXaRes) throws XAException {
// don't allow a XAResourceImpl to be joined to a XAResourceForMC or a XAResourceForRA
// (even though they all implement XAResourceForJMQ)
// as this would imply joining of JMSJCA and JMSRA resources, which has not been tested
if ((foreignXaRes instanceof XAResourceImpl)) {
return isSameJMQRM((XAResourceForJMQ)foreignXaRes);
} else {
return false;
}
}
/**
* Return whether this XAResourceForJMQ and the specified XAResourceForJMQ
* represent the same resource manager instance.
*
* This is determined by checking whether the two resources
* have the same brokerSessionID
*
* @param xaResource XAResourceForJMQ
* @return true if same RM instance, otherwise false.
*/
public boolean isSameJMQRM(XAResourceForJMQ xaResource) {
boolean result;
if ((getBrokerSessionID()!=0) && (getBrokerSessionID()==xaResource.getBrokerSessionID())){
result= true;
} else {
result=false;
}
// logging
if (SessionImpl.sessionLogger.isLoggable(Level.FINE)){
long myBrokerSessionID = getBrokerSessionID();
long otherBrokerSessionID = xaResource.getBrokerSessionID();
SessionImpl.sessionLogger.log(Level.FINE, "myBrokerSessionID="+myBrokerSessionID+" otherBrokerSessionID="+otherBrokerSessionID+" isSameRM()="+result);
}
return result;
}
/**
* Return the brokerSessionID of this object's connection
* @return
*/
public long getBrokerSessionID(){
return _connection.getBrokerSessionID();
}
/**
* Ask the resource manager to prepare for a transaction commit
* of the transaction specified in xid.
*
* @param foreignXid A global transaction identifier.
*
* @exception XAException An error has occurred. Possible exception
* values are: XA_RB*, XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL,
* or XAER_PROTO.
*
* @return A value indicating the resource manager's vote on the
* outcome of the transaction. The possible values are: XA_RDONLY
* or XA_OK. If the resource manager wants to roll back the
* transaction, it should do so by raising an appropriate XAException
* in the prepare method.
*/
private int prepare(Xid foreignXid, boolean onePhase, boolean insyncstate)
throws XAException {
if (_logger.isLoggable(Level.FINE)){
_logger.fine(_lgrPrefix+"XAResourceImpl ("+this.hashCode()+") Prepare "+printXid(foreignXid));
}
//JMS does not do RDONLY transactions - right?
int result = XA_OK;
//convert to jmq xid
JMQXid jmqXid = new JMQXid(foreignXid);
if (Debug.debug) {
Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:prepare:txid=\n"+jmqXid.toString());
}
try {
// HACC
if (this._session.isRollbackOnly) {
Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:prepare:forcing Rollback due to:" + this._session.rollbackCause.getMessage());
//Debug.printStackTrace(this._session.rollbackCause);
if (connectionConsumer != null &&
this._session.rollbackCause instanceof RemoteAcknowledgeException) {
throw this._session.rollbackCause;
}
XAException xae = new XAException(XAException.XAER_RMFAIL);
xae.initCause(this._session.rollbackCause);
throw xae;
}
try {
//Re-open if needed
//_connection.openConnection(true);
//Bug6664278 - must synced
_connection.openConnectionFromRA (true);
} catch (Exception oce) {
//XXX:RFE:Configuration needed
//For now-retry once after a sec
try {
Thread.sleep(1000);
} catch (Exception e) {}
//_connection.openConnection(true);
//Bug6664278 - must synced
_connection.openConnectionFromRA (true);
}
if (_transaction == null) {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:prepare:using 0 as txnID");
_connection.getProtocolHandler().prepare(0L, jmqXid, onePhase);
} else {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:prepare:using real txnID");
//Setup protocol Handler
_transaction.setProtocolHandler(_connection.getProtocolHandler());
//Perform XA prepare
if ( onePhase ) {
_connection.getProtocolHandler().prepare(0L, jmqXid, onePhase);
} else {
_transaction.prepareXATransaction(jmqXid);
}
}
//_connection.closeConnection();
//Bug6664278 - must synced
_connection.closeConnectionFromRA();
} catch (Throwable jmse) {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:prepareXAException");
Debug.printStackTrace(jmse);
if (jmse instanceof XAException) throw (XAException)jmse;
XAException xae = new XAException(XAException.XAER_RMFAIL);
xae.initCause(jmse);
if (jmse instanceof RemoteAcknowledgeException) {
if (connectionConsumer != null) {
ConnectionConsumerImpl cc = connectionConsumer;
if (!cc.canRecreate()) throw xae;
try {
this.rollback(foreignXid);
xae = new XAException(XAException.XA_RBROLLBACK);
xae.initCause(jmse);
lastInternalRBCache.put(this, jmqXid);
lastInternalRB = true;
} catch (Throwable t) {
SessionImpl.sessionLogger.log(Level.SEVERE,
"Exception on rollback transaction "+jmqXid+" after prepared failed with remote exception", t);
} finally {
cc.notifyRecreation((RemoteAcknowledgeException)jmse);
}
} else if (_session.isRemoteException((RemoteAcknowledgeException)jmse)) {
try {
this.rollback(foreignXid);
xae = new XAException(XAException.XA_RBROLLBACK);
xae.initCause(jmse);
lastInternalRBCache.put(this, jmqXid);
lastInternalRB = true;
} catch (Throwable t) {
SessionImpl.sessionLogger.log(Level.SEVERE,
"Exception on rollback transaction "+jmqXid+" after prepare failed with remote exception", t);
}
if (!insyncstate) {
try {
_session.setInSyncState();
} catch (Throwable t) {
SessionImpl.sessionLogger.log(Level.SEVERE,
"Exception on setting sync state after prepare "+jmqXid+" failed with remote exception", t);
throw xae;
}
}
try {
_session.recreateConsumers(true);
} catch (Throwable t) {
SessionImpl.sessionLogger.log(Level.SEVERE,
"Exception on recreating consumers after prepare "+jmqXid+" failed with remote exception", t);
throw xae;
} finally {
if (!insyncstate) {
_session.releaseInSyncState();
}
}
}
} else if (jmse instanceof TransactionPrepareStateFAILEDException) {
if (onePhase) {
if (_transaction == null || _session.isClosed ||
connectionConsumer != null) {
try {
_connection.getProtocolHandler().rollback(0L, jmqXid,
connectionConsumer != null);
xae = new XAException(XAException.XA_RBROLLBACK);
xae.initCause(jmse);
lastInternalRBCache.put(this, jmqXid);
lastInternalRB = true;
} catch (Throwable t) {
SessionImpl.sessionLogger.log(Level.SEVERE,
"Exception on rollback after TransactionPrepareStateFAILEDException "+jmqXid, t);
throw xae;
}
} else {
if (!insyncstate) {
try {
_session.setInSyncState();
} catch (Throwable t) {
SessionImpl.sessionLogger.log(Level.SEVERE,
"Exception on setting sync state on TransactionPrepareStateFAILEDException "+jmqXid, t);
throw xae;
}
}
try {
_session.rollbackAfterReceiveCommit(jmqXid);
xae = new XAException(XAException.XA_RBROLLBACK);
xae.initCause(jmse);
lastInternalRBCache.put(this, jmqXid);
lastInternalRB = true;
} catch (Throwable t) {
SessionImpl.sessionLogger.log(Level.SEVERE,
"Exception on rollback after TransactionPrepareStateFAILEDException "+jmqXid, t);
throw xae;
} finally {
if (!insyncstate) {
_session.releaseInSyncState();
}
}
}
} else {
xae = new XAException(XAException.XAER_RMERR);
xae.initCause(jmse);
}
}
throw xae;
}
// update the resource state
resourceState=PREPARED;
return result;
}
/**
* Obtains a list of prepared transaction branches from a resource
* manager. The transaction manager calls this method during recovery
* to obtain the list of transaction branches that are currently in
* prepared or heuristically completed states.
*
* @param flags One of TMSTARTRSCAN, TMENDRSCAN, TMNOFLAGS. TMNOFLAGS
* must be used when no other flags are set in the parameter.
*
* @exception XAException An error has occurred. Possible values are
* XAER_RMERR, XAER_RMFAIL, XAER_INVAL, and XAER_PROTO.
*
* @return The resource manager returns zero or more XIDs of the
* transaction branches that are currently in a prepared or
* heuristically completed state. If an error occurs during the
* operation, the resource manager should throw the appropriate
* XAException.
*
*/
public Xid[] recover(int flags) throws XAException {
Xid[] result = null;
if (Debug.debug) {
Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:recover:flags="+flags);
}
try {
try {
//Re-open if needed
//_connection.openConnection(true);
//Bug6664278 - must synced
_connection.openConnectionFromRA (true);
} catch (Exception oce) {
//XXX:RFE:Configuration needed
//For now-retry once after a sec
try {
Thread.sleep(1000);
} catch (Exception e) {}
//_connection.openConnection(true);
//Bug6664278 - must synced
_connection.openConnectionFromRA (true);
}
//Perform XA recover
result = _connection.getProtocolHandler().recover(flags);
//_connection.closeConnection();
//Bug6664278 - must synced
_connection.closeConnectionFromRA();
} catch (Exception jmse) {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:recoverXAException");
Debug.printStackTrace(jmse);
XAException xae = new XAException(XAException.XAER_RMFAIL);
xae.initCause(jmse);
throw xae;
}
return result;
}
/**
* Informs the resource manager to roll back work done on behalf
* of a transaction branch.
*
* @param foreignXid A global transaction identifier.
*
* @exception XAException An error has occurred.
*/
public void rollback(Xid foreignXid) throws XAException {
if (_logger.isLoggable(Level.FINE)){
_logger.fine(_lgrPrefix+"("+this.hashCode()+") Rollback "+printXid(foreignXid)+")");
}
//convert to jmq xid
JMQXid jmqXid = new JMQXid(foreignXid);
if (Debug.debug) {
Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:rollback:txid=\n"+jmqXid.toString());
}
try {
try {
//Re-open if needed
//_connection.openConnection(true);
//Bug6664278 - must synced
_connection.openConnectionFromRA (true);
} catch (Exception oce) {
//XXX:RFE:Configuration needed
//For now-retry once after a sec
try {
Thread.sleep(1000);
} catch (Exception e) {}
//_connection.openConnection(true);
//Bug6664278 - must synced
_connection.openConnectionFromRA (true);
}
if (_transaction == null) {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:rollback:using 0 as txnID");
try {
if (this._connection.isConnectedToHABroker) {
this.HARollback(jmqXid, false);
} else {
_connection.getProtocolHandler().rollback(0L, jmqXid);
}
} catch (JMSException e) {
checkInternalRB(e, jmqXid);
}
} else {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:rollback:using real txnID");
//Setup protocol Handler
_transaction.setProtocolHandler(_connection.getProtocolHandler());
//Need to set redeliver on msgs if this session has not been closed.
if (!_session.isClosed) {
//set sync flag
_session.setInSyncState();
//redeliver all messages received in this session
_session.receiveRollback();
}
try {
//Perform XA rollback
if (this._connection.isConnectedToHABroker) {
this.HARollback(jmqXid, false);
} else {
_transaction.rollbackXATransaction(jmqXid);
}
} catch (JMSException e) {
checkInternalRB(e, jmqXid);
}
}
//_connection.closeConnection();
//Bug6664278 - must synced
_connection.closeConnectionFromRA();
} catch (JMSException jmse) {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:rollbackXAException");
Debug.printStackTrace(jmse);
XAException xae = new XAException(XAException.XAER_RMFAIL);
xae.initCause(jmse);
throw xae;
} finally {
// finish up this resource and any others joined to it in this transaction
boolean throwExceptionIfNotFound = false;
XAResourceImpl[] resources = XAResourceMap.getXAResources(jmqXid,throwExceptionIfNotFound);
for (int i = 0; i < resources.length; i++) {
XAResourceImpl xari = resources[i];
xari.finishRollback();
xari.clearTransactionInfo();
}
XAResourceMap.unregister(jmqXid);
_session.releaseInSyncState();
}
}
private void checkInternalRB(JMSException e, JMQXid jmqXid) throws JMSException {
if (e.getErrorCode().equals(ClientResources.X_SERVER_ERROR)) {
Exception e1 = e.getLinkedException();
if (e1 != null && (e1 instanceof JMSException) &&
((JMSException)e1).getErrorCode().equals(Status.getString(Status.NOT_FOUND))) {
if (lastInternalRBCache.containsValue(jmqXid)) {
_connection.connectionLogger.log(Level.INFO,
"Transaction "+jmqXid+" has already been rolled back");
lastInternalRBCache.remove(this);
lastInternalRB = false;
return;
}
}
}
throw e;
}
/**
* Sets the current transaction timeout value for this XAResource
* instance. Once set, this timeout value is effective until
* setTransactionTimeout
is invoked again with a different
* value. To reset the timeout value to the default value used by the resource
* manager, set the value to zero.
*
* If the timeout operation is performed successfully, the method returns
* true; otherwise false. If a resource manager does not
* support explicitly setting the transaction timeout value, this method
* returns false.
*
* @param transactionTimeout The transaction timeout value in seconds.
*
* @return true if the transaction timeout value is set successfully;
* otherwise false.
*
* @exception XAException An error has occurred. Possible exception values
* are XAER_RMERR, XAER_RMFAIL, or XAER_INVAL.
*/
public boolean setTransactionTimeout(int transactionTimeout) throws XAException {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:setTransactionTimeout:timeout="+transactionTimeout);
//XXX:GT RFE - transactionTimeout not supported for now
return false;
/*
if (trannsactionTimeout != null && transactionTimeout >= 0) {
this.transactionTimeout = transactionTimeout;
} else {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:setTransactionTimeoutXAException");
//XXX:GT TBF I18N
XAException xae = new XAException("Invalid transactionTimeout");
xae.initCause(jmse);
throw xae;
}
*/
}
/**
* Starts work on behalf of a transaction branch specified in
* foreignXid
.
*
* If TMJOIN is specified, the start applies to joining a transaction
* previously seen by the resource manager. If TMRESUME is specified,
* the start applies to resuming a suspended transaction specified in the
* parameter foreignXid
.
*
* If neither TMJOIN nor TMRESUME is specified and the transaction
* specified by foreignXid
has previously been seen by the resource
* manager, the resource manager throws the XAException exception with
* XAER_DUPID error code.
*
* @param foreignXid A global transaction identifier to be associated
* with the resource.
*
* @param flags One of TMNOFLAGS, TMJOIN, or TMRESUME.
*
* @exception XAException An error has occurred. Possible exceptions
* are XA_RB*, XAER_RMERR, XAER_RMFAIL, XAER_DUPID, XAER_OUTSIDE,
* XAER_NOTA, XAER_INVAL, or XAER_PROTO.
*
*/
public void start(Xid foreignXid, int flags) throws XAException {
if (_logger.isLoggable(Level.FINE)){
_logger.fine(_lgrPrefix+"("+this.hashCode()+") Start "+printXid(foreignXid)+printFlags(flags));
}
//convert to jmq xid
JMQXid jmqXid = new JMQXid(foreignXid);
if (lastInternalRB) {
lastInternalRBCache.remove(this);
lastInternalRB = false;
}
if (Debug.debug) {
Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:start:flags="+flags+"\txid=\n"+jmqXid.toString());
}
try {
_session.switchOnXATransaction();
_transaction = _session.transaction;
_transaction.startXATransaction(flags, jmqXid);
if (!isResume(flags)){
XAResourceMap.register(jmqXid, this, isJoin(flags));
}
currentJMQXid=jmqXid;
} catch (JMSException jmse) {
//Debug.println("*=*=*=*=*=*=*=*=*=*=XAR:startXAException");
Debug.printStackTrace(jmse);
XAException xae = new XAException(XAException.XAER_RMFAIL);
xae.initCause(jmse);
throw xae;
}
// update the resource state
resourceState=STARTED;
}
/**
* Ends a recovery scan.
*/
//public final static int TMENDRSCAN = 0x00800000;
/**
* Disassociates the caller and marks the transaction branch
* rollback-only.
*/
//public final static int TMFAIL = 0x20000000;
/**
* Caller is joining existing transaction branch.
*/
//public final static int TMJOIN = 0x00200000;
/**
* Use TMNOFLAGS to indicate no flags value is selected.
*/
//public final static int TMNOFLAGS = 0x00000000;
/**
* Caller is using one-phase optimization.
*/
//public final static int TMONEPHASE = 0x40000000;
/**
* Caller is resuming association with a suspended
* transaction branch.
*/
//public final static int TMRESUME = 0x08000000;
/**
* Starts a recovery scan.
*/
//public final static int TMSTARTRSCAN = 0x01000000;
/**
* Disassociates caller from a transaction branch.
*/
//public final static int TMSUCCESS = 0x04000000;
/**
* Caller is suspending (not ending) its association with
* a transaction branch.
*/
//public final static int TMSUSPEND = 0x02000000;
/**
* The transaction branch has been read-only and has been committed.
*/
//public final static int XA_RDONLY = 0x00000003;
/**
* The transaction work has been prepared normally.
*/
//public final static int XA_OK = 0;
/****** XaResourceImpl private methods *****/
/**
*
* @param key -- JMQXid
* @param value -- transaction state.
*
* HACC -- set rollbackOnly/prepared transaction state.
*/
protected static synchronized void setState (Object key, Object value) {
xaTable.put(key, value);
}
/**
* HACC
* @param key
* @return
*/
protected static synchronized Integer getState (Object key) {
return (Integer) xaTable.get(key);
}
/**
* HACC
* remove transaction states. Called after commit/rollback successfully.
* @param key
* @return
*/
protected static synchronized Object removeXid(Object key) {
return xaTable.remove(key);
}
/**
* HACC
* @param key
* @return
*/
protected static synchronized boolean isPrepared (Object key) {
boolean st = false;
Integer value = (Integer) xaTable.get(key);
if ( value != null ) {
if (value.intValue() == XA_PREPARE.intValue()) {
st = true;
}
}
return st;
}
/**
* HACC
* @param key
* @return
*/
protected static synchronized boolean isRollbackOnly (Object key) {
boolean st = false;
Integer value = (Integer) xaTable.get(key);
if ( value != null ) {
if (value.intValue() == XA_ROLLBACK_ONLY.intValue()) {
st = true;
}
}
return st;
}
/**
* two-phase commit prepare for HA.
*
*/
public synchronized int prepare(Xid foreignXid) throws XAException {
//result code
int result = XA_OK;
//this.twoPhasePrepared = false;
JMQXid jmqXid = null;
if (_connection.isConnectedToHABroker) {
jmqXid = new JMQXid(foreignXid);
}
try {
//two phase commit
this.prepare (foreignXid, false, false);
} catch (XAException xae) {
if (_connection.isConnectedToHABroker) {
checkPrepareStatus(xae, jmqXid);
} else {
//non HA -- propagate exception
throw xae;
}
}
//set to true so that in case we need to verify transaction later.
//this.twoPhasePrepared = true;
if (isXATracking()) {
xaTable.put (jmqXid, XA_PREPARE);
}
return result;
}
private boolean isXATracking() {
return (_connection.isConnectedToHABroker() && (XATracking));
}
/**
* check prepared status
*
* @param jmse
* @param tstate -- transaction state when exception occurred
* @param jmqXid --
* @throws XAException if the transaction is not in prepared state.
*/
private void checkPrepareStatus (XAException xae, JMQXid jmqXid) throws XAException {
if (_connection.imqReconnect == false) {
throw xae;
}
try {
SessionImpl.yield();
_connection.checkReconnecting(null);
if (_connection.isCloseCalled || _connection.connectionIsBroken) {
throw xae;
}
//check failover broker to see the status
verifyPrepare(jmqXid);
} catch (XAException xae2) {
//if any xaexception we simply propagate up
throw xae2;
} catch (Exception e) {
//for any other exceptions, throws an XAException.
XAException xae3 = new XAException(XAException.XAER_RMFAIL);
xae3.initCause(e);
throw xae3;
}
}
private void verifyPrepare (JMQXid jmqXid) throws XAException, JMSException {
SessionImpl.sessionLogger.log(Level.INFO, "XA verifyPrepare(), jmqXid: " + jmqXid);
int state = _connection.protocolHandler.verifyHATransaction(0L, Transaction.TRANSACTION_ENDED, jmqXid);
switch (state) {
case 6:
//transaction is in prepared state
SessionImpl.sessionLogger.log(Level.INFO, "transaction in prepared state: " + jmqXid);
return;
case 8:
case 9:
default:
//for the rest of the state, the transaction was rolled back
//by the broker.
XAException xae = new XAException(XAException.XA_RBROLLBACK);
throw xae;
}
}
/**
* For XA onePhase commit, if RA is connected to HA brokers,
* we use two phase MQ protocol to commit a transaction.
*
* "JMQXAOnePhase" property is set to true for prepare and commit pkts.
*
* "TMNOFLAGS" is used in the onePhase commit pkt.
*
*
* @param foreignXid
* @param jmqXid
* @throws JMSException
* @throws XAException
*/
private void HAOnePhaseCommit (Xid foreignXid, JMQXid jmqXid, boolean insyncstate)
throws JMSException, XAException {
int tstate = Transaction.TRANSACTION_ENDED;
try {
//prepare xa onephase commit
this.prepare(foreignXid, true, insyncstate);
tstate = Transaction.TRANSACTION_PREPARED;
if (isXATracking()) {
xaTable.put(jmqXid, XAResourceForRA.XA_PREPARE);
}
//param true is to indicate "JMQXAOnePhase" is needed
//for the commit protocol property.
_connection.getProtocolHandler().commit(0L, XAResource.TMNOFLAGS, jmqXid, true);
} catch (Exception jmse) {
if ((jmse instanceof XAException) &&
((XAException)jmse).errorCode == XAException.XA_RBROLLBACK) {
throw (XAException)jmse;
}
//check onephase commit status
this.checkCommitStatus(jmse, tstate, jmqXid, true);
}
this.removeXid(jmqXid);
}
private void HATwoPhaseCommit (JMQXid jmqXid) throws JMSException, XAException {
//if (this.twoPhasePrepared == false) {
// throw new XAException (XAException.XAER_PROTO);
//}
try {
_connection.getProtocolHandler().commit(0L, XAResource.TMNOFLAGS, jmqXid);
} catch (JMSException jmse) {
if ( this.isXATracking() ) {
Integer ts = (Integer) xaTable.get(jmqXid);
//we must gaurantee that the transaction was indeed in prepared state.
if (ts != null && ts.intValue() == (Transaction.TRANSACTION_PREPARED)) {
this.checkCommitStatus(jmse, Transaction.TRANSACTION_PREPARED, jmqXid, false);
}
} else {
//propagate the exception
throw jmse;
}
}
//transaction has successfully committed
//this.twoPhasePrepared = false;
this.removeXid(jmqXid);
}
private void checkCommitStatus(Exception cause, int tstate, JMQXid jmqXid, boolean onePhase)
throws JMSException, XAException {
try {
doCheckCommitStatus (cause, tstate, jmqXid, onePhase);
} catch (Exception ex) {
//throw the original type of exception.
if (ex instanceof JMSException) {
throw (JMSException) ex;
} else if (ex instanceof XAException) {
throw (XAException) ex;
} else {
XAException xae = new XAException (XAException.XAER_RMFAIL);
xae.initCause(ex);
throw xae;
}
}
}
private void doCheckCommitStatus(Exception cause, int tstate, JMQXid jmqXid, boolean onePhase)
throws Exception {
if (_connection.imqReconnect == false) {
throw cause;
}
SessionImpl.yield();
_connection.checkReconnecting(null);
if (_connection.isCloseCalled || _connection.connectionIsBroken) {
throw cause;
}
verifyTransaction(tstate, jmqXid, onePhase);
}
private void verifyTransaction(int tstate, JMQXid jmqXid, boolean onePhase) throws JMSException, XAException {
int state = _connection.protocolHandler.verifyHATransaction(0L, tstate, jmqXid);
switch (state) {
case 7:
//committed
return;
case 6:
//transaction is in prepared state. ask broker to commit.
try {
//protocolHandler.rollback(this.transactionID);
SessionImpl.sessionLogger.log(Level.INFO, "XA verifyTransaction(): transaction is in prepred state, committing the transaction: " + jmqXid);
//epConnection.protocolHandler.commitHATransaction(this.transactionID);
_connection.getProtocolHandler().commit(0L, XAResource.TMNOFLAGS, jmqXid, onePhase);
SessionImpl.sessionLogger.log(Level.INFO, "XA verifyTransaction(): prepared transaction committed successfully: " + jmqXid);
//done if we can commit.
return;
} catch (JMSException jmse) {
//in case this failed, we try one more time.
//This is the third failure at this point.
//If failed again, we log this and throw Exception.
SessionImpl.yield();
_connection.checkReconnecting(null);
//if ( epConnection.isCloseCalled || epConnection.connectionIsBroken) {
throw jmse;
//}
//we should rollback at this point.
//epConnection.protocolHandler.rollback(this.transactionID);
}
case 8:
case 9:
default:
//for the rest of the state, the transaction was rolled back
//by the broker.
//String errorString = AdministeredObject.cr.getKString(
// AdministeredObject.cr.X_TRANSACTION_FAILOVER_OCCURRED);
//TransactionRolledBackException tre =
// new TransactionRolledBackException(errorString,
// AdministeredObject.cr.
// X_TRANSACTION_FAILOVER_OCCURRED);
//throwRollbackException(tre);
XAException xae = new XAException(XAException.XA_RBROLLBACK);
throw xae;
}
}
private void HACommit (Xid foreignXid, JMQXid jmqXid, boolean onePhase, boolean insyncstate)
throws JMSException, XAException {
if (onePhase) {
this.HAOnePhaseCommit(foreignXid, jmqXid, insyncstate);
} else {
this.HATwoPhaseCommit(jmqXid);
}
}
private void HARollback(JMQXid jmqXid, boolean redeliverMsgs) throws JMSException, XAException {
try {
_connection.getProtocolHandler().rollback(0L, jmqXid, redeliverMsgs);
} catch (JMSException jmse) {
//yield/pause
SessionImpl.yield();
//block until fail-over/re-connected/closed
this._connection.checkReconnecting(null);
//check if we still connected.
if (_connection.isCloseCalled || _connection.connectionIsBroken) {
throw jmse;
}
//re-send with I bit set to true
this.retryRollBack(jmqXid, redeliverMsgs);
} finally {
//remove this xid
this.removeXid(jmqXid);
}
}
private void retryRollBack (JMQXid jmqXid, boolean redeliverMsgs) throws JMSException, XAException {
try {
_connection.getProtocolHandler().rollback(0L, jmqXid, redeliverMsgs, true);
} catch (JMSException jmse) {
if (isXATracking()) {
Integer tstate = (Integer) xaTable.get(jmqXid);
if (tstate != null && tstate.intValue() != Transaction.TRANSACTION_PREPARED) {
// the transaction is not in prepared state, we log the info
// and continue.
ConnectionImpl.connectionLogger.log(Level.WARNING, jmse.toString());
} else {
throw jmse;
}
} else {
throw jmse;
}
}
}
private boolean isJoin(int flags){
return((flags & XAResource.TMJOIN) == XAResource.TMJOIN);
}
private boolean isResume(int flags){
return((flags & XAResource.TMRESUME) == XAResource.TMRESUME);
}
public boolean isComplete() {
return this.resourceState==COMPLETE;
}
public void clearTransactionInfo(){
this.resourceState=CREATED;
}
// Used for debugging only
private String printXid(Xid foreignXid){
return ("(GlobalTransactionID="+foreignXid.getGlobalTransactionId()) +
", BranchQualifier="+foreignXid.getBranchQualifier()+") ";
}
// Used for debugging only
private boolean isNoFlags(int flags){
return((flags & XAResource.TMNOFLAGS) == XAResource.TMNOFLAGS);
}
// Used for debugging only
private boolean isFail(int flags){
return((flags & XAResource.TMFAIL) == XAResource.TMFAIL);
}
// Used for debugging only
private boolean isOnePhase(int flags){
return((flags & XAResource.TMONEPHASE) == XAResource.TMONEPHASE);
}
// Used for debugging only
private boolean isSuccess(int flags){
return((flags & XAResource.TMSUCCESS) == XAResource.TMSUCCESS);
}
// Used for debugging only
private boolean isSuspend(int flags){
return((flags & XAResource.TMSUSPEND) == XAResource.TMSUSPEND);
}
// Used for debugging only
private boolean isTMENDRSCAN(int flags){
return((flags & XAResource.TMENDRSCAN) == XAResource.TMENDRSCAN);
}
// Used for debugging only
private boolean TMSTARTRSCAN(int flags){
return((flags & XAResource.TMSTARTRSCAN) == XAResource.TMSTARTRSCAN);
}
// Used for debugging only
private String printFlags(int flags){
String result = ("(Flags: ");
if (isJoin(flags)){
result=result+("JOIN ");
}
if (isNoFlags(flags)){
result=result+("TMNOFLAGS ");
}
if (isFail(flags)){
result=result+("TMFAIL ");
}
if (isOnePhase(flags)){
result=result+("TMONEPHASE ");
}
if (isResume(flags)){
result=result+("TMRESUME ");
}
if (isSuccess(flags)){
result=result+("TMSUCCESS ");
}
if (isSuspend(flags)){
result=result+("TMSUSPEND ");
}
if (isTMENDRSCAN(flags)){
result=result+("TMENDRSCAN ");
}
if (TMSTARTRSCAN(flags)){
result=result+("TMSTARTRSCAN ");
}
result=result+(")");
return result;
}
}