001    /**
002     * Copyright 2005-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.objectweb.jotm;
017    
018    import java.nio.ByteBuffer;
019    import java.rmi.RemoteException;
020    import java.util.List;
021    import java.util.Vector;
022    
023    import javax.rmi.PortableRemoteObject;
024    import javax.transaction.InvalidTransactionException;
025    import javax.transaction.RollbackException;
026    import javax.transaction.Status;
027    import javax.transaction.Synchronization;
028    import javax.transaction.SystemException;
029    import javax.transaction.Transaction;
030    import javax.transaction.TransactionManager;
031    import javax.transaction.TransactionRolledbackException;
032    import javax.transaction.xa.XAException;
033    import javax.transaction.xa.XAResource;
034    
035    import org.objectweb.howl.log.xa.XACommittingTx;
036    
037    /**
038     * Log associated to this transaction coordinator
039     */
040    class SLog {
041    
042            private Vector loggedResources = new Vector();
043            private Vector loggedByteResources = new Vector();
044    
045            private Vector loggedXids = new Vector();
046    
047            private Vector loggedJavaxXids = new Vector();
048    
049            int decision;
050            static final int DECISION_TO_COMMIT = 1;
051            static final int DECISION_TO_ROLLBACK = 2;
052    
053            public void addResource(XAResource res, Xid xid, javax.transaction.xa.Xid javaxxid) {
054                    if (TraceTm.jta.isDebugEnabled()) {
055                            TraceTm.jta.debug("res= " + res);
056                            TraceTm.jta.debug("xid= " + xid);
057                            TraceTm.jta.debug("javaxxid= " + javaxxid);
058                    }
059                    loggedResources.addElement(res);
060                    loggedByteResources.addElement(res.toString().getBytes());
061                    loggedXids.addElement(xid);
062                    loggedJavaxXids.addElement(javaxxid);
063            }
064    
065            /**
066             * @return a List of logged Resources
067             */
068            public List getLoggedResources() {
069                    if (TraceTm.jta.isDebugEnabled()) {
070                            TraceTm.jta.debug("logged resources=" + loggedResources);
071                    }
072                    return loggedResources;
073            }
074    
075            public List getByteLoggedResources() {
076                    if (TraceTm.jta.isDebugEnabled()) {
077                            TraceTm.jta.debug("logged resources=" + loggedResources);
078                    }
079                    return loggedByteResources;
080            }
081    
082            public List getLoggedXids() {
083                    return loggedXids;
084            }
085    
086            public List getLoggedJavaxXids() {
087                    return loggedJavaxXids;
088            }
089    
090            public void flushLog(int decide) {
091                    if (TraceTm.jta.isDebugEnabled()) {
092                            TraceTm.jta.debug("decide=" + decide);
093                    }
094                    decision = decide;
095    
096                    // XXX serialize log on disk
097            }
098    
099            public void forgetLog() {
100                    if (TraceTm.jta.isDebugEnabled()) {
101                            TraceTm.jta.debug("forget log");
102                    }
103                    // XXX remove file on disk
104            }
105    }
106    
107    class JotmTransactionRolledbackException extends TransactionRolledbackException {
108            private static final long serialVersionUID = 1L;
109    
110            public JotmTransactionRolledbackException() {
111                    super();
112            }
113    
114            public JotmTransactionRolledbackException(String message) {
115                    super(message);
116            }
117    
118            public JotmTransactionRolledbackException(String message, Throwable cause) {
119                    super(message);
120                    detail = cause;
121            }
122    
123            public JotmTransactionRolledbackException(Throwable cause) {
124                    super();
125                    detail = cause;
126            }
127    }
128    
129    /**
130     * This object is the local coordinator. It may be registered as sub-coordinator in case of distributed transaction, so
131     * it must be callable remotely and implement Resource
132     */
133    public class SubCoordinator extends PortableRemoteObject implements Resource {
134    
135            // ------------------------------------------------------------------
136            // Object state
137            // ------------------------------------------------------------------
138    
139            /**
140             * @serial
141             */
142            private TransactionImpl tx = null;
143    
144            /**
145             * List of Synchronization objects TODO should synchronize this list.
146             * 
147             * @serial
148             */
149            private Vector synchroList = new Vector();
150    
151            /**
152             * List of XAResource objects
153             * 
154             * @serial
155             */
156            private Vector resourceList = new Vector();
157            private Vector javaxxidList = new Vector();
158    
159            /**
160             * Keep a reference on TransactionManager
161             * 
162             * @serial
163             */
164            private TransactionManager tm;
165    
166            /**
167             * @serial
168             */
169            private Xid xid = null;
170    
171            /**
172             * @serial
173             */
174            private SLog log = null;
175    
176            /**
177             * javax.transaction.Status
178             * <dl>
179             * <dt>ACTIVE</dt>
180             * <dd>transaction started, commit phase not started</dd>
181             * <dt>PREPARING</dt>
182             * <dd>prepare is being sent to resources</dd>
183             * <dt>PREPARED</dt>
184             * <dd>prepare is done. Must commit now</dd>
185             * <dt>COMMITTING</dt>
186             * <dd>commit is being sent to resources</dd>
187             * <dt>COMMITTED</dt>
188             * <dd>commit was successful</dd>
189             * <dt>ROLLING_BACK</dt>
190             * <dd>not used</dd>
191             * <dt>MARKED_ROLLBACK</dt>
192             * <dd>setRollbackOnly has been called</dd>
193             * <dt>ROLLEDBACK</dt>
194             * <dd>transaction has been rolled back (or prepare returned "vote_rollback")</dd>
195             * <dt>UNKNOWN</dt>
196             * <dd>commit raised heuristics</dd>
197             * <dt>NO_TRANSACTION</dt>
198             * <dd>cannot get the status value from JTM</dd>
199             * </dl>
200             * 
201             * @serial
202             */
203            private int status = Status.STATUS_ACTIVE;
204    
205            private Exception rollbackCause;
206            /**
207             * @serial
208             */
209            private boolean beforeCompletionDone = false;
210    
211            static final String JOTM_RD_ONLY = "JOTM_RD_ONLY";
212    
213            static final byte[] RT1 = "RR1".getBytes();
214            static final byte[] RT2 = "RR2".getBytes();
215            static final byte[] JOTMDONE = "RR3JOTMDONE".getBytes();
216    
217            // ------------------------------------------------------------------
218            // Constructors
219            // ------------------------------------------------------------------
220    
221            /**
222             * constructor used by TransactionImpl
223             */
224            SubCoordinator(TransactionImpl tx, Xid xid) throws RemoteException {
225    
226                    if (TraceTm.jta.isDebugEnabled()) {
227                            TraceTm.jta.debug("tx=" + tx + ",  xid=" + xid);
228                    }
229    
230                    this.tx = tx;
231                    this.xid = xid;
232                    this.tm = Current.getTransactionManager();
233    
234                    beforeCompletionDone = false;
235            }
236    
237            // ------------------------------------------------------------------
238            // Resource interface
239            // ------------------------------------------------------------------
240    
241            /**
242             * phase 1 of the 2PC.
243             * 
244             * @return int vote commit, rollback, or readonly.
245             */
246            @Override
247            public int prepare() throws RemoteException {
248    
249                    if (TraceTm.jta.isDebugEnabled()) {
250                            TraceTm.jta.debug("status=" + StatusHelper.getStatusName(status));
251                    }
252    
253                    try {
254                            tx.doDetach(XAResource.TMSUCCESS);
255                    } catch (SystemException e) {
256    
257                            if (TraceTm.jta.isDebugEnabled()) {
258                                    String error = "Error when detaching XAResource:" + e + "--" + e.getMessage();
259                                    TraceTm.jta.debug(error);
260                            }
261                    }
262    
263                    switch (status) {
264                    case Status.STATUS_MARKED_ROLLBACK:
265                            // Doing rollback now may be an issue, because the following rollback
266                            // will not work (this object do not exist any longer)
267                            // doBeforeCompletion(false);
268                            // doRollback();
269                            status = Status.STATUS_ROLLING_BACK;
270                            return Resource.VOTE_ROLLBACK;
271                    case Status.STATUS_COMMITTED:
272                            return Resource.VOTE_COMMIT;
273                    default:
274                            doBeforeCompletion(true);
275                            break;
276                    }
277    
278                    // Recheck Status after doBeforeCompletion
279    
280                    if (status == Status.STATUS_MARKED_ROLLBACK) {
281                            TraceTm.jotm.info("Rollback during beforeCompletion in SubCoordinator.prepare");
282                            // doRollback();
283                            status = Status.STATUS_ROLLING_BACK;
284                            return Resource.VOTE_ROLLBACK;
285                    }
286    
287                    int ret = doPrepare();
288    
289                    if (ret == Resource.VOTE_READONLY) {
290                            // Transaction completed for this Resource
291                            doAfterCompletion();
292                    }
293    
294                    if (TraceTm.jta.isDebugEnabled()) {
295                            TraceTm.jta.debug("vote = " + ret);
296                    }
297    
298                    return ret;
299            }
300    
301            /**
302             * rollback transaction
303             */
304    
305            @Override
306            public void rollback() throws RemoteException {
307    
308                    if (TraceTm.jta.isDebugEnabled()) {
309                            TraceTm.jta.debug("status=" + StatusHelper.getStatusName(status));
310                    }
311    
312                    try {
313                            tx.doDetach(XAResource.TMSUCCESS);
314                    } catch (SystemException e) {
315                            if (TraceTm.jta.isDebugEnabled()) {
316                                    String error = "Error when detaching XAResource:" + e + "--" + e.getMessage();
317                                    TraceTm.jta.debug(error);
318                            }
319                    }
320    
321                    switch (status) {
322                    case Status.STATUS_ACTIVE:
323                    case Status.STATUS_MARKED_ROLLBACK:
324                    case Status.STATUS_ROLLING_BACK:
325                            if (TraceTm.jotm.isDebugEnabled()) {
326                                    TraceTm.jotm.debug("transaction rolling back");
327                            }
328                            break;
329                    case Status.STATUS_PREPARED:
330                            if (TraceTm.jotm.isDebugEnabled()) {
331                                    TraceTm.jotm.debug("should not rollback a prepared transaction");
332                            }
333                            break;
334                    case Status.STATUS_ROLLEDBACK:
335                            if (TraceTm.jotm.isDebugEnabled()) {
336                                    TraceTm.jotm.debug("already rolledback");
337                            }
338                            return;
339                    default:
340                            TraceTm.jotm.error("rollback: bad status: " + StatusHelper.getStatusName(status));
341                            return;
342                    }
343                    doBeforeCompletion(false);
344                    doRollback();
345            }
346    
347            /**
348             * phase 2 of the 2PC.
349             */
350            @Override
351            public void commit() throws RemoteException {
352    
353                    if (TraceTm.jta.isDebugEnabled()) {
354                            TraceTm.jta.debug("status=" + StatusHelper.getStatusName(status));
355                    }
356    
357                    switch (status) {
358                    case Status.STATUS_PREPARED:
359                            break;
360                    default:
361                            TraceTm.jotm.error("commit: bad status: " + StatusHelper.getStatusName(status));
362                            return;
363                    }
364                    doCommit();
365            }
366    
367            /**
368             * commit 1 phase. Called either from JTM (distributed transaction) or from Transaction.commit (local transaction).
369             */
370    
371            @Override
372            public void commit_one_phase() throws RemoteException {
373    
374                    if (TraceTm.jta.isDebugEnabled()) {
375                            TraceTm.jta.debug("status=" + StatusHelper.getStatusName(status));
376                    }
377    
378                    switch (status) {
379                    case Status.STATUS_ROLLEDBACK:
380                            try {
381                                    tx.doDetach(XAResource.TMSUCCESS);
382                            } catch (SystemException e) {
383    
384                                    if (TraceTm.jta.isDebugEnabled()) {
385                                            String error = "Error when detaching XAResource:" + e + "--" + e.getMessage();
386                                            TraceTm.jta.debug(error);
387                                    }
388                            }
389                            // KULRICE-9919 : Updated to include the rollback cause
390                            throw new JotmTransactionRolledbackException(rollbackCause);
391                            // END KULRICE-9919
392                    case Status.STATUS_MARKED_ROLLBACK:
393                            doBeforeCompletion(false);
394                            try {
395                                    tx.doDetach(XAResource.TMSUCCESS);
396                            } catch (SystemException e) {
397    
398                                    if (TraceTm.jta.isDebugEnabled()) {
399                                            String error = "Error when detaching XAResource:" + e + "--" + e.getMessage();
400                                            TraceTm.jta.debug(error);
401                                    }
402                            }
403                            doRollback();
404                            // KULRICE-9919 : Updated to include the rollback cause
405                            throw new JotmTransactionRolledbackException(rollbackCause);
406                            // END KULRICE-9919
407                    case Status.STATUS_COMMITTED:
408                            try {
409                                    tx.doDetach(XAResource.TMSUCCESS);
410                            } catch (SystemException e) {
411    
412                                    if (TraceTm.jta.isDebugEnabled()) {
413                                            String error = "Error when detaching XAResource:" + e + "--" + e.getMessage();
414                                            TraceTm.jta.debug(error);
415                                    }
416                            }
417                            return;
418                    default:
419                            doBeforeCompletion(true);
420                            try {
421                                    tx.doDetach(XAResource.TMSUCCESS);
422                            } catch (SystemException e) {
423    
424                                    if (TraceTm.jta.isDebugEnabled()) {
425                                            String error = "Error when detaching XAResource:" + e + "--" + e.getMessage();
426                                            TraceTm.jta.debug(error);
427                                    }
428                            }
429                            break;
430                    }
431    
432                    // status many have changed in doBeforeCompletion
433    
434                    if (TraceTm.jta.isDebugEnabled()) {
435                            TraceTm.jta.debug("status=" + StatusHelper.getStatusName(status));
436                    }
437    
438                    // Recheck Status after doBeforeCompletion
439    
440                    if (status == Status.STATUS_MARKED_ROLLBACK) {
441                            TraceTm.jotm.info("Rollback during beforeCompletion in SubCoordinator.commit_one_phase");
442                            doRollback();
443                            // KULRICE-9919 : Updated to include the rollback cause
444                            throw new JotmTransactionRolledbackException(rollbackCause);
445                            // END KULRICE-9919
446                    }
447    
448                    // only 1 Resource => 1 phase commit
449    
450                    if (resourceList.size() == 1) {
451                            doOnePhaseCommit();
452                            return;
453                    }
454    
455                    // 2 phase commit
456    
457                    int vote = doPrepare();
458    
459                    switch (vote) {
460                    case Resource.VOTE_COMMIT:
461                            doCommit();
462                            break;
463                    case Resource.VOTE_READONLY:
464                            doAfterCompletion();
465                            break;
466                    case Resource.VOTE_ROLLBACK:
467                            doRollback();
468                            // KULRICE-9919 : Updated to include the rollback cause
469                            throw new JotmTransactionRolledbackException(rollbackCause);
470                            // END KULRICE-9919
471                    }
472            }
473    
474            /**
475             * forget heuristics about this transaction.
476             */
477            @Override
478            public void forget() throws RemoteException {
479                    if (TraceTm.jta.isDebugEnabled()) {
480                            TraceTm.jta.debug("SubCoordinator.forget()");
481                    }
482                    doForget();
483            }
484    
485            // ------------------------------------------------------------------
486            // Other public methods (interface exposed to Transaction object)
487            // ------------------------------------------------------------------
488    
489            /**
490             * add a Synchronization to the list
491             * 
492             * @param synchro
493             *            The javax.transaction.Synchronization object for the transaction associated with the target object
494             * 
495             * @exception RollbackException
496             *                Thrown to indicate that the transaction has been marked for rollback only.
497             * 
498             * @exception IllegalStateException
499             *                Thrown if the transaction in the target object is in prepared state or the transaction is
500             *                inactive.
501             * 
502             */
503            public void addSynchronization(Synchronization synchro) throws RollbackException, IllegalStateException {
504    
505                    if (TraceTm.jta.isDebugEnabled()) {
506                            TraceTm.jta.debug("synchro=" + synchro);
507                            TraceTm.jta.debug("status=" + StatusHelper.getStatusName(status));
508                    }
509    
510                    // check status: should be ACTIVE.
511                    boolean markedRollback = false;
512    
513                    switch (status) {
514                    case Status.STATUS_MARKED_ROLLBACK:
515                    case Status.STATUS_ROLLEDBACK:
516                            markedRollback = true;
517                            break;
518                    case Status.STATUS_ACTIVE:
519                            break;
520                    default:
521                            String errorMsg = "addSynchronization: bad status = " + StatusHelper.getStatusName(status);
522                            TraceTm.jotm.error(errorMsg);
523                            throw new IllegalStateException(errorMsg);
524                    }
525    
526                    // Add synchro to the list of local synchros
527                    synchroList.addElement(synchro);
528    
529                    // If transaction marked rollback only, we add synchro but we throw
530                    // the correct exception because nobody can presume what will be done in the
531                    // Synchronization object, so we must send it beforeCompletion and afterCompletion
532                    // even in case of rollback.
533    
534                    if (markedRollback) {
535                            if (TraceTm.jta.isDebugEnabled()) {
536                                    TraceTm.jta.debug("SubCoordinator.addSynchronization: transaction rollback only");
537                            }
538                            throw new RollbackException();
539                    }
540            }
541    
542            /**
543             * add a XAResource to the list
544             * 
545             * @param xares
546             *            XAResource to register
547             * 
548             * @return true if this datasource was already known
549             * 
550             * @exception IllegalStateException
551             *                Thrown if the transaction in the target object is in prepared state or the transaction is
552             *                inactive.
553             */
554            public synchronized boolean addResource(XAResource xares) throws IllegalStateException {
555    
556                    if (TraceTm.jta.isDebugEnabled()) {
557                            TraceTm.jta.debug("xares=" + xares);
558                            TraceTm.jta.debug("status=" + StatusHelper.getStatusName(status));
559                    }
560    
561                    // check status: should be ACTIVE.
562                    boolean markedRollback = false;
563    
564                    switch (status) {
565                    case Status.STATUS_MARKED_ROLLBACK:
566                            markedRollback = true;
567                            break;
568                    case Status.STATUS_ACTIVE:
569                            break;
570                    default:
571                            String errorMsg = "SubCoordinator.addResource: bad status= " + StatusHelper.getStatusName(status);
572                            TraceTm.jotm.error(errorMsg);
573                            throw new IllegalStateException(errorMsg);
574                    }
575    
576                    // Check if this DataSource is already known
577                    // -> we must register only ONE resource per DataSource.
578    
579                    boolean found = false;
580    
581                    for (int i = 0; i < resourceList.size(); i++) {
582                            XAResource res = (XAResource) resourceList.elementAt(i);
583    
584                            try {
585                                    if (res.isSameRM(xares)) {
586                                            found = true;
587                                            break;
588                                    }
589                            } catch (XAException e) {
590                                    String error = "Cannot send res.isSameRM:" + e + " (error code = " + e.errorCode + ") --"
591                                                    + e.getMessage();
592                                    TraceTm.jotm.error("Exception on resource.isSameRM: " + error);
593                            }
594                    }
595    
596                    // add this XAResource to the list
597    
598                    if (found == false) {
599                            if (TraceTm.jta.isDebugEnabled()) {
600                                    TraceTm.jta.debug("new XAResource added to the list");
601                            }
602                            resourceList.addElement(xares);
603                    }
604    
605                    // If transaction marked rollback only, we enlist resource but we throw
606                    // the correct exception. It is important to enlist the Resource because
607                    // if we don't, the database would be updated although transaction has been
608                    // rolled back.
609    
610                    if (markedRollback) {
611                            TraceTm.jta.debug("SubCoordinator.addResource: transaction set rollback only");
612                    }
613    
614                    return found;
615            }
616    
617            /**
618             * add this javaxxid to the List
619             * 
620             * @param javaxxid
621             */
622            public synchronized void addJavaxXid(javax.transaction.xa.Xid javaxxid) {
623                    if (TraceTm.jta.isDebugEnabled()) {
624                            TraceTm.jta.debug("addJavaxXid javaxxid=" + javaxxid);
625                    }
626    
627                    // add this javaxxid to the List at the index location of XAResource
628                    javaxxidList.addElement(javaxxid);
629                    if (TraceTm.jta.isDebugEnabled()) {
630                            TraceTm.jta.debug("new JavaxXid added to the list");
631                    }
632            }
633    
634            /**
635             * Get the javaxxid at specified index in the list
636             * 
637             * @param xaresindex
638             * @return javaxxid
639             */
640            public javax.transaction.xa.Xid getJavaxXid(int xaresindex) {
641                    javax.transaction.xa.Xid myjavaxxid = (javax.transaction.xa.Xid) javaxxidList.elementAt(xaresindex);
642                    return myjavaxxid;
643            }
644    
645            /**
646             * return the status of this transaction
647             */
648            public int getStatus() {
649                    if (TraceTm.jta.isDebugEnabled()) {
650                            TraceTm.jta.debug("status=" + StatusHelper.getStatusName(status));
651                    }
652                    return status;
653            }
654    
655            /**
656             * set the transaction "rollback only"
657             */
658            public void setRollbackOnly() {
659    
660                    if (TraceTm.jta.isDebugEnabled()) {
661                            TraceTm.jta.debug("status=" + StatusHelper.getStatusName(status));
662                    }
663    
664                    switch (status) {
665                    case Status.STATUS_ACTIVE:
666                    case Status.STATUS_UNKNOWN:
667                    case Status.STATUS_PREPARING:
668                            status = Status.STATUS_MARKED_ROLLBACK;
669                            break;
670                    case Status.STATUS_MARKED_ROLLBACK:
671                    case Status.STATUS_ROLLING_BACK:
672                            break;
673                    case Status.STATUS_PREPARED:
674                    case Status.STATUS_COMMITTED:
675                    case Status.STATUS_ROLLEDBACK:
676                    case Status.STATUS_NO_TRANSACTION:
677                    case Status.STATUS_COMMITTING:
678                            TraceTm.jotm.error("Cannot set transaction as rollback only");
679                            TraceTm.jotm.error("Bad status=" + StatusHelper.getStatusName(status));
680                            break;
681                    }
682            }
683    
684            // ------------------------------------------------------------------
685            // private methods
686            // ------------------------------------------------------------------
687    
688            /**
689             * forget Transaction
690             */
691            private void doForget() throws RemoteException {
692                    if (TraceTm.jta.isDebugEnabled()) {
693                            TraceTm.jta.debug("SubCoordinator.doForget()");
694                    }
695    
696                    boolean exception = false;
697    
698                    for (int i = 0; i < resourceList.size(); i++) {
699                            // Tell this Resource to forget the transaction
700                            // Remove from the list ?
701                            XAResource xar = (XAResource) resourceList.elementAt(i);
702    
703                            javax.transaction.xa.Xid myjavaxxid = (javax.transaction.xa.Xid) javaxxidList.elementAt(i);
704    
705                            if (TraceTm.jta.isDebugEnabled()) {
706                                    TraceTm.jta.debug("myjavaxxid= " + myjavaxxid);
707                                    TraceTm.jta.debug("forgotten with resource= " + xar);
708                            }
709    
710                            try {
711                                    xar.forget(myjavaxxid);
712                            } catch (XAException e) {
713                                    String error = "Cannot send xar.forget:" + e + " (error code = " + e.errorCode + ") --"
714                                                    + e.getMessage();
715                                    TraceTm.jotm.error("Got XAException from xar.forget: " + error);
716                                    exception = true;
717                            }
718                    }
719    
720                    if (exception) {
721                            throw new RemoteException("XAException on forget");
722                    }
723    
724                    unexportObject(this);
725            }
726    
727            /**
728             * 2PC phase 1 (prepare) Basically, send prepare on each XAResource and collect the results.
729             */
730            private synchronized int doPrepare() throws RemoteException {
731                    if (TraceTm.jta.isDebugEnabled()) {
732                            TraceTm.jta.debug("SubCoordinator.doPrepare()");
733                    }
734    
735                    int ret = VOTE_READONLY;
736                    int errors = 0;
737    
738                    // No resource
739    
740                    if (resourceList.size() == 0) {
741                            // increment counter for management
742                            Current.getCurrent().incrementCommitCounter();
743                            status = Status.STATUS_COMMITTED;
744                            return ret;
745                    }
746    
747                    // Creates a log for that transaction, where we will add all the
748                    // resources that replied VOTE_COMMIT to prepare.
749                    // Do not flush the log on disk before decision to commit.
750    
751                    log = new SLog();
752    
753                    // Sends prepare to each resource while no error
754                    // In case of prepare on sub-coord. we may have only 1 resource.
755    
756                    status = Status.STATUS_PREPARING;
757    
758                    for (int i = 0; i < resourceList.size(); i++) {
759                            XAResource res = (XAResource) resourceList.elementAt(i);
760                            javax.transaction.xa.Xid myjavaxxid = (javax.transaction.xa.Xid) javaxxidList.elementAt(i);
761    
762                            Xid xid = new XidImpl(this.xid, i);
763    
764                            if (errors > 0) {
765    
766                                    if (TraceTm.jta.isDebugEnabled()) {
767                                            TraceTm.jta.debug("xid=        " + xid);
768                                            TraceTm.jta.debug("myjavaxxid= " + myjavaxxid);
769                                            TraceTm.jta.debug("rolled back with resource= " + res);
770                                    }
771    
772                                    try {
773                                            res.rollback(myjavaxxid);
774                                            // res.rollback(xid);
775                                    } catch (XAException e) {
776                                            String error = "Cannot send res.rollback:" + e + " (error code = " + e.errorCode + ") --"
777                                                            + e.getMessage();
778                                            TraceTm.jotm.error("Got XAException from res.rollback: " + error);
779                                            // Nothing to do ?
780                                    }
781                            } else {
782                                    if (TraceTm.jta.isDebugEnabled()) {
783                                            TraceTm.jta.debug("xid=        " + xid);
784                                            TraceTm.jta.debug("myjavaxxid= " + myjavaxxid);
785                                            TraceTm.jta.debug("prepared with resource= " + res);
786                                    }
787    
788                                    try {
789                                            switch (res.prepare(myjavaxxid)) {
790                                            // switch (res.prepare(xid)) {
791                                            case XAResource.XA_OK:
792                                                    log.addResource(res, xid, myjavaxxid);
793                                                    ret = VOTE_COMMIT;
794                                                    if (TraceTm.jta.isDebugEnabled()) {
795                                                            TraceTm.jta.debug("Prepare= XA_OK");
796                                                    }
797                                                    break;
798                                            case XAResource.XA_RDONLY:
799                                                    if (TraceTm.jta.isDebugEnabled()) {
800                                                            TraceTm.jta.debug("Prepare= XA_RDONLY");
801                                                    }
802                                                    break;
803                                            }
804                                    } catch (XAException e) {
805                                            String error = "Cannot send res.prepare:" + e + " (error code = " + e.errorCode + ") --"
806                                                            + e.getMessage();
807                                            TraceTm.jotm.error("Got XAException from res.prepare: " + error);
808                                            ret = VOTE_ROLLBACK;
809                                            errors++;
810                                    }
811                            }
812                    }
813    
814                    // Update the status, depending on vote result
815                    // If all resources returned READ_ONLY, we can forget the transaction
816    
817                    switch (ret) {
818                    case VOTE_READONLY:
819                            // increment counter for management
820                            Current.getCurrent().incrementCommitCounter();
821                            status = Status.STATUS_COMMITTED;
822                            if (TraceTm.jta.isDebugEnabled()) {
823                                    TraceTm.jta.debug("VOTE_READONLY");
824                            }
825                            break;
826                    case VOTE_COMMIT:
827                            status = Status.STATUS_PREPARED;
828                            if (TraceTm.jta.isDebugEnabled()) {
829                                    TraceTm.jta.debug("VOTE_COMMIT");
830                            }
831                            break;
832                    case VOTE_ROLLBACK:
833                            status = Status.STATUS_ROLLING_BACK;
834                            if (TraceTm.jta.isDebugEnabled()) {
835                                    TraceTm.jta.debug("VOTE_ROLLBACK");
836                            }
837                            break;
838                    }
839    
840                    // return the global vote
841                    return ret;
842            }
843    
844            /**
845             * 2PC - phase 2 (commit) See JTM for heuristics management
846             */
847            private synchronized int doCommit() throws RemoteException {
848                    if (TraceTm.jta.isDebugEnabled()) {
849                            TraceTm.jta.debug("SubCoordinator.doCommit()");
850                    }
851    
852                    // We build the Recovery Record in doCommit just in case of a system crash
853                    // Store the Recovery Record using HOWL so it can manage for us.
854                    //
855                    // The Recovery Record consists of two record types:
856                    // 1. XA Transaction recovery record
857                    // 2. XA Resource recovery record
858                    //
859                    // The XA Transaction recovery record format:
860                    // recovery record type1 (byte[3]) - 'RR1'
861                    // recovery record stored date-time (long) - 8 bytes
862                    // format id (int) - 4 bytes
863                    // length of transaction's gtrid (int) - 4 bytes
864                    // global transaction id (byte []) - txxidgti.length bytes
865                    // length of transaction's bqual (int) - 4 bytes
866                    // bqual (byte []) - txxidbq.length bytes
867                    // length of transactions store date-time (int) - 4 bytes
868                    // transactions created date-time (byte []) - Date.length bytes
869                    // count of XA resources assigned to the transaction (int) - 4 bytes
870                    //
871                    // The XA Resource recovery record format:
872                    // recovery record type2 (byte[3]) = 'RR2'
873                    // resource index (int)
874                    // length of XA resource (int) - 4 bytes
875                    // XA resource (byte []) - xares.length bytes
876                    // length of XA resource class name (int) - 4 bytes
877                    // XA resource class name (byte []) - resclassname.length bytes
878                    // XID.formatid (int) - 4 bytes
879                    // length of XID.gtrid assigned to XA resource (int) - 4 bytes
880                    // XID.gtrid assigned to XA resource (byte []) - xidgti.length bytes
881                    // length of XID.bqual assigned to XA resource (int) - 4 bytes
882                    // XID.bqual assigned to XA resource (byte []) - xidbq.length bytes
883                    // XID status-state (int) = 4 bytes
884                    //
885                    // The JOTM Done recovery record format:
886                    // recovery record type3 (byte[3]) = 'RR3'
887                    // JOTM done value (byte[8]) = 'JOTMDONE'
888    
889                    // First check that a log is initialized
890    
891                    if (log == null) {
892                            TraceTm.jotm.error("doCommit: no log");
893                            return -1;
894                    }
895    
896                    boolean successfulcommit = true;
897                    int commitnb = 0;
898                    int heuristicnb = 0;
899    
900                    List loggedResources = log.getLoggedResources();
901                    List byteloggedResources = log.getByteLoggedResources();
902                    List loggedXids = log.getLoggedXids();
903                    List loggedJavaxXids = log.getLoggedJavaxXids();
904    
905                    XACommittingTx xaCommitTx = null;
906                    XACommittingTx xaCommitTxRewrite = null;
907    
908                    byte[][] recoveryBuffer = new byte[loggedResources.size() + 1][]; // loggedResources + 1 (recoveryRecord1)
909    
910                    byte[] recoveryRecord1 = null;
911                    byte[] recoveryRecord2 = null;
912                    ByteBuffer rr1 = null;
913                    ByteBuffer rr2 = null;
914    
915                    byte[][] jotmDoneRecord = new byte[1][11];
916    
917                    if (Current.getDefaultRecovery()) {
918                            Xid txxid = tx.getXid();
919    
920                            int txxidfi = txxid.getFormatId();
921                            byte[] txxidgti = txxid.getGlobalTransactionId();
922                            byte[] txxidbq = txxid.getBranchQualifier();
923                            int txxidlength = 4 + txxidgti.length + txxidbq.length;
924    
925                            // formatid(int) + gridlen(int) + grid + bqlen(int) + bqual
926                            int gridlen = txxidgti.length;
927                            int bqlen = txxidbq.length;
928    
929                            String txdate = tx.getTxDate().toString();
930                            int txdatelength = txdate.length();
931                            long rcdate = System.currentTimeMillis();
932    
933                            recoveryRecord1 = new byte[3 + 8 + 4 + 4 + gridlen + 4 + bqlen + 4 + txdatelength + 4];
934    
935                            rr1 = ByteBuffer.wrap(recoveryRecord1);
936                            rr1.put(RT1);
937                            rr1.putLong(rcdate);
938                            rr1.putInt(txxidfi);
939                            rr1.putInt(txxidgti.length);
940                            rr1.put(txxidgti);
941                            rr1.putInt(txxidbq.length);
942                            rr1.put(txxidbq);
943                            rr1.putInt(txdatelength);
944                            rr1.put(txdate.getBytes());
945                            rr1.putInt(loggedResources.size());
946    
947                            recoveryBuffer[0] = rr1.array();
948    
949                            for (int i = 0; i < loggedResources.size(); i++) {
950                                    XAResource res = (XAResource) loggedResources.get(i);
951                                    byte[] resname = (byte[]) byteloggedResources.get(i);
952    
953                                    Xid xid = (Xid) loggedXids.get(i);
954                                    int rmindex = 99; // Store 99, at recovery we will store the correct index
955    
956                                    if (TraceTm.recovery.isDebugEnabled()) {
957                                            TraceTm.recovery.debug("recovery xid= " + xid);
958                                            TraceTm.recovery.debug("recovery resource= " + res);
959                                    }
960    
961                                    int resnamelength = resname.length;
962                                    String resclassname = res.getClass().getName();
963                                    int resclassnamelength = resclassname.length();
964    
965                                    int xidfi = xid.getFormatId();
966                                    byte[] xidgti = xid.getGlobalTransactionId();
967                                    byte[] xidbq = xid.getBranchQualifier();
968                                    int rr2gtilen = xidgti.length;
969                                    int rr2bqlen = xidbq.length;
970    
971                                    // formatid + : + gridlen + : + bqlen + : + xidgti + : + xidbq
972    
973                                    recoveryRecord2 = new byte[3 + 4 + 4 + resnamelength + 4 + resclassnamelength + 4 + 4 + rr2gtilen + 4
974                                                    + rr2bqlen + 4];
975    
976                                    rr2 = ByteBuffer.wrap(recoveryRecord2);
977                                    rr2.put(RT2);
978                                    rr2.putInt(rmindex);
979                                    rr2.putInt(resnamelength);
980                                    rr2.put(resname);
981                                    rr2.putInt(resclassnamelength);
982                                    rr2.put(resclassname.getBytes());
983                                    rr2.putInt(xidfi);
984                                    rr2.putInt(xidgti.length);
985                                    rr2.put(xidgti);
986                                    rr2.putInt(xidbq.length);
987                                    rr2.put(xidbq);
988                                    rr2.putInt(status);
989    
990                                    if (TraceTm.recovery.isDebugEnabled()) {
991                                            TraceTm.recovery.debug("Prepare Init RR2 to Recovery Buffer");
992                                    }
993                                    recoveryBuffer[i + 1] = rr2.array(); // First record (0) is always rr1
994                            }
995    
996                            try {
997                                    xaCommitTx = TransactionRecoveryImpl.getTransactionRecovery().howlCommitLog(recoveryBuffer);
998                            } catch (Exception e) {
999                                    // If we cannot write the Log, we cannot perform recovery, rollback transaction
1000                                    status = Status.STATUS_ROLLEDBACK;
1001    
1002                                    String howlerror = "Cannot howlCommitLog:" + e + " --" + e.getMessage();
1003                                    TraceTm.jotm.error("Got LogException from howlCommitLog: " + howlerror);
1004                                    xaCommitTx = null;
1005                                    doAfterCompletion();
1006                                    log.forgetLog();
1007    
1008                                    // KULRICE-9919 : Updated to include the rollback cause
1009                                    throw new JotmTransactionRolledbackException(e);
1010                                    // END KULRICE-9919
1011                            }
1012                    }
1013    
1014                    // Status Transaction = committing
1015    
1016                    status = Status.STATUS_COMMITTING;
1017                    if (TraceTm.recovery.isDebugEnabled()) {
1018                            TraceTm.recovery.debug("Status Committing");
1019                    }
1020    
1021                    // Send commit to each resource prepared
1022    
1023                    for (int i = 0; i < loggedResources.size(); i++) {
1024                            XAResource res = (XAResource) loggedResources.get(i);
1025    
1026                            // javax.transaction.xa.Xid myjavaxxid = (javax.transaction.xa.Xid) javaxxidList.elementAt(i);
1027                            javax.transaction.xa.Xid myjavaxxid = (javax.transaction.xa.Xid) loggedJavaxXids.get(i);
1028                            Xid xid = (Xid) loggedXids.get(i);
1029    
1030                            // Commit every resource that have been logged even if any of
1031                            // the commit resources fail. During recovery, the administrator
1032                            // will resolve any incomplete transactions.
1033    
1034                            if (TraceTm.jta.isDebugEnabled()) {
1035                                    TraceTm.jta.debug("xid=        " + xid);
1036                                    TraceTm.jta.debug("myjavaxxid= " + myjavaxxid);
1037                                    TraceTm.jta.debug("attempting commit with resource= " + res);
1038                            }
1039    
1040                            if (Current.getDefaultRecovery()) {
1041                                    int rmindex = 99; // Store 99, at recovery we will store the correct index
1042                                    byte[] resname = (byte[]) byteloggedResources.get(i);
1043                                    int resnamelength = resname.length;
1044    
1045                                    String resclassname = res.getClass().getName();
1046                                    int resclassnamelength = resclassname.length();
1047                                    int xidfi = xid.getFormatId();
1048                                    byte[] xidgti = xid.getGlobalTransactionId();
1049                                    byte[] xidbq = xid.getBranchQualifier();
1050                                    int rr2gtilen = xidgti.length;
1051                                    int rr2bqlen = xidbq.length;
1052    
1053                                    // formatid + : + gridlen + : + bqlen + : + xidgti + : + xidbq
1054    
1055                                    recoveryRecord2 = new byte[3 + 4 + 4 + resnamelength + 4 + resclassnamelength + 4 + 4 + rr2gtilen + 4
1056                                                    + rr2bqlen + 4];
1057    
1058                                    rr2 = ByteBuffer.wrap(recoveryRecord2);
1059    
1060                                    rr2.put(RT2);
1061                                    rr2.putInt(rmindex);
1062                                    rr2.putInt(resnamelength);
1063                                    rr2.put(resname);
1064                                    rr2.putInt(resclassnamelength);
1065                                    rr2.put(resclassname.getBytes());
1066                                    rr2.putInt(xidfi);
1067                                    rr2.putInt(xidgti.length);
1068                                    rr2.put(xidgti);
1069                                    rr2.putInt(xidbq.length);
1070                                    rr2.put(xidbq);
1071                            }
1072    
1073                            // commit resource
1074    
1075                            try {
1076                                    res.commit(myjavaxxid, false);
1077                                    // res.commit(xid, false);
1078    
1079                                    if (Current.getDefaultRecovery()) {
1080                                            rr2.putInt(Status.STATUS_COMMITTED);
1081                                            if (TraceTm.recovery.isDebugEnabled()) {
1082                                                    TraceTm.recovery.debug("Status Committed");
1083                                            }
1084                                    }
1085    
1086                                    commitnb++; // an XAresource was committed
1087                            } catch (XAException e) {
1088                                    switch (e.errorCode) {
1089                                    case XAException.XA_HEURHAZ:
1090                                    case XAException.XA_HEURCOM:
1091                                    case XAException.XA_HEURRB:
1092                                    case XAException.XA_HEURMIX:
1093                                            if (TraceTm.jta.isDebugEnabled()) {
1094                                                    TraceTm.jta.debug("Heuristic condition= " + e.getMessage());
1095                                            }
1096    
1097                                            if (Current.getDefaultRecovery()) {
1098                                                    rr2.putInt(Status.STATUS_UNKNOWN);
1099                                                    if (TraceTm.recovery.isDebugEnabled()) {
1100                                                            TraceTm.recovery.debug("Status Unknown");
1101                                                    }
1102                                            }
1103                                            break;
1104                                    case XAException.XAER_RMERR:
1105                                    case XAException.XAER_NOTA:
1106                                    case XAException.XAER_INVAL:
1107                                    case XAException.XAER_PROTO:
1108                                    case XAException.XAER_RMFAIL:
1109                                            if (TraceTm.jta.isDebugEnabled()) {
1110                                                    TraceTm.jta.debug("RM error= " + e.getMessage());
1111                                            }
1112    
1113                                            if (Current.getDefaultRecovery()) {
1114                                                    rr2.putInt(Status.STATUS_COMMITTING);
1115                                                    if (TraceTm.recovery.isDebugEnabled()) {
1116                                                            TraceTm.recovery.debug("Status Committing");
1117                                                    }
1118                                            }
1119                                            break;
1120                                    default:
1121                                            if (TraceTm.jta.isDebugEnabled()) {
1122                                                    TraceTm.jta.debug("Default error= " + e.getMessage());
1123                                            }
1124    
1125                                            if (Current.getDefaultRecovery()) {
1126                                                    rr2.putInt(Status.STATUS_ROLLEDBACK);
1127                                                    if (TraceTm.recovery.isDebugEnabled()) {
1128                                                            TraceTm.recovery.debug("Status Rolledback");
1129                                                    }
1130                                            }
1131                                    }
1132    
1133                                    String error = "Cannot send res.commit:" + e + " (error code = " + e.errorCode + ") --"
1134                                                    + e.getMessage();
1135                                    TraceTm.jotm.error("Got XAException from res.commit: " + error);
1136    
1137                                    successfulcommit = false;
1138                                    if (commitnb > 0) {
1139                                            heuristicnb++;
1140                                    }
1141                            }
1142    
1143                            if (Current.getDefaultRecovery()) {
1144                                    if (TraceTm.recovery.isDebugEnabled()) {
1145                                            TraceTm.recovery.debug("Prepare New RR2 to Recovery Buffer");
1146                                    }
1147                                    recoveryBuffer[i + 1] = rr2.array(); // First record (0) is always rr1
1148                            }
1149                    }
1150    
1151                    if (successfulcommit) {
1152                            // increment counter for management
1153                            Current.getCurrent().incrementCommitCounter();
1154                            // Everything's fine.
1155                            status = Status.STATUS_COMMITTED;
1156    
1157                            if (TraceTm.recovery.isDebugEnabled()) {
1158                                    TraceTm.recovery.debug("Status Committed");
1159                            }
1160    
1161                            if (Current.getDefaultRecovery()) {
1162                                    try {
1163                                            if (TraceTm.recovery.isDebugEnabled()) {
1164                                                    TraceTm.recovery.debug("Done howl log, all okay");
1165                                            }
1166    
1167                                            jotmDoneRecord[0] = JOTMDONE;
1168                                            TransactionRecoveryImpl.getTransactionRecovery().howlDoneLog(jotmDoneRecord, xaCommitTx);
1169                                    } catch (Exception f) {
1170                                            String howlerror = "Cannot howlDoneLog:" + f + "--" + f.getMessage();
1171                                            TraceTm.jotm.error("Got LogException from howlDoneLog: " + howlerror);
1172                                    }
1173                            }
1174    
1175                            doAfterCompletion();
1176                            log.forgetLog();
1177    
1178                            if (TraceTm.jta.isDebugEnabled()) {
1179                                    TraceTm.jta.debug("SubCoordinator.doCommit(): EXIT 0");
1180                            }
1181    
1182                            return 0;
1183                    }
1184    
1185                    /*****
1186                     * if (heuristicnb == 0) { // commits on all XAResources failed, just rollback // Transaction has been
1187                     * eventually rolled back status = Status.STATUS_ROLLEDBACK;
1188                     * 
1189                     * if (Current.getDefaultRecovery()) { try { jotmDoneRecord [0] = JOTMDONE;
1190                     * TransactionRecoveryImpl.getTransactionRecovery().howlDoneLog(jotmDoneRecord, xaCommitTx); } catch (Exception
1191                     * f) { String howlerror = "Cannot howlDoneLog" + f + "--" + f.getMessage();
1192                     * TraceTm.jotm.error("Got LogException from howlDoneLog: "+ howlerror); } }
1193                     * 
1194                     * doAfterCompletion(); log.forgetLog();
1195                     * 
1196                     * throw new TransactionRolledbackException(); }
1197                     *****/
1198    
1199                    // Log heuristics if errors
1200    
1201                    // if (heuristicnb != 0) { // some XAResource commits succeeded, others failed, heuristicmixed
1202    
1203                    if (Current.getDefaultRecovery()) {
1204                            try {
1205                                    if (TraceTm.recovery.isDebugEnabled()) {
1206                                            TraceTm.recovery.debug("Rewrite HowlCommitLog");
1207                                    }
1208                                    xaCommitTxRewrite = TransactionRecoveryImpl.getTransactionRecovery().howlCommitLog(recoveryBuffer);
1209                            } catch (Exception e) {
1210                                    // If we cannot write the Log, we cannot perform recovery, rollback transaction
1211                                    status = Status.STATUS_UNKNOWN;
1212                                    if (TraceTm.recovery.isDebugEnabled()) {
1213                                            TraceTm.recovery.debug("Status Unknown");
1214                                    }
1215    
1216                                    String howlerror = "Cannot howlCommitLog:" + e + " --" + e.getMessage();
1217                                    TraceTm.jotm.error("Got LogException from howlCommitLog: " + howlerror);
1218                                    xaCommitTx = null;
1219    
1220                                    doAfterCompletion();
1221                                    log.forgetLog();
1222    
1223                                    throw new JotmTransactionRolledbackException(e);
1224                            }
1225    
1226                            // Transaction state is unknown, now job for administrator
1227                            // status = Status.STATUS_UNKNOWN;
1228    
1229                            try {
1230                                    jotmDoneRecord[0] = JOTMDONE;
1231                                    TransactionRecoveryImpl.getTransactionRecovery().howlDoneLog(jotmDoneRecord, xaCommitTx);
1232                            } catch (Exception f) {
1233                                    String howlerror = "Cannot howlDoneLog" + f + "--" + f.getMessage();
1234                                    TraceTm.jotm.error("Got LogException from howlDoneLog: " + howlerror);
1235                            }
1236                    }
1237                    // }
1238    
1239                    status = Status.STATUS_UNKNOWN;
1240    
1241                    if (TraceTm.recovery.isDebugEnabled()) {
1242                            TraceTm.recovery.debug("Status Unknown");
1243                    }
1244    
1245                    doAfterCompletion();
1246    
1247                    if (TraceTm.jta.isDebugEnabled()) {
1248                            TraceTm.jta.debug("SubCoordinator.doCommit(): Exit -1");
1249                    }
1250    
1251                    return -1;
1252            }
1253    
1254            /**
1255             * 1PC (commit one phase)
1256             */
1257            private synchronized void doOnePhaseCommit() throws RemoteException {
1258                    if (TraceTm.jta.isDebugEnabled()) {
1259                            TraceTm.jta.debug("SubCoordinator.doOnePhaseCommit()");
1260                    }
1261    
1262                    // Only 1 resource: commit with onePhase=true
1263    
1264                    status = Status.STATUS_COMMITTING;
1265    
1266                    XAResource res = (XAResource) resourceList.elementAt(0);
1267                    javax.transaction.xa.Xid myjavaxxid = (javax.transaction.xa.Xid) javaxxidList.elementAt(0);
1268    
1269                    if (TraceTm.jta.isDebugEnabled()) {
1270                            TraceTm.jta.debug("myjavaxxid= " + myjavaxxid);
1271                            TraceTm.jta.debug("one phase commit with resource= " + res);
1272                    }
1273    
1274                    try {
1275                            res.commit(myjavaxxid, true);
1276    
1277                            // increment counter for management
1278                            Current.getCurrent().incrementCommitCounter();
1279                            status = Status.STATUS_COMMITTED;
1280                    } catch (XAException e) {
1281                            status = Status.STATUS_UNKNOWN;
1282                            String error = "Cannot send res.commit:" + e + " (error code = " + e.errorCode + ") --" + e.getMessage();
1283                            TraceTm.jotm.error("Got XAException from res.commit: " + error);
1284    
1285                            if (e.errorCode == XAException.XA_RBROLLBACK) {
1286                                    // KULRICE-9919 : Updated to include the rollback cause
1287                                    throw new JotmTransactionRolledbackException("XAException:" + error, e);
1288                                    // END KULRICE-9919
1289                            }
1290                            throw new RemoteException("XAException:" + error);
1291                    } finally {
1292                            doAfterCompletion();
1293                    }
1294            }
1295    
1296            /**
1297             * Rollback every resource involved
1298             */
1299    
1300            private synchronized void doRollback() throws RemoteException {
1301                    if (TraceTm.jta.isDebugEnabled()) {
1302                            TraceTm.jta.debug("SubCoordinator.doRollback()");
1303                    }
1304    
1305                    status = Status.STATUS_ROLLEDBACK;
1306                    boolean heurroll = false;
1307                    String heuristic = null;
1308    
1309                    // roll back each resource
1310                    for (int i = 0; i < resourceList.size(); i++) {
1311    
1312                            XAResource res = (XAResource) resourceList.elementAt(i);
1313                            javax.transaction.xa.Xid myjavaxxid = (javax.transaction.xa.Xid) javaxxidList.elementAt(i);
1314    
1315                            if (TraceTm.jta.isDebugEnabled()) {
1316                                    TraceTm.jta.debug("myjavaxxid= " + myjavaxxid);
1317                                    TraceTm.jta.debug("rolled back with resource= " + res);
1318                            }
1319    
1320                            // Rollback every resource that have been logged even if any of
1321                            // the rollback resources fail. During recovery, the administrator
1322                            // will resolve any incomplete transactions.
1323    
1324                            try {
1325                                    res.rollback(myjavaxxid);
1326                            } catch (XAException e) {
1327                                    switch (e.errorCode) {
1328                                    case XAException.XA_HEURHAZ:
1329                                    case XAException.XA_HEURCOM:
1330                                    case XAException.XA_HEURRB:
1331                                    case XAException.XA_HEURMIX:
1332                                            heuristic = "Heuristic condition= " + e.errorCode + "--" + e.getMessage();
1333                                            TraceTm.jta.warn(heuristic);
1334                                            heurroll = true;
1335                                            break;
1336                                    case XAException.XAER_RMERR:
1337                                    case XAException.XAER_NOTA:
1338                                    case XAException.XAER_INVAL:
1339                                    case XAException.XAER_PROTO:
1340                                    case XAException.XAER_RMFAIL:
1341                                            if (TraceTm.jta.isDebugEnabled()) {
1342                                                    TraceTm.jta.debug("RM error= " + e.errorCode + "--" + e.getMessage());
1343                                            }
1344                                            break;
1345                                    default:
1346                                            if (TraceTm.jta.isDebugEnabled()) {
1347                                                    TraceTm.jta.debug("Default error= " + e.errorCode + "--" + e.getMessage());
1348                                            }
1349                                    }
1350                            }
1351                    }
1352    
1353                    // raise Heuristic exception if XAResource returned heuristic
1354                    // no need to throw an exception for other XAResource errors,
1355                    // the resource (xid) has been rolled back anyway
1356    
1357                    if (heurroll) {
1358                            // pass the last heuristic error
1359                            throw new HeuristicRollback(heuristic);
1360                    }
1361    
1362                    // increment counter for management
1363    
1364                    Current.getCurrent().incrementRollbackCounter();
1365    
1366                    doAfterCompletion();
1367            }
1368    
1369            /**
1370             * before completion
1371             * 
1372             * @param boolean true if completion ok, false if rollback
1373             */
1374            private void doBeforeCompletion(boolean committing) {
1375                    if (TraceTm.jta.isDebugEnabled()) {
1376                            TraceTm.jta.debug("doBeforeCompletion committing= " + committing);
1377                    }
1378                    if (beforeCompletionDone) {
1379                            return;
1380                    }
1381    
1382                    // Unset the timer for this transaction, if any
1383                    tx.unsetTimer();
1384    
1385                    // For each synchro, send beforeCompletion (not if rollback)
1386    
1387                    if (committing && synchroList.size() > 0) {
1388                            // We must be in the correct transaction context
1389                            // See JTA spec. page 13 (3.3.2)
1390                            // because at beforeCompletion, the bean will write its cache
1391    
1392                            // Check the trivial case where we already have the correct tx context
1393    
1394                            Transaction mytx = null;
1395                            boolean suspended = false;
1396                            boolean resumed = false;
1397    
1398                            try {
1399                                    mytx = tm.getTransaction();
1400                            } catch (SystemException e) {
1401                                    if (TraceTm.jta.isDebugEnabled()) {
1402                                            String error = "Cannot get transaction:" + e + "--" + e.getMessage();
1403                                            TraceTm.jta.debug(error);
1404                                    }
1405                            }
1406    
1407                            // Suspend if another tx context
1408    
1409                            if (mytx != null && mytx.equals(tx) == false) {
1410                                    try {
1411                                            tm.suspend();
1412                                            suspended = true;
1413                                    } catch (SystemException e) {
1414                                            if (TraceTm.jta.isDebugEnabled()) {
1415                                                    String error = "Cannot suspend transaction:" + e + "--" + e.getMessage();
1416                                                    TraceTm.jta.debug(error);
1417                                            }
1418                                    }
1419                            }
1420    
1421                            // Resume the good tx context
1422    
1423                            if (mytx == null || suspended) {
1424                                    try {
1425                                            tm.resume(tx);
1426                                            resumed = true;
1427                                    } catch (SystemException e) {
1428                                            if (TraceTm.jta.isDebugEnabled()) {
1429                                                    String error = "Cannot resume transaction:" + e + "--" + e.getMessage();
1430                                                    TraceTm.jta.debug(error);
1431                                            }
1432                                    } catch (InvalidTransactionException e) {
1433                                            if (TraceTm.jta.isDebugEnabled()) {
1434                                                    String error = "Cannot resume transaction:" + e + "--" + e.getMessage();
1435                                                    TraceTm.jta.debug(error);
1436                                            }
1437                                    } catch (IllegalStateException e) {
1438                                            if (TraceTm.jta.isDebugEnabled()) {
1439                                                    String error = "Cannot resume transaction:" + e + "--" + e.getMessage();
1440                                                    TraceTm.jta.debug(error);
1441                                            }
1442                                    }
1443                            }
1444    
1445                            // Call the synchronizations
1446                            // beforeCompletion may set the TX rollbackonly if something goes wrong
1447    
1448                            if (TraceTm.jta.isDebugEnabled()) {
1449                                    TraceTm.jta.debug("sychronization list size= " + synchroList.size());
1450                            }
1451    
1452                            for (int i = 0; i < synchroList.size(); i++) {
1453                                    Synchronization sync = (Synchronization) synchroList.elementAt(i);
1454    
1455                                    if (TraceTm.jta.isDebugEnabled()) {
1456                                            TraceTm.jta.debug("Calling Synchro " + sync);
1457                                    }
1458    
1459                                    try {
1460                                            // This can register a new synchro, modifying synchroList!
1461                                            // TODO add a synchronized on list, and call beforeCompletion
1462                                            // outside the block.
1463                                            sync.beforeCompletion();
1464                                    } catch (RuntimeException e) {
1465                                            status = Status.STATUS_MARKED_ROLLBACK;
1466                                            rollbackCause = e;
1467                                            if (TraceTm.jta.isDebugEnabled()) {
1468                                                    String error = "Cannot sync.beforeCompletion:" + e + "--" + e.getMessage();
1469                                                    TraceTm.jta.debug(error);
1470                                            }
1471                                    }
1472                            }
1473    
1474                            // reset context as it was before
1475    
1476                            if (resumed) {
1477                                    try {
1478                                            tm.suspend();
1479                                    } catch (SystemException e) {
1480                                            if (TraceTm.jta.isDebugEnabled()) {
1481                                                    String error = "Cannot suspend transaction:" + e + "--" + e.getMessage();
1482                                                    TraceTm.jta.debug(error);
1483                                            }
1484                                    }
1485                            }
1486    
1487                            if (suspended) {
1488                                    try {
1489                                            tm.resume(mytx);
1490                                            resumed = true;
1491                                    } catch (SystemException e) {
1492                                            if (TraceTm.jta.isDebugEnabled()) {
1493                                                    String error = "Cannot resume transaction:" + e + "--" + e.getMessage();
1494                                                    TraceTm.jta.debug(error);
1495                                            }
1496                                    } catch (InvalidTransactionException e) {
1497                                            if (TraceTm.jta.isDebugEnabled()) {
1498                                                    String error = "Cannot resume transaction:" + e + "--" + e.getMessage();
1499                                                    TraceTm.jta.debug(error);
1500                                            }
1501                                    } catch (IllegalStateException e) {
1502                                            if (TraceTm.jta.isDebugEnabled()) {
1503                                                    String error = "Cannot resume transaction:" + e + "--" + e.getMessage();
1504                                                    TraceTm.jta.debug(error);
1505                                            }
1506                                    }
1507                            }
1508                    }
1509    
1510                    beforeCompletionDone = true;
1511            }
1512    
1513            /**
1514             * after completion
1515             */
1516            private void doAfterCompletion() {
1517                    if (TraceTm.jta.isDebugEnabled()) {
1518                            TraceTm.jta.debug("doAfterCompletion()");
1519                    }
1520    
1521                    // For each synchro, send afterCompletion
1522                    // / CompletedTransactionListener has been replaced by Synchronization
1523    
1524                    if (TraceTm.jta.isDebugEnabled()) {
1525                            TraceTm.jta.debug("sychronization list size= " + synchroList.size());
1526                    }
1527    
1528                    for (int i = 0; i < synchroList.size(); i++) {
1529                            Synchronization sync = (Synchronization) synchroList.elementAt(i);
1530    
1531                            if (TraceTm.jta.isDebugEnabled()) {
1532                                    TraceTm.jta.debug("Synchronization sync= " + sync);
1533                                    TraceTm.jta.debug("sync.afterCompletion status= " + StatusHelper.getStatusName(status));
1534                            }
1535    
1536                            sync.afterCompletion(status);
1537                    }
1538    
1539                    // Forget this transaction.
1540                    // LATER:
1541                    // - Should not forget it in case of heuristics (for recovery)
1542                    // - May be this could be deferred in case of retry from a client: use a timer.
1543                    Current.getCurrent().forgetTx(tx.getXid());
1544            }
1545    
1546            /**
1547             * return index in resourceList of this XAResource
1548             * 
1549             * @param xares
1550             *            the XAResource
1551             * @return index
1552             */
1553            public int getXaresIndex(XAResource xares) {
1554    
1555                    if (TraceTm.jta.isDebugEnabled()) {
1556                            TraceTm.jta.debug("getXaresIndex xares= " + xares);
1557                            TraceTm.jta.debug("resourceList.size= " + resourceList.size());
1558                    }
1559    
1560                    int xaresIndex = -1;
1561    
1562                    // first, search for an XAResource with the same object reference
1563                    if (TraceTm.jta.isDebugEnabled()) {
1564                            TraceTm.jta.debug("search xares with same obj ref");
1565                    }
1566    
1567                    for (int i = 0; i < resourceList.size(); i++) {
1568                            XAResource res = (XAResource) resourceList.elementAt(i);
1569    
1570                            if (TraceTm.jta.isDebugEnabled()) {
1571                                    TraceTm.jta.debug("res= " + res);
1572                            }
1573    
1574                            if (res.equals(xares)) {
1575                                    xaresIndex = i;
1576                                    break;
1577                            }
1578                    }
1579    
1580                    // if not found, search for a xares with the same RM
1581                    if (xaresIndex == -1) {
1582                            if (TraceTm.jta.isDebugEnabled()) {
1583                                    TraceTm.jta.debug("not found -> search for xares with same RM");
1584                            }
1585    
1586                            for (int i = 0; i < resourceList.size(); i++) {
1587                                    XAResource res = (XAResource) resourceList.elementAt(i);
1588    
1589                                    if (TraceTm.jta.isDebugEnabled()) {
1590                                            TraceTm.jta.debug("res= " + res);
1591                                    }
1592    
1593                                    try {
1594                                            if (res.isSameRM(xares)) {
1595                                                    xaresIndex = i;
1596                                                    break;
1597                                            }
1598                                    } catch (XAException e) {
1599                                            if (TraceTm.jta.isDebugEnabled()) {
1600                                                    String error = "res.isSameRm exception:" + e + "--" + e.getMessage();
1601                                                    TraceTm.jta.debug(error);
1602                                            }
1603                                    }
1604                            }
1605                    }
1606    
1607                    if (TraceTm.jta.isDebugEnabled()) {
1608                            TraceTm.jta.debug("xaresIndex= " + xaresIndex);
1609                    }
1610    
1611                    return xaresIndex;
1612            }
1613    
1614    }