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 }