View Javadoc

1   /**
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.geronimo.transaction.manager;
19  
20  import java.io.PrintWriter;
21  import java.io.StringWriter;
22  import java.io.Writer;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.IdentityHashMap;
26  import java.util.Iterator;
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  
32  import javax.transaction.HeuristicMixedException;
33  import javax.transaction.HeuristicRollbackException;
34  import javax.transaction.RollbackException;
35  import javax.transaction.Status;
36  import javax.transaction.Synchronization;
37  import javax.transaction.SystemException;
38  import javax.transaction.Transaction;
39  import javax.transaction.xa.XAException;
40  import javax.transaction.xa.XAResource;
41  import javax.transaction.xa.Xid;
42  
43  import org.slf4j.Logger;
44  import org.slf4j.LoggerFactory;
45  
46  
47  /**
48   * Basic local transaction with support for multiple resources.
49   *
50   * @version $Rev: 737084 $ $Date: 2009-01-23 11:46:23 -0500 (Fri, 23 Jan 2009) $
51   */
52  public class TransactionImpl implements Transaction {
53      private static final Logger log = LoggerFactory.getLogger("Transaction");
54  
55      private final XidFactory xidFactory;
56      private final Xid xid;
57      private final TransactionLog txnLog;
58      private final long timeout;
59      private final List syncList = new ArrayList(5);
60      private final List interposedSyncList = new ArrayList(3);
61      private final LinkedList resourceManagers = new LinkedList();
62      private final IdentityHashMap activeXaResources = new IdentityHashMap(3);
63      private final IdentityHashMap suspendedXaResources = new IdentityHashMap(3);
64      private int status = Status.STATUS_NO_TRANSACTION;
65      private Object logMark;
66  
67      private final Map resources = new HashMap();
68  
69      TransactionImpl(XidFactory xidFactory, TransactionLog txnLog, long transactionTimeoutMilliseconds) throws SystemException {
70          this(xidFactory.createXid(), xidFactory, txnLog, transactionTimeoutMilliseconds);
71      }
72  
73      TransactionImpl(Xid xid, XidFactory xidFactory, TransactionLog txnLog, long transactionTimeoutMilliseconds) throws SystemException {
74          this.xidFactory = xidFactory;
75          this.txnLog = txnLog;
76          this.xid = xid;
77          this.timeout = transactionTimeoutMilliseconds + TransactionTimer.getCurrentTime();
78          try {
79              txnLog.begin(xid);
80          } catch (LogException e) {
81              status = Status.STATUS_MARKED_ROLLBACK;
82              SystemException ex = new SystemException("Error logging begin; transaction marked for roll back)");
83              ex.initCause(e);
84              throw ex;
85          }
86          status = Status.STATUS_ACTIVE;
87      }
88  
89      //reconstruct a tx for an external tx found in recovery
90      public TransactionImpl(Xid xid, TransactionLog txLog) {
91          this.xidFactory = null;
92          this.txnLog = txLog;
93          this.xid = xid;
94          status = Status.STATUS_PREPARED;
95          //TODO is this a good idea?
96          this.timeout = Long.MAX_VALUE;
97      }
98  
99      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 }