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 }