001: /*
002: * CoadunationLib: The coaduntion library.
003: * Copyright (C) 2006 Rift IT Contracting
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
018: *
019: * TransactionManager.java
020: */
021:
022: // package path
023: package com.rift.coad.util.transaction;
024:
025: // java imports
026: import java.util.Iterator;
027: import java.util.Map;
028: import java.util.LinkedHashMap;
029: import java.util.Vector;
030: import java.util.HashMap;
031: import java.util.concurrent.ConcurrentHashMap;
032: import javax.naming.Context;
033: import javax.naming.InitialContext;
034: import javax.transaction.xa.XAException;
035: import javax.transaction.xa.XAResource;
036: import javax.transaction.xa.Xid;
037:
038: // logging import
039: import org.apache.log4j.Logger;
040:
041: // coadunation imports
042: import com.rift.coad.lib.configuration.Configuration;
043: import com.rift.coad.lib.configuration.ConfigurationFactory;
044: import com.rift.coad.util.lock.LockRef;
045: import com.rift.coad.util.lock.ObjectLockFactory;
046:
047: /**
048: * This object is responsible for managing the transactions for a group of
049: * objects.
050: *
051: * @author Brett Chaldecott
052: */
053: public class TransactionManager implements XAResource {
054:
055: /**
056: * The transaction object, responsible for managing an individual
057: * transaction.
058: */
059: public class Transaction {
060:
061: // private member variables
062: private Xid xid = null;
063: private Map transactionEntries = new LinkedHashMap();
064: private Vector transactionQueue = new Vector();
065:
066: /**
067: * The constructor of the transaction object.
068: *
069: * @param xid The id of this transaction.
070: */
071: public Transaction(Xid xid) {
072: this .xid = xid;
073: }
074:
075: /**
076: * This method adds a resource to the transaction for tracking.
077: *
078: * @param xaResource The reference to the resource to add to this
079: * transaction for tracking.
080: * @param writeLock If true a write lock must be aquired
081: * @exception TransactionException
082: */
083: public void addResource(XAResource xaResource, boolean writeLock)
084: throws TransactionException {
085: try {
086: LockRef lockRef = (LockRef) transactionEntries
087: .get(xaResource);
088: int flags = XAResource.TMRESUME;
089: if (lockRef == null) {
090: if (writeLock) {
091: lockRef = ObjectLockFactory
092: .getInstance()
093: .acquireWriteLock(xaResource, xid,
094: ObjectLockFactory.WAIT_ON_NAMED);
095: } else {
096: lockRef = ObjectLockFactory.getInstance()
097: .acquireReadLock(xaResource, xid);
098: }
099: transactionEntries.put(xaResource, lockRef);
100: transactionQueue.add(xaResource);
101: flags = XAResource.TMJOIN;
102: } else if (writeLock
103: && (lockRef.getLockType() == LockRef.READ)) {
104: throw new TransactionException(
105: "Lock Conflict: There is a "
106: + "read lock in place on this object.");
107: } else if (!writeLock
108: && (lockRef.getLockType() == LockRef.WRITE)) {
109: throw new TransactionException(
110: "Lock Conflict: There is a "
111: + "write lock in place on this object.");
112: }
113: xaResource.start(xid, flags);
114: } catch (TransactionException ex) {
115: throw ex;
116: } catch (Exception ex) {
117: log.error("Failed to add a new resource : "
118: + ex.getMessage(), ex);
119: throw new TransactionException(
120: "Failed to add a new resource : "
121: + ex.getMessage(), ex);
122: }
123: }
124:
125: /**
126: * This method is responsible for commiting this transaction
127: *
128: * @param onePhase If true this transaction must commit in a single
129: * phase.
130: * @exception TransactionException
131: */
132: public void commit(boolean onePhase)
133: throws TransactionException {
134: try {
135: for (int index = 0; index < transactionQueue.size(); index++) {
136: Object ref = transactionQueue.get(index);
137: if (ref instanceof XAResource) {
138: ((XAResource) ref).commit(xid, onePhase);
139: }
140: }
141: for (int index = 0; index < transactionQueue.size(); index++) {
142: LockRef lockRef = (LockRef) transactionEntries
143: .get(transactionQueue.get(index));
144: lockRef.release();
145: }
146:
147: } catch (Exception ex) {
148: log.error(
149: "Failed to commit the resource transaction : "
150: + ex.getMessage(), ex);
151: throw new TransactionException(
152: "Failed to commit the resource transaction : "
153: + ex.getMessage(), ex);
154: }
155: }
156:
157: /**
158: * This method is responsible for commiting this transaction
159: *
160: * @exception TransactionException
161: */
162: public void forget() throws TransactionException {
163: try {
164: for (int index = 0; index < transactionQueue.size(); index++) {
165: Object ref = transactionQueue.get(index);
166: if (ref instanceof XAResource) {
167: ((XAResource) ref).forget(xid);
168: }
169: }
170: for (int index = 0; index < transactionQueue.size(); index++) {
171: LockRef lockRef = (LockRef) transactionEntries
172: .get(transactionQueue.get(index));
173: lockRef.release();
174: }
175:
176: } catch (Exception ex) {
177: log.error("Failed to forget the resource transaction: "
178: + ex.getMessage(), ex);
179: throw new TransactionException(
180: "Failed to forget the resource transaction : "
181: + ex.getMessage(), ex);
182: }
183: }
184:
185: /**
186: * This method is called to roll back the specified transaction.
187: *
188: * @param xid The id of the transaction to roll back.
189: * @exception XAException
190: */
191: public void rollback() throws TransactionException {
192: try {
193: for (int index = 0; index < transactionQueue.size(); index++) {
194: int pos = transactionQueue.size() - (index + 1);
195: Object ref = transactionQueue.get(pos);
196: if (ref instanceof XAResource) {
197: ((XAResource) ref).rollback(xid);
198: }
199: }
200: for (int index = 0; index < transactionQueue.size(); index++) {
201: int pos = transactionQueue.size() - (index + 1);
202: LockRef lockRef = (LockRef) transactionEntries
203: .get(transactionQueue.get(pos));
204: lockRef.release();
205: }
206:
207: } catch (Exception ex) {
208: log.error("Failed to forget the resource transaction: "
209: + ex.getMessage(), ex);
210: throw new TransactionException(
211: "Failed to forget the resource transaction : "
212: + ex.getMessage(), ex);
213: }
214: }
215: }
216:
217: // class constants
218: private final static String TIMEOUT = "transaction_timeout";
219: private final static long DEFAULT_TIMEOUT = 180000;
220:
221: // private member variables
222: protected static Logger log = Logger
223: .getLogger(TransactionManager.class.getName());
224:
225: // the singleton object.
226: private static Map singletonMap = new HashMap();
227:
228: // private member variables
229: private Context context = null;
230: private int timeout = 0;
231: private javax.transaction.TransactionManager jtaTransManager = null;
232: private ThreadLocal threadXID = new ThreadLocal();
233: private Map transactions = new ConcurrentHashMap();
234:
235: /**
236: * Creates a new instance of TransactionManager
237: */
238: private TransactionManager() throws TransactionException {
239: try {
240: context = new InitialContext();
241: jtaTransManager = (javax.transaction.TransactionManager) context
242: .lookup("java:comp/TransactionManager");
243: Configuration config = ConfigurationFactory.getInstance()
244: .getConfig(this .getClass());
245: timeout = (int) config.getLong(TIMEOUT, DEFAULT_TIMEOUT);
246: } catch (Exception ex) {
247: log.error(
248: "Failed to log init the transaction manager because : "
249: + ex.getMessage(), ex);
250: throw new TransactionException(
251: "Failed to log init the transaction manager because : "
252: + ex.getMessage(), ex);
253: }
254: }
255:
256: /**
257: * This method creates a new object lock factory singleton for a class
258: * loader.
259: *
260: * @exception TransactionException
261: */
262: public synchronized static void init() throws TransactionException {
263: ClassLoader loader = Thread.currentThread()
264: .getContextClassLoader();
265: if (!singletonMap.containsKey(loader)) {
266: singletonMap.put(loader, new TransactionManager());
267: }
268: }
269:
270: /**
271: * This method returns an instance of the transaction manager singleton.
272: *
273: * @return An instance of the transaction manager singleton.
274: */
275: public synchronized static TransactionManager getInstance()
276: throws TransactionException {
277: ClassLoader loader = Thread.currentThread()
278: .getContextClassLoader();
279: TransactionManager singleton = null;
280: if (singletonMap.containsKey(loader)) {
281: singleton = (TransactionManager) singletonMap.get(loader);
282: } else {
283: throw new TransactionException(
284: "There is no transaction manager "
285: + "for this class loader");
286: }
287: return singleton;
288: }
289:
290: /**
291: * This method removes the object lock factory associated with a class
292: * loader.
293: *
294: * @exception TransactionException
295: */
296: public synchronized static void fin() throws TransactionException {
297: ClassLoader loader = Thread.currentThread()
298: .getContextClassLoader();
299: if (singletonMap.containsKey(loader)) {
300: singletonMap.remove(loader);
301: }
302: }
303:
304: /**
305: * This method binds a resource to the current transaction bound to this
306: * thread.
307: *
308: * @param xaResource The reference to the xaResource.
309: * @param writeLock If true the object must aquire a write lock.
310: * @exception TransactionException
311: */
312: public void bindResource(XAResource xaResource, boolean writeLock)
313: throws TransactionException {
314: try {
315: jtaTransManager.getTransaction().enlistResource(this );
316: Xid currentTransactionId = (Xid) threadXID.get();
317: Transaction transaction = (Transaction) transactions
318: .get(currentTransactionId);
319: if (transaction == null) {
320: transaction = new Transaction(currentTransactionId);
321: transactions.put(currentTransactionId, transaction);
322: }
323: transaction.addResource(xaResource, writeLock);
324: } catch (Exception ex) {
325: log.error("Failed to bind the resource to the transaction "
326: + "manager : " + ex.getMessage(), ex);
327: throw new TransactionException(
328: "Failed to bind the resource to the transaction "
329: + "manager : " + ex.getMessage(), ex);
330: }
331: }
332:
333: /**
334: * This method is responsible for handling the committing of a transaction
335: * identified by the xid.
336: *
337: * @param xid The id of the transaction to commit.
338: * @param onePhase If true a one phase commit should be used.
339: * @exception XAException
340: */
341: public void commit(Xid xid, boolean onePhase) throws XAException {
342: try {
343: Transaction transaction = (Transaction) transactions
344: .get(xid);
345: if (transaction != null) {
346: transaction.commit(onePhase);
347: transactions.remove(xid);
348: }
349: } catch (Exception ex) {
350: log.error("Failed to commit the transaction :"
351: + ex.getMessage(), ex);
352: throw new XAException("Failed to commit the transaction :"
353: + ex.getMessage());
354: }
355: }
356:
357: /**
358: * The resource manager has dissociated this object from the transaction.
359: *
360: * @param xid The id of the transaction that is getting ended.
361: * @param flags The flags associated with this operation.
362: * @exception XAException
363: */
364: public void end(Xid xid, int flags) throws XAException {
365: }
366:
367: /**
368: * The transaction has been completed and must be forgotten.
369: *
370: * @param xid The id of the transaction to forget.
371: * @exception XAException
372: */
373: public void forget(Xid xid) throws XAException {
374: try {
375: Transaction transaction = (Transaction) transactions
376: .get(xid);
377: if (transaction != null) {
378: transaction.forget();
379: transactions.remove(xid);
380: }
381: } catch (Exception ex) {
382: log.error("Failed to forget the transaction :"
383: + ex.getMessage(), ex);
384: throw new XAException("Failed to forget the transaction :"
385: + ex.getMessage());
386: }
387: }
388:
389: /**
390: * This method returns the transaction timeout for this object.
391: *
392: * @return The int containing the transaction timeout.
393: * @exception XAException
394: */
395: public int getTransactionTimeout() throws XAException {
396: return timeout;
397: }
398:
399: /**
400: * This method returns true if this object is the resource manager getting
401: * queried.
402: *
403: * @return TRUE if this is the resource manager, FALSE if not.
404: * @param xaResource The resource to perform the check against.
405: * @exception XAException
406: */
407: public boolean isSameRM(XAResource xAResource) throws XAException {
408: return this == xAResource;
409: }
410:
411: /**
412: * This is called before a transaction is committed.
413: *
414: * @return The results of the transaction.
415: * @param xid The id of the transaction to check against.
416: * @exception XAException
417: */
418: public int prepare(Xid xid) throws XAException {
419: return XAResource.XA_OK;
420: }
421:
422: /**
423: * This method returns the list of transaction branches for this resource
424: * manager.
425: *
426: * @return The list of resource branches.
427: * @param flags The flags
428: * @exception XAException
429: */
430: public Xid[] recover(int flags) throws XAException {
431: return null;
432: }
433:
434: /**
435: * This method is called to roll back the specified transaction.
436: *
437: * @param xid The id of the transaction to roll back.
438: * @exception XAException
439: */
440: public void rollback(Xid xid) throws XAException {
441: try {
442: Transaction transaction = (Transaction) transactions
443: .get(xid);
444: if (transaction != null) {
445: transaction.rollback();
446: transactions.remove(xid);
447: }
448: } catch (Exception ex) {
449: log.error("Failed to rollback the transaction :"
450: + ex.getMessage(), ex);
451: throw new XAException(
452: "Failed to rollback the transaction :"
453: + ex.getMessage());
454: }
455: }
456:
457: /**
458: * This method sets the transaction timeout for this resource manager.
459: *
460: * @return TRUE if the transaction timeout can be set successfully.
461: * @param transactionTimeout The new transaction timeout value.
462: * @exception XAException
463: */
464: public boolean setTransactionTimeout(int transactionTimeout)
465: throws XAException {
466: this .timeout = transactionTimeout;
467: return true;
468: }
469:
470: /**
471: * This method is called to start a transaction on a resource manager.
472: *
473: * @param xid The id of the new transaction.
474: * @param flags The flags associated with the transaction.
475: * @exception XAException
476: */
477: public void start(Xid xid, int flags) throws XAException {
478: threadXID.set(xid);
479: }
480:
481: }
|