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