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}