View Javadoc

1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.objectweb.jotm;
17  
18  import java.rmi.NoSuchObjectException;
19  import java.rmi.RemoteException;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.Date;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  import javax.rmi.PortableRemoteObject;
28  import javax.transaction.HeuristicMixedException;
29  import javax.transaction.HeuristicRollbackException;
30  import javax.transaction.RollbackException;
31  import javax.transaction.Status;
32  import javax.transaction.Synchronization;
33  import javax.transaction.SystemException;
34  import javax.transaction.Transaction;
35  import javax.transaction.TransactionRolledbackException;
36  import javax.transaction.xa.XAException;
37  import javax.transaction.xa.XAResource;
38  
39  /**
40   * TransactionImpl is the implementation of the Transaction interface, defined in JTA specifications. This object is
41   * intended to be used by the EJBServer for transaction management. It is used indirectly by the UserTransaction
42   * implementation too, i.e. the Current object. The goal is to use the JTA interface to hide the JTM interface to the
43   * caller (EJBServer, Bean or Client).
44   */
45  
46  public class TransactionImpl implements Transaction, TimerEventListener {
47  
48  	// ------------------------------------------------------------------
49  	// Private data
50  	// ------------------------------------------------------------------
51  	private SubCoordinator subcoord = null;
52  	private TransactionContext myCtx = null;
53  	private Xid myXid = null;
54  	private boolean genXidhashcode = false;
55  	private boolean genXidtostring = false;
56  	private int myXidhashcode = 0;
57  	private String myXidtostring = null;
58  	private Date txDate = null;
59  	private TimerEvent timer = null; // keep this to unvalidate timer
60  	private RecoveryCoordinator recoveryCoord = null;
61  	// / store enlisted resources
62  	private List<XAResource> enlistedXARes = Collections.synchronizedList(new ArrayList<XAResource>());
63  	// / store suspended resources
64  	private List<XAResource> delistedXARes = null;
65  
66  	/**
67  	 * propagate context or not. No need to propagate Context when accessing TM for example TODO Add a synchro on this
68  	 * object.
69  	 */
70  	private boolean propagateCtx = true;
71  	private List<javax.transaction.xa.Xid> enlistedJavaxXid = Collections
72  			.synchronizedList(new ArrayList<javax.transaction.xa.Xid>());
73  	private Map<Object, Object> userResourceMap = null;
74  
75  	/**
76  	 * Actual Status is kept inside SubCoordinator. This one is used only when SubCoordinator does not exist.
77  	 */
78  	private int localstatus = Status.STATUS_ACTIVE;
79  
80  	private boolean toremove = false;
81  
82  	// ------------------------------------------------------------------
83  	// Constructors
84  	// ------------------------------------------------------------------
85  
86  	/**
87  	 * New transaction (begin).
88  	 * 
89  	 * @param xid
90  	 *            transaction Xid
91  	 * @param timeout
92  	 *            The value of the timeout in seconds.
93  	 * @throws SystemException
94  	 *             could not build Transaction Context
95  	 */
96  	public TransactionImpl(Xid xid, int timeout) throws SystemException {
97  		if (TraceTm.jta.isDebugEnabled()) {
98  			TraceTm.jta.debug("xid= " + xid);
99  			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 }