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