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