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