001 /* 002 * @(#) TransactionImpl.java 003 * 004 * JOTM: Java Open Transaction Manager 005 * 006 * 007 * This module was originally developed by 008 * 009 * - Bull S.A. as part of the JOnAS application server code released in 010 * July 1999 (www.bull.com) 011 * 012 * -------------------------------------------------------------------------- 013 * The original code and portions created by Bull SA are 014 * Copyright (c) 1999 BULL SA 015 * All rights reserved. 016 * 017 * Redistribution and use in source and binary forms, with or without 018 * modification, are permitted provided that the following conditions are met: 019 * 020 * -Redistributions of source code must retain the above copyright notice, this 021 * list of conditions and the following disclaimer. 022 * 023 * -Redistributions in binary form must reproduce the above copyright notice, 024 * this list of conditions and the following disclaimer in the documentation 025 * and/or other materials provided with the distribution. 026 * 027 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 028 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 029 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 030 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 031 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 032 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 033 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 034 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 035 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 036 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 037 * POSSIBILITY OF SUCH DAMAGE. 038 * 039 * -------------------------------------------------------------------------- 040 * Contributor(s): 041 * 01/11/06 Christophe Ney cney@batisseurs.com 042 * Added ResourceManagerListener mechanism to remove ThreadData 043 * dependency. 044 * 045 * 02/01/15 Dean Jennings - List instead of Vector for enlistedXARes delistedXARes 046 * 047 * 02/06/18 Marek Prochazka - in timeoutExpired() removed code 048 * preventing deadlock by rolling back transaction 049 * -------------------------------------------------------------------------- 050 * $Id: TransactionImpl.java,v 1.51 2006-09-07 00:18:41 tonyortiz Exp $ 051 * -------------------------------------------------------------------------- 052 */ 053 package org.objectweb.jotm; 054 055 import java.rmi.NoSuchObjectException; 056 import java.rmi.RemoteException; 057 import java.util.ArrayList; 058 import java.util.Collections; 059 import java.util.Date; 060 import java.util.HashMap; 061 import java.util.List; 062 import java.util.Map; 063 064 import javax.rmi.PortableRemoteObject; 065 import javax.transaction.HeuristicMixedException; 066 import javax.transaction.HeuristicRollbackException; 067 import javax.transaction.RollbackException; 068 import javax.transaction.Status; 069 import javax.transaction.Synchronization; 070 import javax.transaction.SystemException; 071 import javax.transaction.Transaction; 072 import javax.transaction.TransactionRolledbackException; 073 import javax.transaction.xa.XAException; 074 import javax.transaction.xa.XAResource; 075 076 /** 077 * TransactionImpl is the implementation of the Transaction interface, defined in JTA specifications. This object is 078 * intended to be used by the EJBServer for transaction management. It is used indirectly by the UserTransaction 079 * implementation too, i.e. the Current object. The goal is to use the JTA interface to hide the JTM interface to the 080 * caller (EJBServer, Bean or Client). 081 */ 082 083 public class TransactionImpl implements Transaction, TimerEventListener { 084 085 // ------------------------------------------------------------------ 086 // Private data 087 // ------------------------------------------------------------------ 088 private SubCoordinator subcoord = null; 089 private TransactionContext myCtx = null; 090 private Xid myXid = null; 091 private boolean genXidhashcode = false; 092 private boolean genXidtostring = false; 093 private int myXidhashcode = 0; 094 private String myXidtostring = null; 095 private Date txDate = null; 096 private TimerEvent timer = null; // keep this to unvalidate timer 097 private RecoveryCoordinator recoveryCoord = null; 098 // / store enlisted resources 099 private List<XAResource> enlistedXARes = Collections.synchronizedList(new ArrayList<XAResource>()); 100 // / store suspended resources 101 private List<XAResource> delistedXARes = null; 102 103 /** 104 * propagate context or not. No need to propagate Context when accessing TM for example TODO Add a synchro on this 105 * object. 106 */ 107 private boolean propagateCtx = true; 108 private List<javax.transaction.xa.Xid> enlistedJavaxXid = Collections 109 .synchronizedList(new ArrayList<javax.transaction.xa.Xid>()); 110 private Map<Object, Object> userResourceMap = null; 111 112 /** 113 * Actual Status is kept inside SubCoordinator. This one is used only when SubCoordinator does not exist. 114 */ 115 private int localstatus = Status.STATUS_ACTIVE; 116 117 private boolean toremove = false; 118 119 // ------------------------------------------------------------------ 120 // Constructors 121 // ------------------------------------------------------------------ 122 123 /** 124 * New transaction (begin). 125 * 126 * @param xid 127 * transaction Xid 128 * @param timeout 129 * The value of the timeout in seconds. 130 * @throws SystemException 131 * could not build Transaction Context 132 */ 133 public TransactionImpl(Xid xid, int timeout) throws SystemException { 134 if (TraceTm.jta.isDebugEnabled()) { 135 TraceTm.jta.debug("xid= " + xid); 136 TraceTm.jta.debug("timeout= " + timeout); 137 } 138 139 // Build a propagation context local (no ref to JTM yet) 140 myXid = xid; 141 myCtx = new InternalTransactionContext(timeout, null, xid); 142 } 143 144 /** 145 * New Transaction for this thread (setPropagationContext) 146 * 147 * @param pctx 148 * propagation context 149 * 150 */ 151 public TransactionImpl(TransactionContext pctx) { 152 153 if (pctx == null) { 154 TraceTm.jotm.error("TransactionImpl: null PropagationContext"); 155 return; 156 } 157 myCtx = pctx; 158 myXid = pctx.getXid(); 159 160 // Make interposition in any case, to solve problems of memory leaks 161 // and bad transaction count, in case no resource will be implied. 162 // -> this make sure that commit will remove this object. 163 try { 164 makeSubCoord(true, true); 165 } catch (RollbackException e) { 166 toremove = true; 167 TraceTm.jotm.debug("already rolled back"); 168 localstatus = Status.STATUS_ROLLEDBACK; 169 } catch (SystemException e) { 170 toremove = true; 171 TraceTm.jotm.error("cannot make subcoordinator"); 172 localstatus = Status.STATUS_ROLLEDBACK; 173 } 174 } 175 176 // ------------------------------------------------------------------ 177 // Transaction Synchronization Registry implementation 178 // ------------------------------------------------------------------ 179 180 /** 181 * Save User Resource 182 * 183 * @param key 184 * object 185 * 186 * @param value 187 * object 188 * 189 */ 190 public synchronized void putUserResource(Object key, Object value) { 191 192 if (userResourceMap == null) { 193 userResourceMap = Collections.synchronizedMap(new HashMap<Object, Object>()); 194 } 195 userResourceMap.put(key, value); 196 } 197 198 /** 199 * Get User Resource 200 * 201 * @return Object object 202 * 203 * @param key 204 * object 205 * 206 */ 207 public synchronized Object getUserResource(Object key) { 208 209 if (userResourceMap == null) { 210 return null; 211 } 212 213 return userResourceMap.get(key); 214 } 215 216 /** 217 * Register InterposedSynchronization 218 * 219 * @param sync 220 * synchronization 221 * @throws IllegalStateException 222 * could not register synchronization 223 */ 224 public void registerInterposedSynchronization(Synchronization sync) throws IllegalStateException { 225 try { 226 registerSynchronization(sync); 227 } catch (Exception e) { 228 throw new IllegalStateException(); 229 } 230 } 231 232 // ------------------------------------------------------------------ 233 // Transaction implementation 234 // ------------------------------------------------------------------ 235 236 /** 237 * Complete the transaction represented by this Transaction object The calling thread is not required to have the 238 * same transaction associated with the thread. (JTA 3.3.3) 239 * 240 * @exception RollbackException 241 * Thrown to indicate that the transaction has been rolled back rather than committed. 242 * 243 * @exception HeuristicMixedException 244 * Thrown to indicate that a heuristic decision was made and that some relevant updates have been 245 * committed while others have been rolled back. 246 * 247 * @exception HeuristicRollbackException 248 * Thrown to indicate that a heuristic decision was made and that some relevant updates have been 249 * rolled back. 250 * 251 * @exception SecurityException 252 * Thrown to indicate that the thread is not allowed to commit the transaction. 253 * 254 * @exception IllegalStateException 255 * Thrown if the current thread is not associated with a transaction. 256 * 257 * @exception SystemException 258 * Thrown if the transaction manager encounters an unexpected error condition 259 */ 260 @Override 261 public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, 262 SecurityException, SystemException { 263 264 if (TraceTm.jta.isDebugEnabled()) { 265 TraceTm.jta.debug("TransactionImpl.commit (tx= " + this + ")"); 266 } 267 268 // *** Distributed transaction. 269 Terminator term = myCtx.getTerminator(); 270 271 if (term != null) { 272 // Commits the Transaction, with heuristic report 273 try { 274 propagateCtx = false; 275 term.commit(true); 276 } catch (TransactionRolledbackException e) { 277 Current.getCurrent().forgetTx(getXid()); 278 if (TraceTm.jta.isDebugEnabled()) { 279 TraceTm.jta.debug("Commit distributed transaction -> rolled back!"); 280 } 281 localstatus = Status.STATUS_ROLLEDBACK; 282 RollbackException ex = new RollbackException(); 283 ex.initCause(e); 284 throw ex; 285 } catch (RemoteException e) { 286 287 if (TraceTm.jta.isWarnEnabled()) { 288 TraceTm.jta.warn("got a RemoteException", e); 289 } 290 291 if (e.detail instanceof TransactionRolledbackException) { 292 Current.getCurrent().forgetTx(getXid()); 293 if (TraceTm.jta.isDebugEnabled()) { 294 TraceTm.jta.debug("Commit distributed transaction -> rolled back!"); 295 } 296 localstatus = Status.STATUS_ROLLEDBACK; 297 RollbackException ex = new RollbackException(); 298 ex.initCause(e.detail); 299 throw ex; 300 } 301 302 if (e.detail instanceof HeuristicMixed) { 303 TraceTm.jotm.info("Commit distributed transaction -> Heuristic mixed!"); 304 throw new HeuristicMixedException(); 305 } else { 306 SystemException ex = new SystemException("Unexpected RemoteException on commit:" 307 + e.detail.getMessage()); 308 ex.initCause(e.detail); 309 throw ex; 310 } 311 } catch (Exception e) { 312 TraceTm.jotm.error("Unexpected Exception on commit:", e); 313 SystemException ex = new SystemException("Unexpected Exception on commit"); 314 ex.initCause(e); 315 throw ex; 316 } finally { 317 propagateCtx = true; 318 if (subcoord == null) { 319 // if no coordinator, timer will not be unset by JTM. 320 unsetTimer(); 321 } 322 } 323 Current.getCurrent().forgetTx(getXid()); 324 localstatus = Status.STATUS_COMMITTED; 325 return; 326 } 327 328 // *** Local transaction 329 // commit_one_phase may raise remote exceptions. We must rethrow local exceptions. 330 331 if (subcoord != null) { 332 try { 333 subcoord.commit_one_phase(); 334 } catch (TransactionRolledbackException e) { 335 if (TraceTm.jta.isDebugEnabled()) { 336 TraceTm.jta.debug("Commit local transaction -> rolled back!"); 337 } 338 Current.getCurrent().forgetTx(getXid()); 339 localstatus = Status.STATUS_ROLLEDBACK; 340 RollbackException ex = new RollbackException(); 341 ex.initCause(e); 342 throw ex; 343 } catch (RemoteException e) { 344 TraceTm.jotm.error("Unexpected Exception on commit_one_phase:", e); 345 Current.getCurrent().forgetTx(getXid()); 346 localstatus = Status.STATUS_UNKNOWN; 347 SystemException ex = new SystemException("Unexpected Exception on commit_one_phase"); 348 ex.initCause(e); 349 throw ex; 350 } 351 } else { 352 // if no coordinator, just unset the timer and release this object. 353 unsetTimer(); 354 Current.getCurrent().forgetTx(getXid()); 355 localstatus = Status.STATUS_COMMITTED; 356 } 357 } 358 359 /** 360 * Delist the resource specified from the current transaction associated with the calling thread. 361 * 362 * @param xares 363 * The XAResource object representing the resource to delist 364 * 365 * @param flag 366 * One of the values of TMSUCCESS, TMSUSPEND, or TMFAIL. 367 * 368 * @exception IllegalStateException 369 * Thrown if the transaction in the target object is inactive. 370 * 371 * @exception SystemException 372 * Thrown if the transaction manager encounters an unexpected error condition 373 * 374 * @return true if the dissociation of the Resource is successful; false otherwise. 375 */ 376 @Override 377 public boolean delistResource(XAResource xares, int flag) throws IllegalStateException, SystemException { 378 379 if (TraceTm.jta.isDebugEnabled()) { 380 TraceTm.jta.debug("TransactionImpl.delistResource"); 381 TraceTm.jta.debug("xares= " + xares + ", flag= " + flag); 382 } 383 384 if (enlistedXARes == null) { 385 if (TraceTm.jta.isDebugEnabled()) { 386 TraceTm.jta.error("No XA resources enlisted by JOTM"); 387 } 388 return false; 389 } 390 391 // Verify that the XAResource to be delisted was enlisted earlier. 392 393 if (!enlistedXARes.contains(xares)) { 394 if (TraceTm.jta.isDebugEnabled()) { 395 TraceTm.jta.error("XAResouce " + xares + " not enlisted by JOTM"); 396 } 397 return false; 398 } 399 400 javax.transaction.xa.Xid javaxxid = subcoord.getJavaxXid(subcoord.getXaresIndex(xares)); 401 402 if (!enlistedJavaxXid.contains(javaxxid)) { 403 if (TraceTm.jta.isDebugEnabled()) { 404 TraceTm.jta.error("XAResouce " + xares + " not enlisted by JOTM"); 405 } 406 return false; 407 } 408 409 int javaxxidindex = enlistedJavaxXid.indexOf(javaxxid); 410 javax.transaction.xa.Xid myjavaxxid = enlistedJavaxXid.get(javaxxidindex); 411 412 if (TraceTm.jta.isDebugEnabled()) { 413 TraceTm.jta.debug("delisted with resource= " + xares); 414 TraceTm.jta.debug("end myjavaxxid= " + myjavaxxid); 415 } 416 417 // Send the XA end to the XAResource 418 try { 419 xares.end(myjavaxxid, flag); 420 } catch (XAException e) { 421 String error = "Cannot send XA end:" + e + " (error code = " + e.errorCode + ") --" + e.getMessage(); 422 TraceTm.jotm.error(error); 423 if (TraceTm.jta.isDebugEnabled()) { 424 TraceTm.jotm.debug("xares.end= " + xares); 425 } 426 throw new SystemException(error); 427 } 428 429 if (TraceTm.jta.isDebugEnabled()) { 430 TraceTm.jta.debug("enlistedXAres.remove xares= " + xares); 431 } 432 433 // / remove from enlisted list 434 enlistedXARes.remove(xares); 435 enlistedJavaxXid.remove(javaxxid); 436 return true; 437 } 438 439 /** 440 * Enlist the resource specified with the current transaction context of the calling thread 441 * 442 * @param xares 443 * The XAResource object representing the resource to enlist 444 * 445 * @return <i>true</i> if the resource was enlisted successfully; otherwise false. 446 * 447 * @exception RollbackException 448 * Thrown to indicate that the transaction has been marked for rollback only. 449 * 450 * @exception IllegalStateException 451 * Thrown if the transaction in the target object is in prepared state or the transaction is 452 * inactive. 453 * 454 * @exception SystemException 455 * Thrown if the transaction manager encounters an unexpected error condition 456 * 457 */ 458 @Override 459 public boolean enlistResource(XAResource xares) throws RollbackException, IllegalStateException, SystemException { 460 461 if (TraceTm.jta.isDebugEnabled()) { 462 TraceTm.jta.debug("TransactionImpl.enlistResource"); 463 TraceTm.jta.debug("xares= " + xares); 464 } 465 466 // Check trivial cases 467 if (xares == null) { 468 TraceTm.jotm.error("enlistResource: null argument"); 469 throw new SystemException("enlistResource: null argument"); 470 } 471 472 if (myCtx == null) { 473 throw new SystemException("enlistResource: no Transactional Context"); 474 } 475 476 // make a subCoordinator object if not existing yet 477 if (subcoord == null) { 478 makeSubCoord(false, true); 479 if (subcoord == null) { 480 TraceTm.jotm.error("enlistResource: could not create subcoordinator"); 481 throw new SystemException("enlistResource: could not create subcoordinator"); 482 } 483 } 484 485 boolean found; 486 487 try { 488 found = subcoord.addResource(xares); 489 } catch (IllegalStateException e) { 490 throw new IllegalStateException("enlistResource: could not addResource " + xares); 491 } 492 // Transaction may have been set rollback only. 493 // Enlist is done, but RollbackException will be thrown anyway. 494 495 // Send the XA start to the XAResource 496 // A new Xid branch should be generated in case of new RM (if !found) 497 // See JTA Specifications, page 12/13. 498 int flag = found ? XAResource.TMJOIN : XAResource.TMNOFLAGS; 499 500 if ((delistedXARes != null) && delistedXARes.contains(xares)) { 501 flag = XAResource.TMRESUME; 502 } 503 504 Xid resXid = new XidImpl(getXid(), subcoord.getXaresIndex(xares)); 505 javax.transaction.xa.Xid javaxxid = new JavaXidImpl(resXid); 506 507 if (TraceTm.jta.isDebugEnabled()) { 508 TraceTm.jta.debug("enlisted with resource= " + xares); 509 TraceTm.jta.debug("start javaxxid= " + javaxxid); 510 } 511 512 if (!found) { 513 subcoord.addJavaxXid(javaxxid); 514 } 515 516 try { 517 xares.start(javaxxid, flag); 518 } catch (XAException e) { 519 String error = "Cannot send XA(" + xares + ") start:" + e + " (error code = " + e.errorCode + ") --" 520 + e.getMessage(); 521 TraceTm.jotm.error(error); 522 throw new SystemException(error); 523 } 524 525 if (!enlistedXARes.contains(xares)) { 526 // / add to enlisted list 527 enlistedXARes.add(xares); 528 enlistedJavaxXid.add(javaxxid); 529 } 530 531 int status = this.getStatus(); 532 533 switch (status) { 534 case Status.STATUS_ACTIVE: 535 case Status.STATUS_PREPARING: 536 break; 537 case Status.STATUS_PREPARED: 538 throw new IllegalStateException("Transaction already prepared."); 539 case Status.STATUS_COMMITTING: 540 // throw new IllegalStateException("Transaction already started committing."); 541 break; 542 case Status.STATUS_COMMITTED: 543 throw new IllegalStateException("Transaction already committed."); 544 case Status.STATUS_MARKED_ROLLBACK: 545 throw new RollbackException("Transaction already marked for rollback"); 546 case Status.STATUS_ROLLING_BACK: 547 throw new RollbackException("Transaction already started rolling back."); 548 case Status.STATUS_ROLLEDBACK: 549 throw new RollbackException("Transaction already rolled back."); 550 case Status.STATUS_NO_TRANSACTION: 551 throw new IllegalStateException("No current transaction."); 552 case Status.STATUS_UNKNOWN: 553 throw new IllegalStateException("Unknown transaction status"); 554 default: 555 throw new IllegalStateException("Illegal transaction status: " + status); 556 } 557 558 return true; 559 } 560 561 /** 562 * delist all enlisted resources and move to suspended 563 */ 564 public void doDetach(int flag) throws SystemException { 565 if (TraceTm.jta.isDebugEnabled()) { 566 TraceTm.jta.debug("TransactionImpl.doDetach flag= " + XAResourceHelper.getFlagName(flag)); 567 TraceTm.jta.debug("number of enlisted= " + enlistedXARes.size()); 568 } 569 570 // always copy enlisted to suspended resource list 571 // since jonas may resume the transaction in beforecompletion 572 delistedXARes = new ArrayList<XAResource>(enlistedXARes); 573 for (XAResource xar : delistedXARes) { 574 delistResource(xar, flag); 575 } 576 } 577 578 /** 579 * enlist/clear all suspended resource 580 */ 581 public void doAttach(int flag) throws SystemException, RollbackException { 582 if (TraceTm.jta.isDebugEnabled()) { 583 TraceTm.jta.debug("TransactionImpl.doAttach flag= " + XAResourceHelper.getFlagName(flag)); 584 TraceTm.jta.debug("number of enlisted= " + enlistedXARes.size()); 585 } 586 587 boolean rollbackonenlist = false; 588 RollbackException mye = null; 589 590 // we attach suspended transactions 591 592 if (flag == XAResource.TMRESUME) { 593 // we may be calling resume from beforecompletion on transaction that are not suspended! 594 595 for (int i = 0; (delistedXARes != null) && (i < delistedXARes.size()); i++) { 596 597 try { 598 enlistResource(delistedXARes.get(i)); 599 } catch (RollbackException e) { 600 if (!rollbackonenlist) { 601 rollbackonenlist = true; 602 mye = e; 603 } 604 } 605 } 606 } 607 608 delistedXARes = null; 609 610 if (rollbackonenlist) { 611 throw new RollbackException(mye.getMessage()); 612 } 613 } 614 615 /** 616 * get a copy of the list of currently enlisted resource 617 */ 618 public List getEnlistedXAResource() { 619 if (TraceTm.jta.isDebugEnabled()) { 620 TraceTm.jta.debug("getEnlistedXAResource size= " + enlistedXARes.size()); 621 } 622 return new ArrayList<XAResource>(enlistedXARes); 623 } 624 625 /** 626 * Obtain the status of the transaction associated with the current thread. 627 * 628 * @return The transaction status. If no transaction is associated with the current thread, this method returns the 629 * Status.NoTransaction value. 630 * 631 * @exception SystemException 632 * Thrown if the transaction manager encounters an unexpected error condition 633 * 634 */ 635 @Override 636 public int getStatus() throws SystemException { 637 if (TraceTm.jta.isDebugEnabled()) { 638 TraceTm.jta.debug("TransactionImpl.getStatus()"); 639 } 640 641 // *** Distributed transaction 642 Coordinator coord = myCtx.getCoordinator(); 643 644 if (coord != null) { 645 // Ask the transaction status to JTM 646 int ret; 647 try { 648 propagateCtx = false; 649 ret = coord.get_status(); 650 } catch (Exception e) { 651 TraceTm.jotm.error("cannot reach JTM:", e); 652 return Status.STATUS_NO_TRANSACTION; 653 } finally { 654 propagateCtx = true; 655 } 656 return ret; 657 } 658 659 // *** Local transaction 660 // The status is kept in the subcoordinator 661 if (subcoord != null) { 662 return subcoord.getStatus(); 663 } else { 664 return localstatus; 665 } 666 } 667 668 /** 669 * Register a synchronization object for the transaction currently associated with the calling thread. The 670 * transction manager invokes the beforeCompletion method prior to starting the transaction commit process. After 671 * the transaction is completed, the transaction manager invokes the afterCompletion method. 672 * 673 * @param sync 674 * The javax.transaction.Synchronization object for the transaction associated with the target object 675 * 676 * @exception RollbackException 677 * Thrown to indicate that the transaction has been marked for rollback only. 678 * 679 * @exception IllegalStateException 680 * Thrown if the transaction in the target object is in prepared state or the transaction is 681 * inactive. 682 * 683 * @exception SystemException 684 * Thrown if the transaction manager encounters an unexpected error condition 685 */ 686 @Override 687 public void registerSynchronization(Synchronization sync) throws RollbackException, IllegalStateException, 688 SystemException { 689 if (TraceTm.jta.isDebugEnabled()) { 690 TraceTm.jta.debug("Synchro=" + sync); 691 } 692 693 // It's time to make the subcoordinator, if not existing yet. 694 if (subcoord == null) { 695 makeSubCoord(false, true); 696 } 697 698 // Add Synchronization to the list. 699 // may raise exceptions 700 subcoord.addSynchronization(sync); 701 } 702 703 /** 704 * Rollback the transaction represented by this Transaction object. 705 * 706 * @exception IllegalStateException 707 * Thrown if the transaction in the target object is in prepared state or the transaction is 708 * inactive. 709 * 710 * @exception SystemException 711 * Thrown if the transaction manager encounters an unexpected error condition 712 * 713 */ 714 @Override 715 public void rollback() throws IllegalStateException, SystemException { 716 if (TraceTm.jta.isDebugEnabled()) { 717 TraceTm.jta.debug("TransactionImpl.rollback(tx= " + this + ")"); 718 } 719 720 // *** Distributed transaction. 721 Terminator term = myCtx.getTerminator(); 722 723 if (term != null) { 724 // Rollback the Transaction 725 try { 726 propagateCtx = false; 727 term.rollback(); 728 } catch (java.rmi.ServerException e) { 729 // HeuristicCommit ???? 730 throw new IllegalStateException("Exception on rollback:" + e); 731 } catch (Exception e) { 732 Current.getCurrent().forgetTx(getXid()); 733 localstatus = Status.STATUS_UNKNOWN; 734 735 clearUserResourceMap(); 736 737 throw new SystemException("Unexpected Exception on rollback"); 738 } finally { 739 propagateCtx = true; 740 } 741 742 if (subcoord == null) { 743 // if no coordinator, timer will not be unset by JTM. 744 unsetTimer(); 745 } 746 747 // release this object. 748 Current.getCurrent().forgetTx(getXid()); 749 localstatus = Status.STATUS_ROLLEDBACK; 750 751 clearUserResourceMap(); 752 753 return; 754 } 755 756 // *** Local transaction. 757 // if no coordinator, nothing to do. 758 759 if (subcoord != null) { 760 try { 761 subcoord.rollback(); 762 } catch (RemoteException e) { 763 Current.getCurrent().forgetTx(getXid()); 764 localstatus = Status.STATUS_UNKNOWN; 765 766 clearUserResourceMap(); 767 768 throw new IllegalStateException("Exception on rollback:" + e); 769 } 770 771 } else { 772 // if no coordinator, just unset the timer. 773 unsetTimer(); 774 } 775 776 // release this object. 777 Current.getCurrent().forgetTx(getXid()); 778 localstatus = Status.STATUS_ROLLEDBACK; 779 780 clearUserResourceMap(); 781 } 782 783 /** 784 * Prepare the transaction represented by this Transaction object. 785 * 786 * @exception IllegalStateException 787 * Thrown if the transaction in the target object is in prepared state or the transaction is 788 * inactive. 789 * 790 * @exception SystemException 791 * Thrown if the transaction manager encounters an unexpected error condition 792 * 793 * @return prepare status 794 */ 795 public int prepare() throws IllegalStateException, SystemException { 796 if (TraceTm.jta.isDebugEnabled()) { 797 TraceTm.jta.debug("TransactionImpl.prepare(tx= " + this + ")"); 798 } 799 800 int ret = 0; 801 if (subcoord != null) { 802 try { 803 ret = subcoord.prepare(); 804 } catch (RemoteException e) { 805 TraceTm.jotm.error("Unexpected Exception on prepare:", e); 806 throw new SystemException("Unexpected Exception on prepare"); 807 } 808 } 809 return ret; 810 } 811 812 /** 813 * Modify the transaction associated with the current thread such that the only possible outcome of the transaction 814 * is to roll back the transaction. 815 * 816 * @exception IllegalStateException 817 * Thrown if the current thread is not associated with any transaction. 818 * 819 * @exception SystemException 820 * Thrown if the transaction manager encounters an unexpected error condition 821 * 822 */ 823 @Override 824 public void setRollbackOnly() throws IllegalStateException, SystemException { 825 if (TraceTm.jta.isDebugEnabled()) { 826 TraceTm.jta.debug("Tx=" + this); 827 } 828 829 Coordinator coord = myCtx.getCoordinator(); 830 831 if (coord != null) { 832 // Distributed transaction 833 try { 834 propagateCtx = false; 835 coord.rollback_only(); 836 } catch (RemoteException e) { 837 TraceTm.jotm.error("Cannot perform coordinator rollback only", e); 838 } finally { 839 propagateCtx = true; 840 } 841 } 842 843 // perform this even for distributed transactions: 844 // resolves bugs 300077, 300078 845 if (subcoord == null) { 846 // make a subCoordinator object if not existing yet 847 try { 848 makeSubCoord(false, false); 849 } catch (RollbackException e) { 850 TraceTm.jotm.debug("already rolled back"); 851 return; 852 } 853 } 854 subcoord.setRollbackOnly(); 855 } 856 857 // ------------------------------------------------------------------ 858 // TimerEventListener implementation 859 // ------------------------------------------------------------------ 860 861 /** 862 * timeout for that transaction has expired 863 */ 864 865 @Override 866 public void timeoutExpired(Object arg) { 867 if (TraceTm.jta.isDebugEnabled()) { 868 TraceTm.jta.debug("TransactionImpl.timeoutExpired"); 869 } 870 871 // increment counter for management 872 Current.getCurrent().incrementExpiredCounter(); 873 874 // make the subcoordinator object, if not existing yet. 875 876 if (subcoord == null) { 877 // if this is a proxy, just forget this object. The JTM will 878 // rollback transaction with its own timer. 879 Terminator term = myCtx.getTerminator(); 880 881 if (term != null) { 882 if (TraceTm.jta.isDebugEnabled()) { 883 TraceTm.jta.debug("forget tx (tx=" + this + ")"); 884 } 885 Current.getCurrent().forgetTx(getXid()); 886 localstatus = Status.STATUS_ROLLEDBACK; 887 return; 888 } 889 try { 890 makeSubCoord(false, false); 891 } catch (RollbackException e) { 892 TraceTm.jotm.debug("already rolled back"); 893 localstatus = Status.STATUS_ROLLEDBACK; 894 return; 895 } catch (SystemException e) { 896 TraceTm.jotm.error("cannot make subcoordinator"); 897 localstatus = Status.STATUS_ROLLEDBACK; 898 return; 899 } 900 } 901 902 // Try to set it "rollback only" 903 // avoids a rollback while SQL requests are in progress 904 if (TraceTm.jta.isDebugEnabled()) { 905 TraceTm.jta.debug("set rollback only (tx=" + this + ")"); 906 } 907 908 try { 909 subcoord.setRollbackOnly(); 910 } catch (Exception e) { 911 TraceTm.jotm.error("cannot rollbackonly:" + e); 912 } 913 } 914 915 // ------------------------------------------------------------------ 916 // This object is used as an HashTable index 917 // ------------------------------------------------------------------ 918 919 /** 920 * return true if objects are identical 921 */ 922 @Override 923 public boolean equals(Object obj2) { 924 if (obj2 instanceof TransactionImpl) { 925 TransactionImpl tx2 = (TransactionImpl) obj2; 926 927 // trivial case 928 if (tx2 == this) { 929 return true; 930 } 931 932 // compare otids 933 return getXid().equals(tx2.getXid()); 934 } else { 935 return false; 936 } 937 } 938 939 /** 940 * return a hashcode value for this object 941 */ 942 @Override 943 public int hashCode() { 944 if (!genXidhashcode) { 945 genXidhashcode = true; 946 myXidhashcode = getXid().hashCode(); 947 } 948 949 return myXidhashcode; 950 } 951 952 // ------------------------------------------------------------------ 953 // Other methods 954 // ------------------------------------------------------------------ 955 956 /** 957 * string form 958 */ 959 @Override 960 public String toString() { 961 if (!genXidtostring) { 962 genXidtostring = true; 963 myXidtostring = getXid().toString(); 964 } 965 return myXidtostring; 966 } 967 968 /** 969 * Return associated PropagationContext Used for implicit Context propagation. 970 * 971 * @param hold 972 * true if must increment the count to hold the object (not used!) 973 * @return PropagationContext associated with the transaction. 974 */ 975 public synchronized TransactionContext getPropagationContext(boolean hold) { 976 if (propagateCtx) { 977 return myCtx; 978 } else { 979 return null; 980 } 981 } 982 983 /** 984 * set a timer for the transaction 985 * 986 * @param timer 987 * the timer event to set 988 */ 989 public void setTimer(TimerEvent timer) { 990 if (TraceTm.jta.isDebugEnabled()) { 991 TraceTm.jta.debug("set timer for tx (timer=" + timer + ", tx=" + this + ")"); 992 } 993 this.timer = timer; 994 } 995 996 /** 997 * unset the timer 998 */ 999 public void unsetTimer() { 1000 if (TraceTm.jta.isDebugEnabled()) { 1001 TraceTm.jta.debug("unset timer for tx (timer=" + timer + ", tx=" + this + ")"); 1002 } 1003 if (timer != null) { 1004 timer.unset(); 1005 timer = null; 1006 } 1007 } 1008 1009 /** 1010 * set the date time stamp for the transaction 1011 * 1012 * @param date 1013 * the Date to set for the transaction 1014 */ 1015 public void setTxDate(Date date) { 1016 if (TraceTm.jta.isDebugEnabled()) { 1017 TraceTm.jta.debug("set date for tx (data=" + date + ", tx=" + this + ")"); 1018 } 1019 txDate = (Date) date.clone(); 1020 } 1021 1022 /** 1023 * get the date time stamp for the transaction 1024 * 1025 * @return the timestamp 1026 */ 1027 public Date getTxDate() { 1028 if (TraceTm.jta.isDebugEnabled()) { 1029 TraceTm.jta.debug("get date for tx (date=" + txDate + ", tx=" + this + ")"); 1030 } 1031 return (Date) txDate.clone(); 1032 } 1033 1034 /** 1035 * update the propagation context We should be inside the reply of a request involved in a tx here! 1036 * 1037 * @param pctx 1038 * propagation context 1039 */ 1040 public synchronized void updatePropagationContext(TransactionContext pctx) { 1041 if (TraceTm.jta.isDebugEnabled()) { 1042 TraceTm.jta.debug("TransactionImpl.updatePropagationContext"); 1043 } 1044 1045 Coordinator remoteCoord = pctx.getCoordinator(); 1046 1047 if (remoteCoord == null && myCtx.getCoordinator() != null) { 1048 TraceTm.jotm.error("setPropagationContext: Bad Coordinator"); 1049 TraceTm.jotm.error("remoteCoord = " + remoteCoord); 1050 TraceTm.jotm.error("myCtx.getCoordinator()= " + myCtx.getCoordinator()); 1051 return; 1052 } 1053 1054 // Interpose subCoordinator if newly distributed Tx 1055 if (remoteCoord != null && myCtx.getCoordinator() == null) { 1056 myCtx.setCoordinator(pctx.getCoordinator()); 1057 1058 if (subcoord != null) { 1059 // register the subCoordinator as a Resource. 1060 TraceTm.jta.debug("register the subCoordinator as a Resource"); 1061 try { 1062 propagateCtx = false; 1063 recoveryCoord = remoteCoord.register_resource(subcoord); 1064 } catch (RemoteException e) { 1065 TraceTm.jotm.warn("Cannot make interposition :" + e.getCause()); 1066 return; 1067 } finally { 1068 propagateCtx = true; 1069 } 1070 } 1071 } 1072 1073 if (pctx.getTerminator() != null) { 1074 myCtx.setTerminator(pctx.getTerminator()); 1075 } 1076 } 1077 1078 /** 1079 * Get the Xid of the transaction 1080 * 1081 * @return the Xid 1082 */ 1083 public Xid getXid() { 1084 return myXid; 1085 } 1086 1087 /** 1088 * make a SubCoordinator for this Transaction object 1089 * 1090 * @param interpose 1091 * Make interposition if not already done. 1092 * @param active 1093 * transaction still active. 1094 */ 1095 private void makeSubCoord(boolean interpose, boolean active) throws RollbackException, SystemException { 1096 if (TraceTm.jta.isDebugEnabled()) { 1097 TraceTm.jta.debug("make subcoordinator"); 1098 } 1099 1100 // Build the SubCoordinator object 1101 try { 1102 subcoord = new SubCoordinator(this, getXid()); 1103 } catch (RemoteException e) { 1104 // should never go here. 1105 TraceTm.jotm.error("new SubCoordinator raised exception: ", e); 1106 return; 1107 } 1108 if (!active) { 1109 // Just create the subCoordinator to store some state locally. 1110 // Any attempt to register will fail if transaction is rolled back. 1111 return; 1112 } 1113 1114 // If interposition must be done: do it now! 1115 // Each time we have a remoteCoord + a subCoord, we must interpose. 1116 Coordinator remoteCoord = myCtx.getCoordinator(); 1117 1118 // First of all, create the Control object on JTM 1119 // if it was not created before, if interpose flag is set. 1120 if (interpose && remoteCoord == null) { 1121 try { 1122 if (TraceTm.jta.isDebugEnabled()) { 1123 TraceTm.jta.debug("Creating a remote Control on JTM for a distributed transaction"); 1124 } 1125 propagateCtx = false; 1126 // Control coord = Current.getJTM().create(myCtx.getTimeout()); 1127 // Control coord = Current.getJTM().create(myCtx.getTimeout(), getXid()); 1128 Control coord = Current.getJTM().recreate(myCtx); 1129 remoteCoord = (Coordinator) javax.rmi.PortableRemoteObject.narrow(coord, Coordinator.class); 1130 1131 } catch (RemoteException e) { 1132 TraceTm.jotm.error("Cannot create distributed transaction:", e); 1133 cleanup(); 1134 return; 1135 } finally { 1136 propagateCtx = true; 1137 } 1138 1139 myCtx.setCoordinator(remoteCoord); 1140 1141 // fix for transaction context propagation with 1142 // the Jeremie protocol 1143 1144 if (myCtx.getTerminator() == null) { 1145 myCtx.setTerminator((Terminator) remoteCoord); 1146 } 1147 } 1148 1149 // Achieve interposition if not already done: 1150 // - register the subCoordinator as a Resource. 1151 if (remoteCoord != null && recoveryCoord == null) { 1152 try { 1153 propagateCtx = false; 1154 recoveryCoord = remoteCoord.register_resource(subcoord); 1155 } catch (RemoteException e) { 1156 // If cannot be registered, destroy it. 1157 // transaction status will be only local. 1158 cleanup(); 1159 if (e.getCause() instanceof TransactionRolledbackException) { 1160 TraceTm.jotm.warn("Cannot Make Interposition: rolled back occured"); 1161 throw new RollbackException("Cannot Make Interposition"); 1162 } else { 1163 TraceTm.jotm.warn("Cannot make Interposition:" + e.getCause()); 1164 throw new SystemException("Cannot Make Interposition"); 1165 } 1166 } finally { 1167 propagateCtx = true; 1168 } 1169 } 1170 // increment counter for management 1171 Current.getCurrent().incrementBeginCounter(); 1172 } 1173 1174 public boolean toRemove() { 1175 return toremove; 1176 } 1177 1178 public void cleanup() { 1179 if (subcoord != null) { 1180 TraceTm.jta.debug("unexport SubCoordinator"); 1181 try { 1182 PortableRemoteObject.unexportObject(subcoord); 1183 } catch (NoSuchObjectException e) { 1184 TraceTm.jta.debug("Cannot unexport subcoord:" + e); 1185 } 1186 subcoord = null; 1187 } 1188 } 1189 1190 /** 1191 * clear userResourceMap 1192 */ 1193 private synchronized void clearUserResourceMap() { 1194 1195 if ((userResourceMap != null) && !(userResourceMap.isEmpty())) { 1196 userResourceMap.clear(); 1197 userResourceMap = null; 1198 } 1199 } 1200 1201 }