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