1 /*
2 * JBoss, the OpenSource J2EE webOS
3 *
4 * Distributable under LGPL license.
5 * See terms of license at gnu.org.
6 */
7 package org.jboss.tm;
8
9 import java.util.Collections;
10 import java.util.HashMap;
11 import java.util.Map;
12
13 import javax.transaction.Status;
14 import javax.transaction.TransactionManager;
15 import javax.transaction.Transaction;
16 import javax.transaction.NotSupportedException;
17 import javax.transaction.SystemException;
18 import javax.transaction.RollbackException;
19 import javax.transaction.HeuristicMixedException;
20 import javax.transaction.HeuristicRollbackException;
21 import javax.transaction.InvalidTransactionException;
22
23 import org.jboss.logging.Logger;
24
25 /**
26 * Our TransactionManager implementation.
27 *
28 * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Öberg</a>
29 * @author <a href="mailto:marc.fleury@telkel.com">Marc Fleury</a>
30 * @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
31 * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
32 * @version $Revision: 1.3.2.9 $
33 */
34 public class TxManager
35 implements TransactionManager,
36 TransactionPropagationContextImporter,
37 TransactionPropagationContextFactory,
38 TransactionLocalDelegate
39 {
40 // Constants -----------------------------------------------------
41
42 // Attributes ----------------------------------------------------
43
44 /** Instance logger. */
45 private Logger log = Logger.getLogger(this.getClass());
46
47 /** True if trace messages should be logged. */
48 private boolean trace = log.isTraceEnabled();
49
50 /**
51 * Default timeout in milliseconds.
52 * Must be >= 1000!
53 */
54 private long timeOut = 5 * 60 * 1000;
55
56 // The following two fields are ints (not longs) because
57 // volatile 64Bit types are broken (i.e. access is not atomic) in most VMs, and we
58 // don't want to lock just for a statistic. Additionaly,
59 // it will take several years on a highly loaded system to
60 // exceed the int range. Note that we might loose an
61 // increment every now and then, since the ++ operation is
62 // not atomic on volatile data types.
63 /** A count of the transactions that have been committed */
64 private volatile int commitCount;
65 /** A count of the transactions that have been rolled back */
66 private volatile int rollbackCount;
67
68 // Static --------------------------------------------------------
69
70 /**
71 * The singleton instance.
72 */
73 private static TxManager singleton = new TxManager();
74
75 /**
76 * Get a reference to the singleton instance.
77 */
78 public static TxManager getInstance()
79 {
80 return singleton;
81 }
82
83 // Constructors --------------------------------------------------
84
85 /**
86 * Private constructor for singleton. Use getInstance() to obtain
87 * a reference to the singleton.
88 */
89 private TxManager()
90 {
91 //make sure TxCapsule can be used
92 TransactionImpl.defaultXidFactory();
93 }
94
95 // Public --------------------------------------------------------
96
97 /**
98 * Begin a new transaction.
99 * The new transaction will be associated with the calling thread.
100 */
101 public void begin()
102 throws NotSupportedException, SystemException
103 {
104 ThreadInfo ti = getThreadInfo();
105 TransactionImpl current = ti.tx;
106
107 if (current != null)
108 {
109 if (current.isDone())
110 disassociateThread(ti);
111 else
112 throw new NotSupportedException
113 ("Transaction already active, cannot nest transactions.");
114 }
115
116 long timeout = (ti.timeout == 0) ? timeOut : ti.timeout;
117 TransactionImpl tx = new TransactionImpl(timeout);
118 associateThread(ti, tx);
119 globalIdTx.put(tx.getGlobalId(), tx);
120
121 if (trace)
122 log.trace("began tx: " + tx);
123 }
124
125 /**
126 * Commit the transaction associated with the currently running thread.
127 */
128 public void commit()
129 throws RollbackException,
130 HeuristicMixedException,
131 HeuristicRollbackException,
132 SecurityException,
133 IllegalStateException,
134 SystemException
135 {
136 ThreadInfo ti = getThreadInfo();
137 TransactionImpl current = ti.tx;
138
139 if (current != null)
140 {
141 current.commit();
142 disassociateThread(ti);
143 if (trace)
144 log.trace("commited tx: " + current);
145 }
146 else
147 throw new IllegalStateException("No transaction.");
148 }
149
150 /**
151 * Return the status of the transaction associated with the currently
152 * running thread, or <code>Status.STATUS_NO_TRANSACTION</code> if no
153 * active transaction is currently associated.
154 */
155 public int getStatus() throws SystemException
156 {
157 ThreadInfo ti = getThreadInfo();
158 TransactionImpl current = ti.tx;
159
160 if (current != null)
161 {
162 if (current.isDone())
163 disassociateThread(ti);
164 else
165 return current.getStatus();
166 }
167 return Status.STATUS_NO_TRANSACTION;
168 }
169
170 /**
171 * Return the transaction currently associated with the invoking thread,
172 * or <code>null</code> if no active transaction is currently associated.
173 */
174 public Transaction getTransaction() throws SystemException
175 {
176 ThreadInfo ti = getThreadInfo();
177 TransactionImpl current = ti.tx;
178
179 if (current != null && current.isDone())
180 {
181 current = null;
182 disassociateThread(ti);
183 }
184
185 return current;
186 }
187
188 /**
189 * Resume a transaction.
190 *
191 * Note: This will not enlist any resources involved in this
192 * transaction. According to JTA1.0.1 specification section 3.2.3,
193 * that is the responsibility of the application server.
194 */
195 public void resume(Transaction transaction)
196 throws InvalidTransactionException,
197 IllegalStateException,
198 SystemException
199 {
200 if (transaction != null && !(transaction instanceof TransactionImpl))
201 throw new RuntimeException("Not a TransactionImpl, but a " +
202 transaction.getClass().getName());
203
204 ThreadInfo ti = getThreadInfo();
205 TransactionImpl current = ti.tx;
206
207 if (current != null)
208 {
209 if (current.isDone())
210 current = ti.tx = null;
211 else
212 throw new IllegalStateException("Already associated with a tx");
213 }
214
215 if (current != transaction)
216 {
217 associateThread(ti, (TransactionImpl)transaction);
218 }
219
220 if (trace)
221 log.trace("resumed tx: " + ti.tx);
222 }
223
224 /**
225 * Suspend the transaction currently associated with the current
226 * thread, and return it.
227 *
228 * Note: This will not delist any resources involved in this
229 * transaction. According to JTA1.0.1 specification section 3.2.3,
230 * that is the responsibility of the application server.
231 */
232 public Transaction suspend() throws SystemException
233 {
234 ThreadInfo ti = getThreadInfo();
235 TransactionImpl current = ti.tx;
236
237 if (current != null)
238 {
239 ti.tx = null;
240
241 if (trace)
242 log.trace("suspended tx: " + current);
243
244 if (current.isDone())
245 current = null;
246 }
247
248 return current;
249 }
250
251 /**
252 * Roll back the transaction associated with the currently running thread.
253 */
254 public void rollback()
255 throws IllegalStateException, SecurityException, SystemException
256 {
257 ThreadInfo ti = getThreadInfo();
258 TransactionImpl current = ti.tx;
259
260 if (current != null)
261 {
262 if (!current.isDone())
263 {
264 current.rollback();
265
266 if (trace)
267 log.trace("rolled back tx: " + current);
268 return;
269 }
270 disassociateThread(ti);
271 }
272 throw new IllegalStateException("No transaction.");
273 }
274
275 /**
276 * Mark the transaction associated with the currently running thread
277 * so that the only possible outcome is a rollback.
278 */
279 public void setRollbackOnly()
280 throws IllegalStateException, SystemException
281 {
282 ThreadInfo ti = getThreadInfo();
283 TransactionImpl current = ti.tx;
284
285 if (current != null)
286 {
287 if (!current.isDone())
288 {
289 current.setRollbackOnly();
290
291 if (trace)
292 log.trace("tx marked for rollback only: " + current);
293 return;
294 }
295 ti.tx = null;
296 }
297 throw new IllegalStateException("No transaction.");
298 }
299
300 /**
301 * Set the transaction timeout for new transactions started by the
302 * calling thread.
303 */
304 public void setTransactionTimeout(int seconds)
305 throws SystemException
306 {
307 getThreadInfo().timeout = 1000 * seconds;
308
309 if (trace)
310 log.trace("tx timeout is now: " + seconds + "s");
311 }
312
313 /**
314 * Set the default transaction timeout for new transactions.
315 * This default value is used if <code>setTransactionTimeout()</code>
316 * was never called, or if it was called with a value of <code>0</code>.
317 */
318 public void setDefaultTransactionTimeout(int seconds)
319 {
320 timeOut = 1000L * seconds;
321
322 if (trace)
323 log.trace("default tx timeout is now: " + seconds + "s");
324 }
325
326 /**
327 * Get the default transaction timeout.
328 *
329 * @return Default transaction timeout in seconds.
330 */
331 public int getDefaultTransactionTimeout()
332 {
333 return (int) (timeOut / 1000);
334 }
335
336 /**
337 * The following 2 methods are here to provide association and
338 * disassociation of the thread.
339 */
340 public Transaction disassociateThread()
341 {
342 return disassociateThread(getThreadInfo());
343 }
344
345 private Transaction disassociateThread(ThreadInfo ti) {
346 TransactionImpl current = ti.tx;
347 ti.tx=null;
348 current.disassociateCurrentThread();
349 return current;
350 }
351
352 public void associateThread(Transaction transaction)
353 {
354 if (transaction != null && !(transaction instanceof TransactionImpl))
355 throw new RuntimeException("Not a TransactionImpl, but a " +
356 transaction.getClass().getName());
357
358 // Associate with the thread
359 TransactionImpl transactionImpl = (TransactionImpl) transaction;
360 ThreadInfo ti = getThreadInfo();
361 ti.tx = transactionImpl;
362 transactionImpl.associateCurrentThread();
363 }
364
365 private void associateThread(ThreadInfo ti, TransactionImpl transaction)
366 {
367 // Associate with the thread
368 ti.tx = transaction;
369 transaction.associateCurrentThread();
370 }
371
372 /**
373 * Return the number of active transactions
374 */
375 public int getTransactionCount()
376 {
377 return globalIdTx.size();
378 }
379 /** A count of the transactions that have been committed */
380 public long getCommitCount()
381 {
382 return commitCount;
383 }
384 /** A count of the transactions that have been rolled back */
385 public long getRollbackCount()
386 {
387 return rollbackCount;
388 }
389
390 // Implements TransactionPropagationContextImporter ---------------
391
392 /**
393 * Import a transaction propagation context into this TM.
394 * The TPC is loosely typed, as we may (at a later time) want to
395 * import TPCs that come from other transaction domains without
396 * offloading the conversion to the client.
397 *
398 * @param tpc The transaction propagation context that we want to
399 * import into this TM. Currently this is an instance
400 * of GlobalId. At some later time this may be an instance
401 * of a transaction propagation context from another
402 * transaction domain like
403 * org.omg.CosTransactions.PropagationContext.
404 *
405 * @return A transaction representing this transaction propagation
406 * context, or null if this TPC cannot be imported.
407 */
408 public Transaction importTransactionPropagationContext(Object tpc)
409 {
410 if (tpc instanceof GlobalId)
411 {
412 GlobalId id = (GlobalId) tpc;
413 return (Transaction) globalIdTx.get(id);
414 }
415
416 log.warn("Cannot import transaction propagation context: " + tpc);
417 return null;
418 }
419
420 // Implements TransactionPropagationContextFactory ---------------
421
422 /**
423 * Return a TPC for the current transaction.
424 */
425 public Object getTransactionPropagationContext()
426 {
427 return getTransactionPropagationContext(getThreadInfo().tx);
428 }
429
430 /**
431 * Return a TPC for the argument transaction.
432 */
433 public Object getTransactionPropagationContext(Transaction tx)
434 {
435 // If no transaction or unknown transaction class, return null.
436 if (tx == null)
437 return null;
438 if (!(tx instanceof TransactionImpl))
439 {
440 log.warn("Cannot export transaction propagation context: " + tx);
441 return null;
442 }
443
444 return ((TransactionImpl) tx).getGlobalId();
445 }
446
447 // Implements TransactionLocalDelegate ----------------------
448
449 /**
450 * get the transaction local value. Pull it from the TransactionImpl object
451 */
452 public Object getValue(TransactionLocal local, Transaction tx)
453 {
454 TransactionImpl tximpl = (TransactionImpl) tx;
455 return tximpl.getTransactionLocalValue(local);
456 }
457
458 /**
459 * put the value in the TransactionImpl map
460 */
461 public void storeValue(TransactionLocal local, Transaction tx, Object value)
462 {
463 TransactionImpl tximpl = (TransactionImpl) tx;
464 tximpl.putTransactionLocalValue(local, value);
465 }
466
467 /**
468 * does TransactionImpl contain object?
469 */
470 public boolean containsValue(TransactionLocal local, Transaction tx)
471 {
472 TransactionImpl tximpl = (TransactionImpl) tx;
473 return tximpl.containsTransactionLocal(local);
474 }
475
476 // Package protected ---------------------------------------------
477
478 /**
479 * Release the given TransactionImpl.
480 */
481 void releaseTransactionImpl(TransactionImpl tx)
482 {
483 globalIdTx.remove(tx.getGlobalId());
484 }
485
486 /**
487 * Increment the commit count
488 */
489 void incCommitCount()
490 {
491 ++commitCount;
492 }
493
494 /**
495 * Increment the rollback count
496 */
497 void incRollbackCount()
498 {
499 ++rollbackCount;
500 }
501
502 // Protected -----------------------------------------------------
503
504 // Private -------------------------------------------------------
505
506 /**
507 * This keeps track of the thread association with transactions
508 * and timeout values.
509 * In some cases terminated transactions may not be cleared here.
510 */
511 private ThreadLocal threadTx = new ThreadLocal();
512
513 /**
514 * This map contains the active transactions as values.
515 * The keys are the <code>GlobalId</code>s of the transactions.
516 */
517 private Map globalIdTx = Collections.synchronizedMap(new HashMap());
518
519
520 /**
521 * Return the ThreadInfo for the calling thread, and create if not
522 * found.
523 */
524 private ThreadInfo getThreadInfo()
525 {
526 ThreadInfo ret = (ThreadInfo) threadTx.get();
527
528 if (ret == null)
529 {
530 ret = new ThreadInfo();
531 ret.timeout = timeOut;
532 threadTx.set(ret);
533 }
534
535 return ret;
536 }
537
538
539 // Inner classes -------------------------------------------------
540
541 /**
542 * A simple aggregate of a thread-associated timeout value
543 * and a thread-associated transaction.
544 */
545 static class ThreadInfo
546 {
547 long timeout;
548 TransactionImpl tx;
549 }
550 }