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 */ 017 018 package org.apache.geronimo.transaction.manager; 019 020 import java.io.PrintWriter; 021 import java.io.StringWriter; 022 import java.io.Writer; 023 import java.util.ArrayList; 024 import java.util.HashMap; 025 import java.util.IdentityHashMap; 026 import java.util.Iterator; 027 import java.util.LinkedList; 028 import java.util.List; 029 import java.util.Map; 030 import java.util.Set; 031 032 import javax.transaction.HeuristicMixedException; 033 import javax.transaction.HeuristicRollbackException; 034 import javax.transaction.RollbackException; 035 import javax.transaction.Status; 036 import javax.transaction.Synchronization; 037 import javax.transaction.SystemException; 038 import javax.transaction.Transaction; 039 import javax.transaction.xa.XAException; 040 import javax.transaction.xa.XAResource; 041 import javax.transaction.xa.Xid; 042 043 import org.slf4j.Logger; 044 import org.slf4j.LoggerFactory; 045 046 047 /** 048 * Basic local transaction with support for multiple resources. 049 * 050 * @version $Rev: 737084 $ $Date: 2009-01-23 11:46:23 -0500 (Fri, 23 Jan 2009) $ 051 */ 052 public class TransactionImpl implements Transaction { 053 private static final Logger log = LoggerFactory.getLogger("Transaction"); 054 055 private final XidFactory xidFactory; 056 private final Xid xid; 057 private final TransactionLog txnLog; 058 private final long timeout; 059 private final List syncList = new ArrayList(5); 060 private final List interposedSyncList = new ArrayList(3); 061 private final LinkedList resourceManagers = new LinkedList(); 062 private final IdentityHashMap activeXaResources = new IdentityHashMap(3); 063 private final IdentityHashMap suspendedXaResources = new IdentityHashMap(3); 064 private int status = Status.STATUS_NO_TRANSACTION; 065 private Object logMark; 066 067 private final Map resources = new HashMap(); 068 069 TransactionImpl(XidFactory xidFactory, TransactionLog txnLog, long transactionTimeoutMilliseconds) throws SystemException { 070 this(xidFactory.createXid(), xidFactory, txnLog, transactionTimeoutMilliseconds); 071 } 072 073 TransactionImpl(Xid xid, XidFactory xidFactory, TransactionLog txnLog, long transactionTimeoutMilliseconds) throws SystemException { 074 this.xidFactory = xidFactory; 075 this.txnLog = txnLog; 076 this.xid = xid; 077 this.timeout = transactionTimeoutMilliseconds + TransactionTimer.getCurrentTime(); 078 try { 079 txnLog.begin(xid); 080 } catch (LogException e) { 081 status = Status.STATUS_MARKED_ROLLBACK; 082 SystemException ex = new SystemException("Error logging begin; transaction marked for roll back)"); 083 ex.initCause(e); 084 throw ex; 085 } 086 status = Status.STATUS_ACTIVE; 087 } 088 089 //reconstruct a tx for an external tx found in recovery 090 public TransactionImpl(Xid xid, TransactionLog txLog) { 091 this.xidFactory = null; 092 this.txnLog = txLog; 093 this.xid = xid; 094 status = Status.STATUS_PREPARED; 095 //TODO is this a good idea? 096 this.timeout = Long.MAX_VALUE; 097 } 098 099 public synchronized int getStatus() { 100 return status; 101 } 102 103 public Object getResource(Object key) { 104 return resources.get(key); 105 } 106 107 public boolean getRollbackOnly() { 108 return status == Status.STATUS_MARKED_ROLLBACK; 109 } 110 111 public Object getTransactionKey() { 112 return xid; 113 } 114 115 public int getTransactionStatus() { 116 return status; 117 } 118 119 public void putResource(Object key, Object value) { 120 if (key == null) { 121 throw new NullPointerException("You must supply a non-null key for putResource"); 122 } 123 resources.put(key, value); 124 } 125 126 public void registerInterposedSynchronization(Synchronization synchronization) { 127 interposedSyncList.add(synchronization); 128 } 129 130 public synchronized void setRollbackOnly() throws IllegalStateException { 131 switch (status) { 132 case Status.STATUS_ACTIVE: 133 case Status.STATUS_PREPARING: 134 status = Status.STATUS_MARKED_ROLLBACK; 135 break; 136 case Status.STATUS_MARKED_ROLLBACK: 137 case Status.STATUS_ROLLING_BACK: 138 // nothing to do 139 break; 140 default: 141 throw new IllegalStateException("Cannot set rollback only, status is " + getStateString(status)); 142 } 143 } 144 145 public synchronized void registerSynchronization(Synchronization synch) throws IllegalStateException, RollbackException, SystemException { 146 if (synch == null) { 147 throw new IllegalArgumentException("Synchronization is null"); 148 } 149 switch (status) { 150 case Status.STATUS_ACTIVE: 151 case Status.STATUS_PREPARING: 152 break; 153 case Status.STATUS_MARKED_ROLLBACK: 154 throw new RollbackException("Transaction is marked for rollback"); 155 default: 156 throw new IllegalStateException("Status is " + getStateString(status)); 157 } 158 syncList.add(synch); 159 } 160 161 public synchronized boolean enlistResource(XAResource xaRes) throws IllegalStateException, RollbackException, SystemException { 162 if (xaRes == null) { 163 throw new IllegalArgumentException("XAResource is null"); 164 } 165 switch (status) { 166 case Status.STATUS_ACTIVE: 167 break; 168 case Status.STATUS_MARKED_ROLLBACK: 169 break; 170 default: 171 throw new IllegalStateException("Status is " + getStateString(status)); 172 } 173 174 if (activeXaResources.containsKey(xaRes)) { 175 throw new IllegalStateException("xaresource: " + xaRes + " is already enlisted!"); 176 } 177 178 try { 179 TransactionBranch manager = (TransactionBranch) suspendedXaResources.remove(xaRes); 180 if (manager != null) { 181 //we know about this one, it was suspended 182 xaRes.start(manager.getBranchId(), XAResource.TMRESUME); 183 activeXaResources.put(xaRes, manager); 184 return true; 185 } 186 //it is not suspended. 187 for (Iterator i = resourceManagers.iterator(); i.hasNext();) { 188 manager = (TransactionBranch) i.next(); 189 boolean sameRM; 190 //if the xares is already known, we must be resuming after a suspend. 191 if (xaRes == manager.getCommitter()) { 192 throw new IllegalStateException("xaRes " + xaRes + " is a committer but is not active or suspended"); 193 } 194 //Otherwise, see if this is a new xares for the same resource manager 195 try { 196 sameRM = xaRes.isSameRM(manager.getCommitter()); 197 } catch (XAException e) { 198 log.warn("Unexpected error checking for same RM", e); 199 continue; 200 } 201 if (sameRM) { 202 xaRes.start(manager.getBranchId(), XAResource.TMJOIN); 203 activeXaResources.put(xaRes, manager); 204 return true; 205 } 206 } 207 //we know nothing about this XAResource or resource manager 208 Xid branchId = xidFactory.createBranch(xid, resourceManagers.size() + 1); 209 xaRes.start(branchId, XAResource.TMNOFLAGS); 210 activeXaResources.put(xaRes, addBranchXid(xaRes, branchId)); 211 return true; 212 } catch (XAException e) { 213 log.warn("Unable to enlist XAResource " + xaRes + ", errorCode: " + e.errorCode, e); 214 // mark status as rollback only because enlist resource failed 215 setRollbackOnly(); 216 return false; 217 } 218 } 219 220 public synchronized boolean delistResource(XAResource xaRes, int flag) throws IllegalStateException, SystemException { 221 if (!(flag == XAResource.TMFAIL || flag == XAResource.TMSUCCESS || flag == XAResource.TMSUSPEND)) { 222 throw new IllegalStateException("invalid flag for delistResource: " + flag); 223 } 224 if (xaRes == null) { 225 throw new IllegalArgumentException("XAResource is null"); 226 } 227 switch (status) { 228 case Status.STATUS_ACTIVE: 229 case Status.STATUS_MARKED_ROLLBACK: 230 break; 231 default: 232 throw new IllegalStateException("Status is " + getStateString(status)); 233 } 234 TransactionBranch manager = (TransactionBranch) activeXaResources.remove(xaRes); 235 if (manager == null) { 236 if (flag == XAResource.TMSUSPEND) { 237 throw new IllegalStateException("trying to suspend an inactive xaresource: " + xaRes); 238 } 239 //not active, and we are not trying to suspend. We must be ending tx. 240 manager = (TransactionBranch) suspendedXaResources.remove(xaRes); 241 if (manager == null) { 242 throw new IllegalStateException("Resource not known to transaction: " + xaRes); 243 } 244 } 245 246 try { 247 xaRes.end(manager.getBranchId(), flag); 248 if (flag == XAResource.TMSUSPEND) { 249 suspendedXaResources.put(xaRes, manager); 250 } 251 return true; 252 } catch (XAException e) { 253 log.warn("Unable to delist XAResource " + xaRes + ", error code: " + e.errorCode, e); 254 return false; 255 } 256 } 257 258 //Transaction method, does 2pc 259 public void commit() throws HeuristicMixedException, HeuristicRollbackException, RollbackException, SecurityException, SystemException { 260 beforePrepare(); 261 262 try { 263 boolean timedout = false; 264 if (TransactionTimer.getCurrentTime() > timeout) { 265 status = Status.STATUS_MARKED_ROLLBACK; 266 timedout = true; 267 } 268 269 if (status == Status.STATUS_MARKED_ROLLBACK) { 270 rollbackResourcesDuringCommit(resourceManagers, false); 271 if (timedout) { 272 throw new RollbackException("Unable to commit: Transaction timeout"); 273 } else { 274 throw new RollbackException("Unable to commit: transaction marked for rollback"); 275 } 276 } 277 synchronized (this) { 278 if (status == Status.STATUS_ACTIVE) { 279 if (this.resourceManagers.size() == 0) { 280 // nothing to commit 281 status = Status.STATUS_COMMITTED; 282 } else if (this.resourceManagers.size() == 1) { 283 // one-phase commit decision 284 status = Status.STATUS_COMMITTING; 285 } else { 286 // start prepare part of two-phase 287 status = Status.STATUS_PREPARING; 288 } 289 } 290 // resourceManagers is now immutable 291 } 292 293 // no-phase 294 if (resourceManagers.size() == 0) { 295 synchronized (this) { 296 status = Status.STATUS_COMMITTED; 297 } 298 return; 299 } 300 301 // one-phase 302 if (resourceManagers.size() == 1) { 303 TransactionBranch manager = (TransactionBranch) resourceManagers.getFirst(); 304 commitResource(manager); 305 return; 306 } 307 308 boolean willCommit = false; 309 try { 310 // two-phase 311 willCommit = internalPrepare(); 312 } catch (SystemException e) { 313 rollbackResources(resourceManagers); 314 throw e; 315 } 316 317 // notify the RMs 318 if (willCommit) { 319 commitResources(resourceManagers); 320 } else { 321 // set everRollback to true here because the rollback here is caused by 322 // XAException during the above internalPrepare 323 rollbackResourcesDuringCommit(resourceManagers, true); 324 } 325 } finally { 326 afterCompletion(); 327 synchronized (this) { 328 status = Status.STATUS_NO_TRANSACTION; 329 } 330 } 331 } 332 333 //Used from XATerminator for first phase in a remotely controlled tx. 334 int prepare() throws SystemException, RollbackException { 335 beforePrepare(); 336 int result = XAResource.XA_RDONLY; 337 try { 338 LinkedList rms; 339 synchronized (this) { 340 if (status == Status.STATUS_ACTIVE) { 341 if (resourceManagers.size() == 0) { 342 // nothing to commit 343 status = Status.STATUS_COMMITTED; 344 return result; 345 } else { 346 // start prepare part of two-phase 347 status = Status.STATUS_PREPARING; 348 } 349 } 350 // resourceManagers is now immutable 351 rms = resourceManagers; 352 } 353 354 boolean willCommit = internalPrepare(); 355 356 // notify the RMs 357 if (willCommit) { 358 if (!rms.isEmpty()) { 359 result = XAResource.XA_OK; 360 } 361 } else { 362 rollbackResources(rms); 363 throw new RollbackException("Unable to commit"); 364 } 365 } finally { 366 if (result == XAResource.XA_RDONLY) { 367 afterCompletion(); 368 synchronized (this) { 369 status = Status.STATUS_NO_TRANSACTION; 370 } 371 } 372 } 373 return result; 374 } 375 376 //used from XATerminator for commit phase of non-readonly remotely controlled tx. 377 void preparedCommit() throws HeuristicRollbackException, HeuristicMixedException, SystemException { 378 try { 379 commitResources(resourceManagers); 380 } finally { 381 afterCompletion(); 382 synchronized (this) { 383 status = Status.STATUS_NO_TRANSACTION; 384 } 385 } 386 } 387 388 //helper method used by Transaction.commit and XATerminator prepare. 389 private void beforePrepare() { 390 synchronized (this) { 391 switch (status) { 392 case Status.STATUS_ACTIVE: 393 case Status.STATUS_MARKED_ROLLBACK: 394 break; 395 default: 396 throw new IllegalStateException("Status is " + getStateString(status)); 397 } 398 } 399 400 beforeCompletion(); 401 endResources(); 402 } 403 404 405 //helper method used by Transaction.commit and XATerminator prepare. 406 private boolean internalPrepare() throws SystemException { 407 for (Iterator rms = resourceManagers.iterator(); rms.hasNext();) { 408 synchronized (this) { 409 if (status != Status.STATUS_PREPARING) { 410 // we were marked for rollback 411 break; 412 } 413 } 414 TransactionBranch manager = (TransactionBranch) rms.next(); 415 try { 416 int vote = manager.getCommitter().prepare(manager.getBranchId()); 417 if (vote == XAResource.XA_RDONLY) { 418 // we don't need to consider this RM any more 419 rms.remove(); 420 } 421 } catch (XAException e) { 422 if (e.errorCode == XAException.XAER_RMERR 423 || e.errorCode == XAException.XAER_PROTO 424 || e.errorCode == XAException.XAER_INVAL) { 425 throw (SystemException) new SystemException("Error during prepare; transaction was rolled back").initCause(e); 426 } 427 synchronized (this) { 428 status = Status.STATUS_MARKED_ROLLBACK; 429 /* Per JTA spec, If the resource manager wants to roll back the transaction, 430 it should do so by throwing an appropriate XAException in the prepare method. 431 Also per OTS spec: 432 The resource can return VoteRollback under any circumstances, including not having 433 any knowledge about the transaction (which might happen after a crash). If this 434 response is returned, the transaction must be rolled back. Furthermore, the Transaction 435 Service is not required to perform any additional operations on this resource.*/ 436 //rms.remove(); 437 break; 438 } 439 } 440 } 441 442 // decision time... 443 boolean willCommit; 444 synchronized (this) { 445 willCommit = (status != Status.STATUS_MARKED_ROLLBACK); 446 if (willCommit) { 447 status = Status.STATUS_PREPARED; 448 } 449 } 450 // log our decision 451 if (willCommit && !resourceManagers.isEmpty()) { 452 try { 453 logMark = txnLog.prepare(xid, resourceManagers); 454 } catch (LogException e) { 455 try { 456 rollbackResources(resourceManagers); 457 } catch (Exception se) { 458 log.error("Unable to rollback after failure to log prepare", se.getCause()); 459 } 460 throw (SystemException) new SystemException("Error logging prepare; transaction was rolled back)").initCause(e); 461 } 462 } 463 return willCommit; 464 } 465 466 public void rollback() throws IllegalStateException, SystemException { 467 List rms; 468 synchronized (this) { 469 switch (status) { 470 case Status.STATUS_ACTIVE: 471 status = Status.STATUS_MARKED_ROLLBACK; 472 break; 473 case Status.STATUS_MARKED_ROLLBACK: 474 break; 475 default: 476 throw new IllegalStateException("Status is " + getStateString(status)); 477 } 478 rms = resourceManagers; 479 } 480 481 endResources(); 482 try { 483 rollbackResources(rms); 484 //only write rollback record if we have already written prepare record. 485 if (logMark != null) { 486 try { 487 txnLog.rollback(xid, logMark); 488 } catch (LogException e) { 489 try { 490 rollbackResources(rms); 491 } catch (Exception se) { 492 log.error("Unable to rollback after failure to log decision", se.getCause()); 493 } 494 throw (SystemException) new SystemException("Error logging rollback").initCause(e); 495 } 496 } 497 } finally { 498 afterCompletion(); 499 synchronized (this) { 500 status = Status.STATUS_NO_TRANSACTION; 501 } 502 } 503 } 504 505 private void beforeCompletion() { 506 beforeCompletion(syncList); 507 beforeCompletion(interposedSyncList); 508 } 509 510 private void beforeCompletion(List syncs) { 511 int i = 0; 512 while (true) { 513 Synchronization synch; 514 synchronized (this) { 515 if (i == syncs.size()) { 516 return; 517 } else { 518 synch = (Synchronization) syncs.get(i++); 519 } 520 } 521 try { 522 synch.beforeCompletion(); 523 } catch (Exception e) { 524 log.warn("Unexpected exception from beforeCompletion; transaction will roll back", e); 525 synchronized (this) { 526 status = Status.STATUS_MARKED_ROLLBACK; 527 } 528 } 529 } 530 } 531 532 private void afterCompletion() { 533 // this does not synchronize because nothing can modify our state at this time 534 afterCompletion(interposedSyncList); 535 afterCompletion(syncList); 536 } 537 538 private void afterCompletion(List syncs) { 539 for (Iterator i = syncs.iterator(); i.hasNext();) { 540 Synchronization synch = (Synchronization) i.next(); 541 try { 542 synch.afterCompletion(status); 543 } catch (Exception e) { 544 log.warn("Unexpected exception from afterCompletion; continuing", e); 545 } 546 } 547 } 548 549 private void endResources() { 550 endResources(activeXaResources); 551 endResources(suspendedXaResources); 552 } 553 554 private void endResources(IdentityHashMap resourceMap) { 555 while (true) { 556 XAResource xaRes; 557 TransactionBranch manager; 558 int flags; 559 synchronized (this) { 560 Set entrySet = resourceMap.entrySet(); 561 if (entrySet.isEmpty()) { 562 return; 563 } 564 Map.Entry entry = (Map.Entry) entrySet.iterator().next(); 565 xaRes = (XAResource) entry.getKey(); 566 manager = (TransactionBranch) entry.getValue(); 567 flags = (status == Status.STATUS_MARKED_ROLLBACK) ? XAResource.TMFAIL : XAResource.TMSUCCESS; 568 resourceMap.remove(xaRes); 569 } 570 try { 571 xaRes.end(manager.getBranchId(), flags); 572 } catch (XAException e) { 573 log.warn("Error ending association for XAResource " + xaRes + "; transaction will roll back. XA error code: " + e.errorCode, e); 574 synchronized (this) { 575 status = Status.STATUS_MARKED_ROLLBACK; 576 } 577 } 578 } 579 } 580 581 private void rollbackResources(List rms) throws SystemException { 582 SystemException cause = null; 583 synchronized (this) { 584 status = Status.STATUS_ROLLING_BACK; 585 } 586 try { 587 for (Iterator i = rms.iterator(); i.hasNext();) { 588 TransactionBranch manager = (TransactionBranch) i.next(); 589 try { 590 manager.getCommitter().rollback(manager.getBranchId()); 591 } catch (XAException e) { 592 log.error("Unexpected exception rolling back " + manager.getCommitter() + "; continuing with rollback", e); 593 if (e.errorCode == XAException.XA_HEURRB) { 594 // let's not set the cause here 595 log.info("Transaction has been heuristically rolled back " + manager.getCommitter() + "; continuing with rollback", e); 596 manager.getCommitter().forget(manager.getBranchId()); 597 } else if (e.errorCode == XAException.XA_RBROLLBACK 598 || e.errorCode == XAException.XAER_RMERR 599 || e.errorCode == XAException.XAER_NOTA 600 || e.errorCode == XAException.XAER_RMFAIL) { 601 // let's not set the cause here because we expect the transaction to be rolled back eventually 602 // TODO: for RMFAIL, it means resource unavailable temporarily. 603 // do we need keep sending request to resource to make sure the roll back completes? 604 } else if (cause == null) { 605 cause = new SystemException(e.errorCode); 606 } 607 } 608 } 609 } catch (XAException e) { 610 throw (SystemException) new SystemException("Error during rolling back").initCause(e); 611 } 612 613 synchronized (this) { 614 status = Status.STATUS_ROLLEDBACK; 615 } 616 if (cause != null) { 617 throw cause; 618 } 619 } 620 621 private void rollbackResourcesDuringCommit(List rms, boolean everRb) throws HeuristicMixedException, RollbackException, SystemException { 622 XAException cause = null; 623 boolean everRolledback = everRb; 624 synchronized (this) { 625 status = Status.STATUS_ROLLING_BACK; 626 } 627 try { 628 for (Iterator i = rms.iterator(); i.hasNext();) { 629 TransactionBranch manager = (TransactionBranch) i.next(); 630 try { 631 manager.getCommitter().rollback(manager.getBranchId()); 632 everRolledback = true; 633 } catch (XAException e) { 634 if (e.errorCode == XAException.XA_HEURRB) { 635 // let's not set the cause here as the resulting behavior is same as requested behavior 636 log.error("Transaction has been heuristically rolled back " + manager.getCommitter() + "; continuing with rollback", e); 637 everRolledback = true; 638 manager.getCommitter().forget(manager.getBranchId()); 639 } else if (e.errorCode == XAException.XA_HEURMIX) { 640 log.error("Transaction has been heuristically committed and rolled back " + manager.getCommitter() + "; continuing with rollback", e); 641 cause = e; 642 everRolledback = true; 643 manager.getCommitter().forget(manager.getBranchId()); 644 } else if (e.errorCode == XAException.XA_HEURCOM) { 645 log.error("Transaction has been heuristically committed " + manager.getCommitter() + "; continuing with rollback", e); 646 cause = e; 647 manager.getCommitter().forget(manager.getBranchId()); 648 } else if (e.errorCode == XAException.XA_RBROLLBACK 649 || e.errorCode == XAException.XAER_RMERR 650 || e.errorCode == XAException.XAER_NOTA 651 || e.errorCode == XAException.XAER_RMFAIL) { 652 // XAException.XA_RBROLLBACK during commit/rollback, thus RollbackException is expected 653 // XAException.XAER_RMERR means transaction branch error and transaction has been rolled back 654 // let's not set the cause here because we expect the transaction to be rolled back eventually 655 // TODO: for RMFAIL, it means resource unavailable temporarily. 656 // do we need keep sending request to resource to make sure the roll back completes? 657 } else if (cause == null) { 658 cause = e; 659 } 660 } 661 } 662 } catch (XAException e) { 663 throw (SystemException) new SystemException("System error during commit/rolling back").initCause(e); 664 } 665 666 synchronized (this) { 667 status = Status.STATUS_ROLLEDBACK; 668 } 669 670 if (cause == null) { 671 throw (RollbackException) new RollbackException("Unable to commit: transaction marked for rollback").initCause(cause); 672 } else { 673 if (cause.errorCode == XAException.XA_HEURCOM && everRolledback) { 674 throw (HeuristicMixedException) new HeuristicMixedException("HeuristicMixed error during commit/rolling back").initCause(cause); 675 } else if (cause.errorCode == XAException.XA_HEURMIX) { 676 throw (HeuristicMixedException) new HeuristicMixedException("HeuristicMixed error during commit/rolling back").initCause(cause); 677 } else { 678 throw (SystemException) new SystemException("System Error during commit/rolling back").initCause(cause); 679 } 680 } 681 } 682 683 private void commitResource(TransactionBranch manager) throws RollbackException, HeuristicRollbackException, HeuristicMixedException, SystemException{ 684 XAException cause = null; 685 try { 686 try { 687 688 manager.getCommitter().commit(manager.getBranchId(), true); 689 synchronized (this) { 690 status = Status.STATUS_COMMITTED; 691 } 692 return; 693 } catch (XAException e) { 694 synchronized (this) { 695 status = Status.STATUS_ROLLEDBACK; 696 } 697 698 if (e.errorCode == XAException.XA_HEURRB) { 699 cause = e; 700 manager.getCommitter().forget(manager.getBranchId()); 701 //throw (HeuristicRollbackException) new HeuristicRollbackException("Error during one-phase commit").initCause(e); 702 } else if (e.errorCode == XAException.XA_HEURMIX) { 703 cause = e; 704 manager.getCommitter().forget(manager.getBranchId()); 705 throw (HeuristicMixedException) new HeuristicMixedException("Error during one-phase commit").initCause(e); 706 } else if (e.errorCode == XAException.XA_HEURCOM) { 707 // let's not throw an exception as the transaction has been committed 708 log.info("Transaction has been heuristically committed"); 709 manager.getCommitter().forget(manager.getBranchId()); 710 } else if (e.errorCode == XAException.XA_RBROLLBACK 711 || e.errorCode == XAException.XAER_RMERR 712 || e.errorCode == XAException.XAER_NOTA) { 713 // Per XA spec, XAException.XAER_RMERR from commit means An error occurred in 714 // committing the work performed on behalf of the transaction branch 715 // and the branchÕs work has been rolled back. 716 // XAException.XAER_NOTA: ssume the DB took a unilateral rollback decision and forgot the transaction 717 log.info("Transaction has been rolled back"); 718 cause = e; 719 // throw (RollbackException) new RollbackException("Error during one-phase commit").initCause(e); 720 } else { 721 cause = e; 722 //throw (SystemException) new SystemException("Error during one-phase commit").initCause(e); 723 } 724 } 725 } catch (XAException e) { 726 if (e.errorCode == XAException.XAER_NOTA) { 727 // NOTA in response to forget, means the resource already forgot the transaction 728 // ignore 729 } else { 730 throw (SystemException) new SystemException("Error during one phase commit").initCause(e); 731 } 732 } 733 734 if (cause != null) { 735 if (cause.errorCode == XAException.XA_HEURRB) { 736 throw (HeuristicRollbackException) new HeuristicRollbackException("Error during two phase commit").initCause(cause); 737 } else if (cause.errorCode == XAException.XA_HEURMIX) { 738 throw (HeuristicMixedException) new HeuristicMixedException("Error during two phase commit").initCause(cause); 739 } else if (cause.errorCode == XAException.XA_RBROLLBACK 740 || cause.errorCode == XAException.XAER_RMERR 741 || cause.errorCode == XAException.XAER_NOTA) { 742 throw (RollbackException) new RollbackException("Error during two phase commit").initCause(cause); 743 } else { 744 throw (SystemException) new SystemException("Error during two phase commit").initCause(cause); 745 } 746 } 747 } 748 749 private void commitResources(List rms) throws HeuristicRollbackException, HeuristicMixedException, SystemException { 750 XAException cause = null; 751 boolean evercommit = false; 752 synchronized (this) { 753 status = Status.STATUS_COMMITTING; 754 } 755 try { 756 for (Iterator i = rms.iterator(); i.hasNext();) { 757 TransactionBranch manager = (TransactionBranch) i.next(); 758 try { 759 manager.getCommitter().commit(manager.getBranchId(), false); 760 evercommit = true; 761 } catch (XAException e) { 762 log.error("Unexpected exception committing " + manager.getCommitter() + "; continuing to commit other RMs", e); 763 764 if (e.errorCode == XAException.XA_HEURRB) { 765 log.info("Transaction has been heuristically rolled back"); 766 cause = e; 767 manager.getCommitter().forget(manager.getBranchId()); 768 } else if (e.errorCode == XAException.XA_HEURMIX) { 769 log.info("Transaction has been heuristically committed and rolled back"); 770 cause = e; 771 evercommit = true; 772 manager.getCommitter().forget(manager.getBranchId()); 773 } else if (e.errorCode == XAException.XA_HEURCOM) { 774 // let's not throw an exception as the transaction has been committed 775 log.info("Transaction has been heuristically committed"); 776 evercommit = true; 777 manager.getCommitter().forget(manager.getBranchId()); 778 } else { 779 cause = e; 780 } 781 } 782 } 783 } catch (XAException e) { 784 if (e.errorCode == XAException.XAER_NOTA) { 785 // NOTA in response to forget, means the resource already forgot the transaction 786 // ignore 787 } else { 788 throw (SystemException) new SystemException("Error during two phase commit").initCause(e); 789 } 790 } 791 //if all resources were read only, we didn't write a prepare record. 792 if (!rms.isEmpty()) { 793 try { 794 txnLog.commit(xid, logMark); 795 } catch (LogException e) { 796 log.error("Unexpected exception logging commit completion for xid " + xid, e); 797 throw (SystemException) new SystemException("Unexpected error logging commit completion for xid " + xid).initCause(e); 798 } 799 } 800 synchronized (this) { 801 status = Status.STATUS_COMMITTED; 802 } 803 if (cause != null) { 804 if (cause.errorCode == XAException.XA_HEURRB && !evercommit) { 805 throw (HeuristicRollbackException) new HeuristicRollbackException("Error during two phase commit").initCause(cause); 806 } else if (cause.errorCode == XAException.XA_HEURRB && evercommit) { 807 throw (HeuristicMixedException) new HeuristicMixedException("Error during two phase commit").initCause(cause); 808 } else if (cause.errorCode == XAException.XA_HEURMIX) { 809 throw (HeuristicMixedException) new HeuristicMixedException("Error during two phase commit").initCause(cause); 810 } else { 811 throw (SystemException) new SystemException("Error during two phase commit").initCause(cause); 812 } 813 } 814 } 815 816 private static String getStateString(int status) { 817 switch (status) { 818 case Status.STATUS_ACTIVE: 819 return "STATUS_ACTIVE"; 820 case Status.STATUS_PREPARING: 821 return "STATUS_PREPARING"; 822 case Status.STATUS_PREPARED: 823 return "STATUS_PREPARED"; 824 case Status.STATUS_MARKED_ROLLBACK: 825 return "STATUS_MARKED_ROLLBACK"; 826 case Status.STATUS_ROLLING_BACK: 827 return "STATUS_ROLLING_BACK"; 828 case Status.STATUS_COMMITTING: 829 return "STATUS_COMMITTING"; 830 case Status.STATUS_COMMITTED: 831 return "STATUS_COMMITTED"; 832 case Status.STATUS_ROLLEDBACK: 833 return "STATUS_ROLLEDBACK"; 834 case Status.STATUS_NO_TRANSACTION: 835 return "STATUS_NO_TRANSACTION"; 836 case Status.STATUS_UNKNOWN: 837 return "STATUS_UNKNOWN"; 838 default: 839 throw new AssertionError(); 840 } 841 } 842 843 public boolean equals(Object obj) { 844 if (obj instanceof TransactionImpl) { 845 TransactionImpl other = (TransactionImpl) obj; 846 return xid.equals(other.xid); 847 } else { 848 return false; 849 } 850 } 851 852 //when used from recovery, do not add manager to active or suspended resource maps. 853 // The xaresources have already been ended with TMSUCCESS. 854 public TransactionBranch addBranchXid(XAResource xaRes, Xid branchId) { 855 TransactionBranch manager = new TransactionBranch(xaRes, branchId); 856 resourceManagers.add(manager); 857 return manager; 858 } 859 860 private static class TransactionBranch implements TransactionBranchInfo { 861 private final XAResource committer; 862 private final Xid branchId; 863 864 public TransactionBranch(XAResource xaRes, Xid branchId) { 865 committer = xaRes; 866 this.branchId = branchId; 867 } 868 869 public XAResource getCommitter() { 870 return committer; 871 } 872 873 public Xid getBranchId() { 874 return branchId; 875 } 876 877 public String getResourceName() { 878 if (committer instanceof NamedXAResource) { 879 return ((NamedXAResource) committer).getName(); 880 } else { 881 // if it isn't a named resource should we really stop all processing here! 882 // Maybe this would be better to handle else where and do we really want to prevent all processing of transactions? 883 log.error("Please correct the integration and supply a NamedXAResource", new IllegalStateException("Cannot log transactions as " + committer + " is not a NamedXAResource.")); 884 return committer.toString(); 885 } 886 } 887 888 public Xid getBranchXid() { 889 return branchId; 890 } 891 } 892 893 894 }