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