001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq;
018
019import java.io.InterruptedIOException;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.HashMap;
023import java.util.List;
024
025import javax.jms.JMSException;
026import javax.jms.TransactionInProgressException;
027import javax.jms.TransactionRolledBackException;
028import javax.transaction.xa.XAException;
029import javax.transaction.xa.XAResource;
030import javax.transaction.xa.Xid;
031
032import org.apache.activemq.command.Command;
033import org.apache.activemq.command.ConnectionId;
034import org.apache.activemq.command.DataArrayResponse;
035import org.apache.activemq.command.DataStructure;
036import org.apache.activemq.command.IntegerResponse;
037import org.apache.activemq.command.LocalTransactionId;
038import org.apache.activemq.command.Response;
039import org.apache.activemq.command.TransactionId;
040import org.apache.activemq.command.TransactionInfo;
041import org.apache.activemq.command.XATransactionId;
042import org.apache.activemq.transaction.Synchronization;
043import org.apache.activemq.util.JMSExceptionSupport;
044import org.apache.activemq.util.LongSequenceGenerator;
045import org.apache.activemq.util.XASupport;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049/**
050 * A TransactionContext provides the means to control a JMS transaction. It
051 * provides a local transaction interface and also an XAResource interface. <p/>
052 * An application server controls the transactional assignment of an XASession
053 * by obtaining its XAResource. It uses the XAResource to assign the session to
054 * a transaction, prepare and commit work on the transaction, and so on. <p/> An
055 * XAResource provides some fairly sophisticated facilities for interleaving
056 * work on multiple transactions, recovering a list of transactions in progress,
057 * and so on. A JTA aware JMS provider must fully implement this functionality.
058 * This could be done by using the services of a database that supports XA, or a
059 * JMS provider may choose to implement this functionality from scratch. <p/>
060 *
061 *
062 * @see javax.jms.Session
063 * @see javax.jms.QueueSession
064 * @see javax.jms.TopicSession
065 * @see javax.jms.XASession
066 */
067public class TransactionContext implements XAResource {
068
069    public static final String xaErrorCodeMarker = "xaErrorCode:";
070    private static final Logger LOG = LoggerFactory.getLogger(TransactionContext.class);
071
072    // XATransactionId -> ArrayList of TransactionContext objects
073    private final static HashMap<TransactionId, List<TransactionContext>> ENDED_XA_TRANSACTION_CONTEXTS =
074            new HashMap<TransactionId, List<TransactionContext>>();
075
076    private ActiveMQConnection connection;
077    private final LongSequenceGenerator localTransactionIdGenerator;
078    private List<Synchronization> synchronizations;
079
080    // To track XA transactions.
081    private Xid associatedXid;
082    private TransactionId transactionId;
083    private LocalTransactionEventListener localTransactionEventListener;
084    private int beforeEndIndex;
085
086    // for RAR recovery
087    public TransactionContext() {
088        localTransactionIdGenerator = null;
089    }
090
091    public TransactionContext(ActiveMQConnection connection) {
092        this.connection = connection;
093        this.localTransactionIdGenerator = connection.getLocalTransactionIdGenerator();
094    }
095
096    public boolean isInXATransaction() {
097        if (transactionId != null && transactionId.isXATransaction()) {
098            return true;
099        } else {
100            if (!ENDED_XA_TRANSACTION_CONTEXTS.isEmpty()) {
101                synchronized(ENDED_XA_TRANSACTION_CONTEXTS) {
102                    for(List<TransactionContext> transactions : ENDED_XA_TRANSACTION_CONTEXTS.values()) {
103                        if (transactions.contains(this)) {
104                            return true;
105                        }
106                    }
107                }
108            }
109        }
110
111        return false;
112    }
113
114    public boolean isInLocalTransaction() {
115        return transactionId != null && transactionId.isLocalTransaction();
116    }
117
118    public boolean isInTransaction() {
119        return transactionId != null;
120    }
121
122    /**
123     * @return Returns the localTransactionEventListener.
124     */
125    public LocalTransactionEventListener getLocalTransactionEventListener() {
126        return localTransactionEventListener;
127    }
128
129    /**
130     * Used by the resource adapter to listen to transaction events.
131     *
132     * @param localTransactionEventListener The localTransactionEventListener to
133     *                set.
134     */
135    public void setLocalTransactionEventListener(LocalTransactionEventListener localTransactionEventListener) {
136        this.localTransactionEventListener = localTransactionEventListener;
137    }
138
139    // ///////////////////////////////////////////////////////////
140    //
141    // Methods that work with the Synchronization objects registered with
142    // the transaction.
143    //
144    // ///////////////////////////////////////////////////////////
145
146    public void addSynchronization(Synchronization s) {
147        if (synchronizations == null) {
148            synchronizations = new ArrayList<Synchronization>(10);
149        }
150        synchronizations.add(s);
151    }
152
153    private void afterRollback() throws JMSException {
154        if (synchronizations == null) {
155            return;
156        }
157
158        Throwable firstException = null;
159        int size = synchronizations.size();
160        for (int i = 0; i < size; i++) {
161            try {
162                synchronizations.get(i).afterRollback();
163            } catch (Throwable t) {
164                LOG.debug("Exception from afterRollback on {}", synchronizations.get(i), t);
165                if (firstException == null) {
166                    firstException = t;
167                }
168            }
169        }
170        synchronizations = null;
171        if (firstException != null) {
172            throw JMSExceptionSupport.create(firstException);
173        }
174    }
175
176    private void afterCommit() throws JMSException {
177        if (synchronizations == null) {
178            return;
179        }
180
181        Throwable firstException = null;
182        int size = synchronizations.size();
183        for (int i = 0; i < size; i++) {
184            try {
185                synchronizations.get(i).afterCommit();
186            } catch (Throwable t) {
187                LOG.debug("Exception from afterCommit on {}", synchronizations.get(i), t);
188                if (firstException == null) {
189                    firstException = t;
190                }
191            }
192        }
193        synchronizations = null;
194        if (firstException != null) {
195            throw JMSExceptionSupport.create(firstException);
196        }
197    }
198
199    private void beforeEnd() throws JMSException {
200        if (synchronizations == null) {
201            return;
202        }
203
204        int size = synchronizations.size();
205        try {
206            for (;beforeEndIndex < size;) {
207                synchronizations.get(beforeEndIndex++).beforeEnd();
208            }
209        } catch (JMSException e) {
210            throw e;
211        } catch (Throwable e) {
212            throw JMSExceptionSupport.create(e);
213        }
214    }
215
216    public TransactionId getTransactionId() {
217        return transactionId;
218    }
219
220    // ///////////////////////////////////////////////////////////
221    //
222    // Local transaction interface.
223    //
224    // ///////////////////////////////////////////////////////////
225
226    /**
227     * Start a local transaction.
228     * @throws javax.jms.JMSException on internal error
229     */
230    public void begin() throws JMSException {
231
232        if (isInXATransaction()) {
233            throw new TransactionInProgressException("Cannot start local transaction.  XA transaction is already in progress.");
234        }
235
236        if (transactionId == null) {
237            synchronizations = null;
238            beforeEndIndex = 0;
239            this.transactionId = new LocalTransactionId(getConnectionId(), localTransactionIdGenerator.getNextSequenceId());
240            TransactionInfo info = new TransactionInfo(getConnectionId(), transactionId, TransactionInfo.BEGIN);
241            this.connection.ensureConnectionInfoSent();
242            this.connection.asyncSendPacket(info);
243
244            // Notify the listener that the tx was started.
245            if (localTransactionEventListener != null) {
246                localTransactionEventListener.beginEvent();
247            }
248
249            LOG.debug("Begin:{}", transactionId);
250        }
251    }
252
253    /**
254     * Rolls back any work done in this transaction and releases any locks
255     * currently held.
256     *
257     * @throws JMSException if the JMS provider fails to roll back the
258     *                 transaction due to some internal error.
259     * @throws javax.jms.IllegalStateException if the method is not called by a
260     *                 transacted session.
261     */
262    public void rollback() throws JMSException {
263        if (isInXATransaction()) {
264            throw new TransactionInProgressException("Cannot rollback() if an XA transaction is already in progress ");
265        }
266
267        try {
268            beforeEnd();
269        } catch (TransactionRolledBackException canOcurrOnFailover) {
270            LOG.warn("rollback processing error", canOcurrOnFailover);
271        }
272        if (transactionId != null) {
273            LOG.debug("Rollback: {} syncCount: {}",
274                transactionId, (synchronizations != null ? synchronizations.size() : 0));
275
276            TransactionInfo info = new TransactionInfo(getConnectionId(), transactionId, TransactionInfo.ROLLBACK);
277            this.transactionId = null;
278            //make this synchronous - see https://issues.apache.org/activemq/browse/AMQ-2364
279            this.connection.syncSendPacket(info);
280            // Notify the listener that the tx was rolled back
281            if (localTransactionEventListener != null) {
282                localTransactionEventListener.rollbackEvent();
283            }
284        }
285
286        afterRollback();
287    }
288
289    /**
290     * Commits all work done in this transaction and releases any locks
291     * currently held.
292     *
293     * @throws JMSException if the JMS provider fails to commit the transaction
294     *                 due to some internal error.
295     * @throws javax.jms.IllegalStateException if the method is not called by a
296     *                 transacted session.
297     */
298    public void commit() throws JMSException {
299        if (isInXATransaction()) {
300            throw new TransactionInProgressException("Cannot commit() if an XA transaction is already in progress ");
301        }
302
303        try {
304            beforeEnd();
305        } catch (JMSException e) {
306            rollback();
307            throw e;
308        }
309
310        // Only send commit if the transaction was started.
311        if (transactionId != null) {
312            LOG.debug("Commit: {} syncCount: {}",
313                transactionId, (synchronizations != null ? synchronizations.size() : 0));
314
315            TransactionInfo info = new TransactionInfo(getConnectionId(), transactionId, TransactionInfo.COMMIT_ONE_PHASE);
316            this.transactionId = null;
317            // Notify the listener that the tx was committed back
318            try {
319                syncSendPacketWithInterruptionHandling(info);
320                if (localTransactionEventListener != null) {
321                    localTransactionEventListener.commitEvent();
322                }
323                afterCommit();
324            } catch (JMSException cause) {
325                LOG.info("commit failed for transaction {}", info.getTransactionId(), cause);
326                if (localTransactionEventListener != null) {
327                    localTransactionEventListener.rollbackEvent();
328                }
329                afterRollback();
330                throw cause;
331            }
332
333        }
334    }
335
336    // ///////////////////////////////////////////////////////////
337    //
338    // XAResource Implementation
339    //
340    // ///////////////////////////////////////////////////////////
341    /**
342     * Associates a transaction with the resource.
343     */
344    @Override
345    public void start(Xid xid, int flags) throws XAException {
346
347        LOG.debug("Start: {}, flags: {}", xid, XASupport.toString(flags));
348
349        if (isInLocalTransaction()) {
350            throw new XAException(XAException.XAER_PROTO);
351        }
352        // Are we already associated?
353        if (associatedXid != null) {
354            throw new XAException(XAException.XAER_PROTO);
355        }
356
357        // if ((flags & TMJOIN) == TMJOIN) {
358        // TODO: verify that the server has seen the xid
359        // // }
360        // if ((flags & TMRESUME) == TMRESUME) {
361        // // TODO: verify that the xid was suspended.
362        // }
363
364        // associate
365        synchronizations = null;
366        beforeEndIndex = 0;
367        setXid(xid);
368    }
369
370    /**
371     * @return connectionId for connection
372     */
373    private ConnectionId getConnectionId() {
374        return connection.getConnectionInfo().getConnectionId();
375    }
376
377    @Override
378    public void end(Xid xid, int flags) throws XAException {
379
380        LOG.debug("End: {}, flags: {}", xid, XASupport.toString(flags));
381
382        if (isInLocalTransaction()) {
383            throw new XAException(XAException.XAER_PROTO);
384        }
385
386        if ((flags & (TMSUSPEND | TMFAIL)) != 0) {
387            // You can only suspend the associated xid.
388            if (!equals(associatedXid, xid)) {
389                throw new XAException(XAException.XAER_PROTO);
390            }
391
392            // TODO: we may want to put the xid in a suspended list.
393            try {
394                beforeEnd();
395            } catch (JMSException e) {
396                throw toXAException(e);
397            } finally {
398                setXid(null);
399            }
400        } else if ((flags & TMSUCCESS) == TMSUCCESS) {
401            // set to null if this is the current xid.
402            // otherwise this could be an asynchronous success call
403            if (equals(associatedXid, xid)) {
404                try {
405                    beforeEnd();
406                } catch (JMSException e) {
407                    throw toXAException(e);
408                } finally {
409                    setXid(null);
410                }
411            }
412        } else {
413            throw new XAException(XAException.XAER_INVAL);
414        }
415    }
416
417    private boolean equals(Xid xid1, Xid xid2) {
418        if (xid1 == xid2) {
419            return true;
420        }
421        if (xid1 == null ^ xid2 == null) {
422            return false;
423        }
424        return xid1.getFormatId() == xid2.getFormatId() && Arrays.equals(xid1.getBranchQualifier(), xid2.getBranchQualifier())
425               && Arrays.equals(xid1.getGlobalTransactionId(), xid2.getGlobalTransactionId());
426    }
427
428    @Override
429    public int prepare(Xid xid) throws XAException {
430        LOG.debug("Prepare: {}", xid);
431
432        // We allow interleaving multiple transactions, so
433        // we don't limit prepare to the associated xid.
434        XATransactionId x;
435        // THIS SHOULD NEVER HAPPEN because end(xid, TMSUCCESS) should have been
436        // called first
437        if (xid == null || (equals(associatedXid, xid))) {
438            throw new XAException(XAException.XAER_PROTO);
439        } else {
440            // TODO: cache the known xids so we don't keep recreating this one??
441            x = new XATransactionId(xid);
442        }
443
444        try {
445            TransactionInfo info = new TransactionInfo(getConnectionId(), x, TransactionInfo.PREPARE);
446
447            // Find out if the server wants to commit or rollback.
448            IntegerResponse response = (IntegerResponse)syncSendPacketWithInterruptionHandling(info);
449            if (XAResource.XA_RDONLY == response.getResult()) {
450                // transaction stops now, may be syncs that need a callback
451                synchronized(ENDED_XA_TRANSACTION_CONTEXTS) {
452                    List<TransactionContext> l = ENDED_XA_TRANSACTION_CONTEXTS.remove(x);
453                    if (l != null && !l.isEmpty()) {
454                        LOG.debug("firing afterCommit callbacks on XA_RDONLY from prepare: {}", xid);
455                        for (TransactionContext ctx : l) {
456                            ctx.afterCommit();
457                        }
458                    }
459                }
460            }
461            return response.getResult();
462
463        } catch (JMSException e) {
464            LOG.warn("prepare of: " + x + " failed with: " + e, e);
465            synchronized(ENDED_XA_TRANSACTION_CONTEXTS) {
466                List<TransactionContext> l = ENDED_XA_TRANSACTION_CONTEXTS.remove(x);
467                if (l != null && !l.isEmpty()) {
468                    for (TransactionContext ctx : l) {
469                        try {
470                            ctx.afterRollback();
471                        } catch (Throwable ignored) {
472                            LOG.debug("failed to firing afterRollback callbacks on prepare " +
473                                      "failure, txid: {}, context: {}", x, ctx, ignored);
474                        }
475                    }
476                }
477            }
478            throw toXAException(e);
479        }
480    }
481
482    @Override
483    public void rollback(Xid xid) throws XAException {
484
485        if (LOG.isDebugEnabled()) {
486            LOG.debug("Rollback: " + xid);
487        }
488
489        // We allow interleaving multiple transactions, so
490        // we don't limit rollback to the associated xid.
491        XATransactionId x;
492        if (xid == null) {
493            throw new XAException(XAException.XAER_PROTO);
494        }
495        if (equals(associatedXid, xid)) {
496            // I think this can happen even without an end(xid) call. Need to
497            // check spec.
498            x = (XATransactionId)transactionId;
499        } else {
500            x = new XATransactionId(xid);
501        }
502
503        try {
504            this.connection.checkClosedOrFailed();
505            this.connection.ensureConnectionInfoSent();
506
507            // Let the server know that the tx is rollback.
508            TransactionInfo info = new TransactionInfo(getConnectionId(), x, TransactionInfo.ROLLBACK);
509            syncSendPacketWithInterruptionHandling(info);
510
511            synchronized(ENDED_XA_TRANSACTION_CONTEXTS) {
512                List<TransactionContext> l = ENDED_XA_TRANSACTION_CONTEXTS.remove(x);
513                if (l != null && !l.isEmpty()) {
514                    for (TransactionContext ctx : l) {
515                        ctx.afterRollback();
516                    }
517                }
518            }
519        } catch (JMSException e) {
520            throw toXAException(e);
521        }
522    }
523
524    // XAResource interface
525    @Override
526    public void commit(Xid xid, boolean onePhase) throws XAException {
527
528        LOG.debug("Commit: {}, onePhase={}", xid, onePhase);
529
530        // We allow interleaving multiple transactions, so
531        // we don't limit commit to the associated xid.
532        XATransactionId x;
533        if (xid == null || (equals(associatedXid, xid))) {
534            // should never happen, end(xid,TMSUCCESS) must have been previously
535            // called
536            throw new XAException(XAException.XAER_PROTO);
537        } else {
538            x = new XATransactionId(xid);
539        }
540
541        try {
542            this.connection.checkClosedOrFailed();
543            this.connection.ensureConnectionInfoSent();
544
545            // Notify the server that the tx was committed back
546            TransactionInfo info = new TransactionInfo(getConnectionId(), x, onePhase ? TransactionInfo.COMMIT_ONE_PHASE : TransactionInfo.COMMIT_TWO_PHASE);
547
548            syncSendPacketWithInterruptionHandling(info);
549
550            synchronized(ENDED_XA_TRANSACTION_CONTEXTS) {
551                List<TransactionContext> l = ENDED_XA_TRANSACTION_CONTEXTS.remove(x);
552                if (l != null && !l.isEmpty()) {
553                    for (TransactionContext ctx : l) {
554                        try {
555                            ctx.afterCommit();
556                        } catch (Exception ignored) {
557                            LOG.debug("ignoring exception from after completion on ended transaction: {}", ignored, ignored);
558                        }
559                    }
560                }
561            }
562
563        } catch (JMSException e) {
564            LOG.warn("commit of: " + x + " failed with: " + e, e);
565            if (onePhase) {
566                synchronized(ENDED_XA_TRANSACTION_CONTEXTS) {
567                    List<TransactionContext> l = ENDED_XA_TRANSACTION_CONTEXTS.remove(x);
568                    if (l != null && !l.isEmpty()) {
569                        for (TransactionContext ctx : l) {
570                            try {
571                                ctx.afterRollback();
572                            } catch (Throwable ignored) {
573                                LOG.debug("failed to firing afterRollback callbacks commit failure, txid: {}, context: {}", x, ctx, ignored);
574                            }
575                        }
576                    }
577                }
578            }
579            throw toXAException(e);
580        }
581    }
582
583    @Override
584    public void forget(Xid xid) throws XAException {
585        LOG.debug("Forget: {}", xid);
586
587        // We allow interleaving multiple transactions, so
588        // we don't limit forget to the associated xid.
589        XATransactionId x;
590        if (xid == null) {
591            throw new XAException(XAException.XAER_PROTO);
592        }
593        if (equals(associatedXid, xid)) {
594            // TODO determine if this can happen... I think not.
595            x = (XATransactionId)transactionId;
596        } else {
597            x = new XATransactionId(xid);
598        }
599
600        TransactionInfo info = new TransactionInfo(getConnectionId(), x, TransactionInfo.FORGET);
601
602        try {
603            // Tell the server to forget the transaction.
604            syncSendPacketWithInterruptionHandling(info);
605        } catch (JMSException e) {
606            throw toXAException(e);
607        }
608        synchronized(ENDED_XA_TRANSACTION_CONTEXTS) {
609            ENDED_XA_TRANSACTION_CONTEXTS.remove(x);
610        }
611    }
612
613    @Override
614    public boolean isSameRM(XAResource xaResource) throws XAException {
615        if (xaResource == null) {
616            return false;
617        }
618        if (!(xaResource instanceof TransactionContext)) {
619            return false;
620        }
621        TransactionContext xar = (TransactionContext)xaResource;
622        try {
623            return getResourceManagerId().equals(xar.getResourceManagerId());
624        } catch (Throwable e) {
625            throw (XAException)new XAException("Could not get resource manager id.").initCause(e);
626        }
627    }
628
629    @Override
630    public Xid[] recover(int flag) throws XAException {
631        LOG.debug("recover({})", flag);
632
633        TransactionInfo info = new TransactionInfo(getConnectionId(), null, TransactionInfo.RECOVER);
634        try {
635            this.connection.checkClosedOrFailed();
636            this.connection.ensureConnectionInfoSent();
637
638            DataArrayResponse receipt = (DataArrayResponse)this.connection.syncSendPacket(info);
639            DataStructure[] data = receipt.getData();
640            XATransactionId[] answer;
641            if (data instanceof XATransactionId[]) {
642                answer = (XATransactionId[])data;
643            } else {
644                answer = new XATransactionId[data.length];
645                System.arraycopy(data, 0, answer, 0, data.length);
646            }
647            LOG.debug("recover({})={}", flag, answer);
648            return answer;
649        } catch (JMSException e) {
650            throw toXAException(e);
651        }
652    }
653
654    @Override
655    public int getTransactionTimeout() throws XAException {
656        return 0;
657    }
658
659    @Override
660    public boolean setTransactionTimeout(int seconds) throws XAException {
661        return false;
662    }
663
664    // ///////////////////////////////////////////////////////////
665    //
666    // Helper methods.
667    //
668    // ///////////////////////////////////////////////////////////
669    protected String getResourceManagerId() throws JMSException {
670        return this.connection.getResourceManagerId();
671    }
672
673    private void setXid(Xid xid) throws XAException {
674
675        try {
676            this.connection.checkClosedOrFailed();
677            this.connection.ensureConnectionInfoSent();
678        } catch (JMSException e) {
679            disassociate();
680            throw toXAException(e);
681        }
682
683        if (xid != null) {
684            // associate
685            associatedXid = xid;
686            transactionId = new XATransactionId(xid);
687
688            TransactionInfo info = new TransactionInfo(getConnectionId(), transactionId, TransactionInfo.BEGIN);
689            try {
690                this.connection.asyncSendPacket(info);
691                LOG.debug("{} started XA transaction {}", this, transactionId);
692            } catch (JMSException e) {
693                disassociate();
694                throw toXAException(e);
695            }
696
697        } else {
698
699            if (transactionId != null) {
700                TransactionInfo info = new TransactionInfo(getConnectionId(), transactionId, TransactionInfo.END);
701                try {
702                    syncSendPacketWithInterruptionHandling(info);
703                    LOG.debug("{} ended XA transaction {}", this, transactionId);
704                } catch (JMSException e) {
705                    disassociate();
706                    throw toXAException(e);
707                }
708
709                // Add our self to the list of contexts that are interested in
710                // post commit/rollback events.
711                synchronized(ENDED_XA_TRANSACTION_CONTEXTS) {
712                    List<TransactionContext> l = ENDED_XA_TRANSACTION_CONTEXTS.get(transactionId);
713                    if (l == null) {
714                        l = new ArrayList<TransactionContext>(3);
715                        ENDED_XA_TRANSACTION_CONTEXTS.put(transactionId, l);
716                        l.add(this);
717                    } else if (!l.contains(this)) {
718                        l.add(this);
719                    }
720                }
721            }
722
723            disassociate();
724        }
725    }
726
727    private void disassociate() {
728         // dis-associate
729         associatedXid = null;
730         transactionId = null;
731    }
732
733    /**
734     * Sends the given command. Also sends the command in case of interruption,
735     * so that important commands like rollback and commit are never interrupted.
736     * If interruption occurred, set the interruption state of the current
737     * after performing the action again.
738     *
739     * @return the response
740     */
741    private Response syncSendPacketWithInterruptionHandling(Command command) throws JMSException {
742        try {
743            return this.connection.syncSendPacket(command);
744        } catch (JMSException e) {
745            if (e.getLinkedException() instanceof InterruptedIOException) {
746                try {
747                    Thread.interrupted();
748                    return this.connection.syncSendPacket(command);
749                } finally {
750                    Thread.currentThread().interrupt();
751                }
752            }
753
754            throw e;
755        }
756    }
757
758    /**
759     * Converts a JMSException from the server to an XAException. if the
760     * JMSException contained a linked XAException that is returned instead.
761     *
762     * @param e JMSException to convert
763     * @return XAException wrapping original exception or its message
764     */
765    private XAException toXAException(JMSException e) {
766        if (e.getCause() != null && e.getCause() instanceof XAException) {
767            XAException original = (XAException)e.getCause();
768            XAException xae = new XAException(original.getMessage());
769            xae.errorCode = original.errorCode;
770            if (xae.errorCode == XA_OK) {
771                // detail not unmarshalled see: org.apache.activemq.openwire.v1.BaseDataStreamMarshaller.createThrowable
772                xae.errorCode = parseFromMessageOr(original.getMessage(), XAException.XAER_RMERR);
773            }
774            xae.initCause(original);
775            return xae;
776        }
777
778        XAException xae = new XAException(e.getMessage());
779        xae.errorCode = XAException.XAER_RMFAIL;
780        xae.initCause(e);
781        return xae;
782    }
783
784    private int parseFromMessageOr(String message, int fallbackCode) {
785        final String marker = "xaErrorCode:";
786        final int index = message.lastIndexOf(marker);
787        if (index > -1) {
788            try {
789                return Integer.parseInt(message.substring(index + marker.length()));
790            } catch (Exception ignored) {}
791        }
792        return fallbackCode;
793    }
794
795    public ActiveMQConnection getConnection() {
796        return connection;
797    }
798
799    // for RAR xa recovery where xaresource connection is per request
800    public ActiveMQConnection setConnection(ActiveMQConnection connection) {
801        ActiveMQConnection existing = this.connection;
802        this.connection = connection;
803        return existing;
804    }
805
806    public void cleanup() {
807        associatedXid = null;
808        transactionId = null;
809    }
810
811    @Override
812    public String toString() {
813        return "TransactionContext{" +
814                "transactionId=" + transactionId +
815                ",connection=" + connection +
816                '}';
817    }
818}