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    }