KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > opensubsystems > core > persist > db > transaction > SimpleLocalTransactionFactoryImpl


1 /*
2  * Copyright (c) 2003 - 2007 OpenSubsystems s.r.o. Slovak Republic. All rights reserved.
3  *
4  * Project: OpenSubsystems
5  *
6  * $Id: SimpleLocalTransactionFactoryImpl.java,v 1.7 2007/01/07 06:14:21 bastafidli Exp $
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; version 2 of the License.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20  */

21
22 package org.opensubsystems.core.persist.db.transaction;
23
24 import java.sql.Connection JavaDoc;
25 import java.sql.SQLException JavaDoc;
26 import java.util.logging.Logger JavaDoc;
27
28 import javax.transaction.HeuristicMixedException JavaDoc;
29 import javax.transaction.HeuristicRollbackException JavaDoc;
30 import javax.transaction.NotSupportedException JavaDoc;
31 import javax.transaction.RollbackException JavaDoc;
32 import javax.transaction.Status JavaDoc;
33 import javax.transaction.SystemException JavaDoc;
34 import javax.transaction.TransactionManager JavaDoc;
35 import javax.transaction.UserTransaction JavaDoc;
36
37 import org.opensubsystems.core.error.OSSDatabaseAccessException;
38 import org.opensubsystems.core.error.OSSException;
39 import org.opensubsystems.core.persist.db.DatabaseConnectionFactoryImpl;
40 import org.opensubsystems.core.persist.db.DatabaseTransactionFactoryImpl;
41 import org.opensubsystems.core.util.GlobalConstants;
42 import org.opensubsystems.core.util.Log;
43
44 /**
45  * Simplified transaction management implementation based on database connection
46  * using following assumptions:
47  *
48  * Assumptions:
49  * ------------
50  * 1. Most applications use/access only single database, therefore there is no
51  * need for distributed transaction implementation.
52  * 2. Applications consists of threads and each thread is using/needing only
53  * single database connection at a time. Most threads will never need two
54  * connections at the same time to the same database.
55  * 3. At most one transaction is in progress at any time in one thread.
56  *
57  * Therefore if an application is accessing only a single database and threads
58  * of the application use only single connection at a time and start at most
59  * one transaction at a time the application can use this implementation of
60  * transaction manager.
61  *
62  * How it works:
63  * -------------
64  * 1. Thread is using JDBC database connection to access the database.
65  * 2. If thread starts an transaction using UserTransaction.begin then the
66  * connection used to access the database in this transaction has to have
67  * autocommit set to false and the thread cannot issue commit or rollback on
68  * the transaction. Then when thread ends transaction using
69  * UserTransaction.commit or UserTransaction.rollback then the transaction
70  * manager commits or rollbacks connections which was used to access the
71  * database within this transaction.
72  * 3. If thread is accessing the database outside of transaction, it can do
73  * whatever it wants with the connection including setting autocommit to true
74  * and calling commit and rollback on the connection.
75  *
76  * There are 4 basic and 4 combined scenarios possible:
77  * ----------------------------------------------------
78  * 1. Connection is requested before the transaction and returned after the transaction
79  *
80  * DatabaseConnectionFactory.requestConnection
81  * UserTransaction.begin
82  * UserTransaction.commit/rollback
83  * DatabaseConnectionFactory.returnConnection
84  *
85  * 2. Connection is requested in the transaction and returned after the transaction
86  *
87  * UserTransaction.begin
88  * DatabaseConnectionFactory.requestConnection
89  * UserTransaction.commit/rollback
90  * DatabaseConnectionFactory.returnConnection
91  *
92  * 3. Connection is requested before the transaction and returned in the transaction
93  *
94  * DatabaseConnectionFactory.requestConnection
95  * UserTransaction.begin
96  * DatabaseConnectionFactory.returnConnection
97  * UserTransaction.commit/rollback
98  *
99  * 4. Connection is requested in the transaction and returned in the transaction
100  *
101  * UserTransaction.begin
102  * DatabaseConnectionFactory.requestConnection
103  * DatabaseConnectionFactory.returnConnection
104  * UserTransaction.commit/rollback
105  *
106  * 5. Connection is requested before the transaction and returned in the transaction
107  * and then connection is requested again ithe transaction and returned in
108  * the transaction
109  *
110  * DatabaseConnectionFactory.requestConnection
111  * UserTransaction.begin
112  * DatabaseConnectionFactory.returnConnection
113  * DatabaseConnectionFactory.requestConnection
114  * DatabaseConnectionFactory.returnConnection
115  * UserTransaction.commit/rollback
116  *
117  * 6. Connection is requested before the transaction and returned in the transaction
118  * and then connection is requested again ithe transaction and returned after
119  * the transaction
120  *
121  * DatabaseConnectionFactory.requestConnection
122  * UserTransaction.begin
123  * DatabaseConnectionFactory.returnConnection
124  * DatabaseConnectionFactory.requestConnection
125  * UserTransaction.commit/rollback
126  * DatabaseConnectionFactory.returnConnection
127  *
128  * 7. Connection is requested in the transaction and returned in the transaction
129  * and then connection is requested again ithe transaction and returned after
130  * the transaction
131  *
132  * UserTransaction.begin
133  * DatabaseConnectionFactory.requestConnection
134  * DatabaseConnectionFactory.returnConnection
135  * DatabaseConnectionFactory.requestConnection
136  * UserTransaction.commit/rollback
137  * DatabaseConnectionFactory.returnConnection
138  *
139  * 8. Connection is requested in the transaction and returned in the transaction
140  * and then connection is requested again ithe transaction and returned in
141  * the transaction
142  *
143  * UserTransaction.begin
144  * DatabaseConnectionFactory.requestConnection
145  * DatabaseConnectionFactory.returnConnection
146  * DatabaseConnectionFactory.requestConnection
147  * DatabaseConnectionFactory.returnConnection
148  * UserTransaction.commit/rollback
149  *
150  * What are the issues:
151  * --------------------
152  * A. If thread acquires connection before the transaction is started then at
153  * the time when the transaction is started we have to associate the
154  * connection with the transaction.
155  * Affects: 1,3,5,6
156  *
157  * A.1 If the connection wasn't used within the transaction the the commit or
158  * rollback on transaction should be a no op.
159  *
160  * B. If the thread acquired connection during the transaction then this
161  * connection should be automatically associated with the transaction
162  * Affects: 2,4,5,6,7,8
163  *
164  * C. If thread returns the connection before the transaction is finished then
165  * the transaction manager has to keep the connection open until the thread
166  * ends the transaction.
167  * Affects: 3,4,5,8
168  *
169  * D: If the thread keeps the connection after the transaction is finished then
170  * after the transaction is finished the connection should be fully usable
171  * as any other JDBC connection including autocommit and commit/rollback
172  * but in the transaction the autocommit/commit/rollback should be disabled.
173  * Affects: 1,2,6,7
174  *
175  * E: If the thread requests and returns connection multiple times during the
176  * same transaction then the same connection has to be given to it since
177  * the transaction is responsible for doing commit/rollback on the connection
178  * and our assumption is that the thread is using only single connection.
179  * Affects: 5,6,7,8
180  *
181  * What are the solutions:
182  * -----------------------
183  * A: The DatabaseConnectionFactory has to keep track if there is a connection
184  * issued to the calling thread so that when the transaction is started then
185  * this connection can be associated to the transaction.
186  * This can be done using ThreadLocal storing the requested connection. Then
187  * when the transaction is started, the UserTransaction has to check if there
188  * is an already requested connection and associate it with the transaction.
189  * This can be done using ThreadLocal storing the transactional connection.
190  *
191  * A.1 This can be done be creating wrapper around Connection.
192  * The DatabaseConnectionFactory will then on request and return create this
193  * wrapper which delegates all calls into underlying connection. When any
194  * method is called, it marks the connection as used. The transaction manager
195  * can then reset the used flag when the connection is first time associated
196  * with transaction or check it once commit or rollback were issued.
197  *
198  * B: The DatabaseConnectionFactory has to be aware of the started transaction
199  * and when an connection is requested then the connection is associated
200  * with the transaction.
201  * This can be done using ThreadLocal storing a flag if the transaction is
202  * in progress or not. If the transaction is in progress then the returned
203  * connection will be stored in the transactional connection.
204  *
205  * C: The UserTransaction has to keep track of the connection which was used
206  * in the transaction and don't allow to return it until the transaction is
207  * finished. Then, once the transaction is finished it has to return the
208  * connection.
209  * This can be done using ThreadLocal storing a flag for the transactional
210  * connection if the connection was returned in the transaction or not.
211  *
212  * D: The connection should be aware if there is a transaction in progress or
213  * not and based on that allow autocommit and commit or rollback or not.
214  * This can be done be creating wrapper around Connection.
215  * The DatabaseConnectionFactory will then on request and return create this
216  * wrapper which delegates all calls into underlying connection and intercepts
217  * and checks autocommit and commit and rollback.
218  *
219  * E. The DatabaseConnectionFactory has to be aware of the started transaction
220  * and when a connection is requested then the connection already associated
221  * with the transaction should be returned.
222  *
223  * What are the implications:
224  * --------------------------
225  * 1. If the connection is associated with transaction it is not returned to the
226  * factory and not available for others. This is natural since there are some
227  * operations pending on that connection and even though the thread said it
228  * no longer needs it, it cannot be reused until the operations are flushed
229  * with commit/rollback.
230  *
231  * Implementation:
232  * ---------------
233  * 1. D. requires to create wrapper around connection. We will implement
234  * TransactionalConnection class as wrapper around JDBC Connection class,
235  * which will delegate all operations directly except setautocommit/commit/rollback,
236  * which will be delegated only after check for pending transaction.
237  *
238  * 2. The previous step required us to create integration between DatabaseTransactionFactory
239  * and DatabaseConnectionFactory, so that the transaction factory can act as
240  * a proxy for connection factory and get the real connection and wrap
241  * it with a wrapper. This will be implemented as a delegator pattern
242  * when DatabaseConnectionFactoryImpl will be delegating calls to
243  * DatabaseTransactionFactoryImpl and vice versa.
244  *
245  * The requestConnection call will
246  * - check if there is an transaction in progress, if there is and it has
247  * already associated connection then the connection associated with the
248  * transaction will be returned.
249  * - if there is no transaction in progress and there is already connection
250  * issued then return the same connection since this would represent the
251  * situation (such as subprocedure call)
252  * DatabaseConnectionFactory.requestConnection
253  * DatabaseConnectionFactory.requestConnection
254  * DatabaseConnectionFactory.returnConnection
255  * DatabaseConnectionFactory.returnConnection
256  * - if there is no transaction in progress and no already issued connection
257  * then get connection from underlying connection factory, create wrapper
258  * and remember the connection as issued using ThreadLocal.
259  * This will solve A, B and E
260  *
261  * The returnConnection call will
262  * - check if there is an transaction in progress, if there is then the
263  * connection will not be returned to the factory and will be returned when
264  * the transaction is done
265  * - if there is no transaction in progress then the underlying connection
266  * will be returned to the factory
267  * This will solve C
268  *
269  * 3. We will provide implementation of UserTransaction.
270  *
271  * The begin call will
272  * - check if there was connection issued and if it was, it will associate
273  * it with this transaction. It will also tell the connection that it
274  * is part of the transaction to ignore the setautocommit/commit/rollback
275  * calls.
276  *
277  * The commit call will
278  * - if there is connection associated with this transaction, it will commit
279  * the connection. If the connection was returned to the factory in the
280  * transaction then this time it will be really returned to the factory
281  * otherwise it will be just disassociated from this transaction so that
282  * the setautocommit/commit/rollback calls are no longer ignored.
283  *
284  * The rollback call will
285  * - if there is connection associated with this transaction, it will rollback
286  * the connection. If the connection was returned to the factory in the
287  * transaction then this time it will be really returned to the factory
288  * otherwise it will be just disassociated from this transaction so that
289  * the setautocommit/commit/rollback calls are no longer ignored.
290  *
291  * TransactionalConnection
292  * To distinguish if the TransactionalConnection was associated in transaction
293  * it will have inTransaction flag.
294  * To distinguish if the TransactionalConnection was used during the transaction
295  * it will have used flag which will be false initially and set to true if
296  * any method on connection is called.
297  * To distinguish if the TransactionalConnection was returned to factory or it
298  * is still used by application it will have active counter which will be
299  * incremented when the connection is requested from factory and decremented
300  * when it is returned.
301  *
302  * @version $Id: SimpleLocalTransactionFactoryImpl.java,v 1.7 2007/01/07 06:14:21 bastafidli Exp $
303  * @author Miro Halas
304  * @code.reviewer Miro Halas
305  * @code.reviewed 1.4 2006/05/24 15:18:32 bastafidli
306  */

307 public class SimpleLocalTransactionFactoryImpl extends DatabaseTransactionFactoryImpl
308                                                implements UserTransaction JavaDoc
309 {
310    // Constants ////////////////////////////////////////////////////////////////
311

312   /**
313    * A transaction is associated with the target object and it is in the active state.
314    */

315    public static final Integer JavaDoc STATUS_ACTIVE_OBJ
316                                   = new Integer JavaDoc(Status.STATUS_ACTIVE);
317    
318   /**
319    * A transaction is associated with the target object and it has been committed.
320    */

321    public static final Integer JavaDoc STATUS_COMMITTED_OBJ
322                                   = new Integer JavaDoc(Status.STATUS_COMMITTED);
323
324   /**
325    * A transaction is associated with the target object and it is in the process
326    * of committing.
327    */

328    public static final Integer JavaDoc STATUS_COMMITTING_OBJ
329                                   = new Integer JavaDoc(Status.STATUS_COMMITTING);
330
331   /**
332    * A transaction is associated with the target object and it has been marked
333    * for rollback, perhaps as a result of a setRollbackOnly operation.
334    */

335    public static final Integer JavaDoc STATUS_MARKED_ROLLBACK_OBJ
336                                   = new Integer JavaDoc(Status.STATUS_MARKED_ROLLBACK);
337
338   /**
339    * No transaction is currently associated with the target object.
340    */

341    public static final Integer JavaDoc STATUS_NO_TRANSACTION_OBJ
342                                   = new Integer JavaDoc(Status.STATUS_NO_TRANSACTION);
343
344   /**
345    * A transaction is associated with the target object and it has been prepared, i.e.
346    */

347    public static final Integer JavaDoc STATUS_PREPARED_OBJ
348                                   = new Integer JavaDoc(Status.STATUS_PREPARED);
349
350   /**
351    * A transaction is associated with the target object and it is in the process
352    * of preparing.
353    */

354    public static final Integer JavaDoc STATUS_PREPARING_OBJ
355                                   = new Integer JavaDoc(Status.STATUS_PREPARING);
356
357   /**
358    * A transaction is associated with the target object and the outcome has been
359    * determined as rollback.
360    */

361    public static final Integer JavaDoc STATUS_ROLLEDBACK_OBJ
362                                   = new Integer JavaDoc(Status.STATUS_ROLLEDBACK);
363
364   /**
365    * A transaction is associated with the target object and it is in the process
366    * of rolling back.
367    */

368    public static final Integer JavaDoc STATUS_ROLLING_BACK_OBJ
369                                   = new Integer JavaDoc(Status.STATUS_ROLLING_BACK);
370
371   /**
372    * A transaction is associated with the target object but its current status
373    * cannot be determined.
374    */

375    public static final Integer JavaDoc STATUS_UNKNOWN_OBJ
376                                   = new Integer JavaDoc(Status.STATUS_UNKNOWN);
377       
378    // Attributes ///////////////////////////////////////////////////////////////
379

380    /**
381     * Database connection which this thread requested from the factory. The
382     * current assumption is that the thread can have only one connection at
383     * a time. The connection can be part of global transaction (since there is
384     * only one transaction per thread then this connection really represents
385     * the transaction). The stored data have type TransactionConnection.
386     */

387    private ThreadLocal JavaDoc m_connection;
388    
389    /**
390     * This is the real database connection which is wraped in TransactionConnection
391     * since the TransactionConnection doesn't allow us to access it.
392     */

393    private ThreadLocal JavaDoc m_realConnection;
394
395    /**
396     * Transaction that is currently in progress if any. The current assumption
397     * is that there can be only one transaction active per thread. The stored
398     * data have type Integer with values defined in javax.transaction.Status
399     * interface.
400     */

401    private ThreadLocal JavaDoc m_transaction;
402    
403    // Cached values ////////////////////////////////////////////////////////////
404

405    /**
406     * Logger for this class
407     */

408    private static Logger JavaDoc s_logger = Log.getInstance(SimpleLocalTransactionFactoryImpl.class);
409
410    // Constructors /////////////////////////////////////////////////////////////
411

412    /**
413     * Default constructor using default database connection factory.
414     *
415     * @throws OSSException - an error has occured
416     */

417    public SimpleLocalTransactionFactoryImpl(
418    ) throws OSSException
419    {
420       super();
421       
422       // Don't make it inheritable so that spawn threads can have their own
423
// connections and transactions
424
m_connection = new ThreadLocal JavaDoc();
425       m_transaction = new ThreadLocal JavaDoc();
426       m_realConnection = new ThreadLocal JavaDoc();
427    }
428
429    // DatabaseTransactionFactory methods ///////////////////////////////////////////////
430

431    /**
432     * {@inheritDoc}
433     */

434    public UserTransaction JavaDoc requestTransaction()
435    {
436       // This class acts also as user transaction
437
UserTransaction JavaDoc transaction = this;
438       
439       if (s_bTransactionMonitor)
440       {
441          transaction = new DelegatingUserTransaction(transaction);
442       }
443       return transaction;
444    }
445    
446    /**
447     * {@inheritDoc}
448     */

449    public void reset(
450    ) throws OSSException
451    {
452       // The only thing we can do is reset the thread local again. This method
453
// should be used only for testing
454
s_logger.severe("About to reset state of transaction manager." +
455                       " Hope you know what you are doing.");
456       m_connection = new ThreadLocal JavaDoc();
457       m_transaction = new ThreadLocal JavaDoc();
458       m_realConnection = new ThreadLocal JavaDoc();
459    }
460    
461    /**
462     * {@inheritDoc}
463     */

464    public void stop(
465    ) throws OSSException
466    {
467       // The transaction factory stop is noop since we don't do here anything
468
// special
469
}
470
471    /**
472     * {@inheritDoc}
473     */

474    public TransactionManager JavaDoc getTransactionManager(
475    )
476    {
477       // There is no transaction manager for this factory since it would require
478
// us to implement the XA classes it references and we really care only
479
// about UserTransaction
480
return null;
481    }
482    
483    // UserTransaction methods //////////////////////////////////////////////////
484

485    /**
486     * {@inheritDoc}
487     */

488    public void begin(
489    ) throws NotSupportedException JavaDoc,
490             SystemException JavaDoc
491    {
492       try
493       {
494          if (isTransactionInProgress())
495          {
496             throw new NotSupportedException JavaDoc("Cannot start another transaction while" +
497                                             " one is still in progress.");
498          }
499          else
500          {
501             m_transaction.set(STATUS_ACTIVE_OBJ);
502             
503             TransactionalConnection existingConnection;
504             
505             existingConnection = (TransactionalConnection)m_connection.get();
506             if (existingConnection != null)
507             {
508                // There is already a connection issued from the factory which
509
// was issued before the transaction was started so make sure
510
// the connection knows it is in transaction so it doesn't allow
511
// commit/rollback/setautocommit
512
if (GlobalConstants.ERROR_CHECKING)
513                {
514                   assert (!existingConnection.isInTransaction())
515                          : "Connection cannot be already in transaction when" +
516                            " transaction is only starting.";
517                }
518                
519                try
520                {
521                   // The connection wasn't used in transaction yet
522
existingConnection.setUsed(false);
523                   existingConnection.setInTransaction(true);
524                }
525                catch (SQLException JavaDoc sqleExc)
526                {
527                   SystemException JavaDoc sysExc = new SystemException JavaDoc(
528                                "Cannot associate connection with transaction.");
529                   sysExc.initCause(sqleExc);
530                   throw sysExc;
531                }
532             }
533             else
534             {
535                // If there is no connection then we don't have to worry about
536
// and anything when the connection will be requested from the
537
// factory it will be associated to this transaction.
538
}
539          }
540       }
541       catch (OSSException ossExc)
542       {
543          throw new SystemException JavaDoc("Error occured while retrieving information " +
544                                    "about transaction status.");
545       }
546    }
547
548    /**
549     * {@inheritDoc}
550     */

551    public void commit(
552    ) throws RollbackException JavaDoc,
553             HeuristicMixedException JavaDoc,
554             HeuristicRollbackException JavaDoc,
555             SecurityException JavaDoc,
556             IllegalStateException JavaDoc,
557             SystemException JavaDoc
558    {
559       endTransaction(true);
560    }
561
562    /**
563     * {@inheritDoc}
564     */

565    public int getStatus(
566    ) throws SystemException JavaDoc
567    {
568       Integer JavaDoc iTransactionStatus = (Integer JavaDoc)m_transaction.get();
569       int iStatus;
570       
571       if (iTransactionStatus == null)
572       {
573          iStatus = Status.STATUS_NO_TRANSACTION;
574       }
575       else
576       {
577          iStatus = iTransactionStatus.intValue();
578       }
579       
580       return iStatus;
581    }
582
583    /**
584     * {@inheritDoc}
585     */

586    public void rollback(
587    ) throws IllegalStateException JavaDoc,
588             SecurityException JavaDoc,
589             SystemException JavaDoc
590    {
591       endTransaction(false);
592    }
593
594    /**
595     * {@inheritDoc}
596     */

597    public void setRollbackOnly(
598    ) throws IllegalStateException JavaDoc,
599             SystemException JavaDoc
600    {
601       try
602       {
603          if (isTransactionInProgress())
604          {
605             m_transaction.set(STATUS_MARKED_ROLLBACK_OBJ);
606          }
607          else
608          {
609             throw new IllegalStateException JavaDoc("No transaction in progress to be marked" +
610                                             " rollbacked only.");
611          }
612       }
613       catch (OSSException ossExc)
614       {
615          throw new SystemException JavaDoc("Error occured while retrieving information " +
616                                    "about transaction status.");
617       }
618    }
619
620    /**
621     * {@inheritDoc}
622     */

623    public void setTransactionTimeout(
624       int arg0
625    ) throws SystemException JavaDoc
626    {
627       // At this time there is no way how to set time out for transaction
628
// TODO: Feature: Implement transaction timeout for this factory
629
}
630    
631    // Helper methods ///////////////////////////////////////////////////////////
632

633    /**
634     * Check if there is an transaction in progress, if there is and it has
635     * already associated connection then the connection associated with the
636     * transaction will be returned.
637     * If there is no transaction in progress and there is already connection
638     * issued then return the same connection since this would represent the
639     * situation (such as subprocedure call)
640     * DatabaseConnectionFactory.requestConnection
641     * DatabaseConnectionFactory.requestConnection
642     * DatabaseConnectionFactory.returnConnection
643     * DatabaseConnectionFactory.returnConnection
644     * If there is no transaction in progress and no already issued connection
645     * then get connection from the calling connection factory, create wrapper
646     * and remember the connection as issued using ThreadLocal.
647     *
648     * @param bAutoCommit {@inheritDoc}
649     * @param strDataSourceName {@inheritDoc}
650     * @param strUser {@inheritDoc}
651     * @param strPassword {@inheritDoc}
652     * @param connectionFactory {@inheritDoc}
653     * @return {@inheritDoc}
654     * @throws OSSDatabaseAccessException {@inheritDoc}
655     */

656    protected Connection JavaDoc requestTransactionalConnection(
657       boolean bAutoCommit,
658       String JavaDoc strDataSourceName,
659       String JavaDoc strUser,
660       String JavaDoc strPassword,
661       DatabaseConnectionFactoryImpl connectionFactory
662    ) throws OSSDatabaseAccessException
663    {
664       TransactionalConnection existingConnection;
665       Connection JavaDoc existingRealConnection;
666       boolean bTransaction;
667       
668       existingConnection = (TransactionalConnection)m_connection.get();
669       existingRealConnection = (Connection JavaDoc)m_realConnection.get();
670       try
671       {
672          bTransaction = isTransactionInProgress();
673       }
674       catch (SystemException JavaDoc sysExc)
675       {
676          throw new OSSDatabaseAccessException(
677                       "There was an exception occured while retrieving UserTransaction " +
678                       "status.", sysExc);
679       }
680       catch (OSSException osseExc)
681       {
682          throw new OSSDatabaseAccessException(
683                       "There was an exception occured while retrieving UserTransaction " +
684                       "status.", osseExc);
685       }
686
687       if (existingConnection != null)
688       {
689          // There is already a connection issued from the factory so it should
690
// be already associated with transaction so test for it
691
if (GlobalConstants.ERROR_CHECKING)
692          {
693             assert (((!existingConnection.isInTransaction()) && (!bTransaction))
694                    || ((existingConnection.isInTransaction()) && (bTransaction)))
695                    : "Connection status about transaction in progress doesn't" +
696                      " match the reality.";
697             assert existingConnection.verifyConnection(existingRealConnection)
698                    : "The real database connection is not the one which is wrapped" +
699                      " in transactional wrapper.";
700          }
701          
702          // Now make sure that this is connection for the same user as requested
703
String JavaDoc strCurrentDataSourceName;
704          String JavaDoc strExistingDataSourceName;
705          String JavaDoc strExistingUser;
706          String JavaDoc strExistingPassword;
707          
708          if (strDataSourceName == null)
709          {
710             strCurrentDataSourceName = connectionFactory.getDefaultDataSourceName();
711          }
712          else
713          {
714             strCurrentDataSourceName = strDataSourceName;
715          }
716          strExistingDataSourceName = existingConnection.getDataSourceName();
717          strExistingUser = existingConnection.getUser();
718          strExistingPassword = existingConnection.getPassword();
719          
720          // I think we do want to compare as == since we want to verify that the
721
// same connection factory for which we already have connection
722
// is trying to get a new one
723
if (connectionFactory != existingConnection.getConnectionFactory())
724          {
725             throw new OSSDatabaseAccessException(
726                          "Cannot issue connection from the factory for a different" +
727                          " connection factory that the factory for which the" +
728                          " connection is already issued.");
729          }
730          
731          if ((((strCurrentDataSourceName == null) && (strExistingDataSourceName == null))
732                || ((strCurrentDataSourceName != null)
733                   && (strCurrentDataSourceName.equals(strExistingDataSourceName))))
734             && (((strUser == null) && (strExistingUser == null))
735                || ((strUser != null) && (strUser.equals(strExistingUser))))
736             && (((strPassword == null) && (strExistingPassword == null))
737                || ((strPassword != null) && (strPassword.equals(strExistingPassword)))))
738          {
739             // Make this connection active again since we are issuing it from
740
// the factory
741
existingConnection.setActive(true);
742          }
743          else
744          {
745             // The data source, user name or password doesn't match so do not
746
// return connection since the assumption is that there can be
747
// only one connection at a time
748
throw new OSSDatabaseAccessException(
749                          "Cannot issue connection from the factory for a different" +
750                          " user than the user for which the connection is already issued.");
751          }
752       }
753       else
754       {
755          Connection JavaDoc realConnection;
756          
757          // There is no connection yet so create a new one. Here we have to
758
// call the non transaction version of the requestConnection method
759
// since the transactional version is the one which called us.
760
realConnection = super.requestTransactionalConnection(bAutoCommit,
761                              strDataSourceName, strUser, strPassword,
762                              connectionFactory);
763          
764          if (strDataSourceName == null)
765          {
766             strDataSourceName = connectionFactory.getDefaultDataSourceName();
767          }
768          
769          existingConnection = new TransactionalConnection(
770                                      realConnection,
771                                      strDataSourceName,
772                                      strUser,
773                                      strPassword,
774                                      bTransaction,
775                                      connectionFactory);
776          // Remember this connection
777
m_connection.set(existingConnection);
778          // Remember the real connection since we will need to return it and
779
// the TransactionalConnection doesn't allow us to access it
780
m_realConnection.set(realConnection);
781       }
782       
783       return existingConnection;
784    }
785
786    /**
787     * {@inheritDoc}
788     */

789    protected void returnTransactionalConnection(
790       Connection JavaDoc cntDBConnection,
791       DatabaseConnectionFactoryImpl connectionFactory
792    )
793    {
794       if (cntDBConnection != null)
795       {
796          if (cntDBConnection instanceof TransactionalConnection)
797          {
798             // This is a transaction aware connection so process it as such
799
TransactionalConnection connection;
800             Connection JavaDoc realConnection;
801       
802             connection = (TransactionalConnection)cntDBConnection;
803             realConnection = (Connection JavaDoc)m_realConnection.get();
804             if (GlobalConstants.ERROR_CHECKING)
805             {
806                TransactionalConnection existingConnection;
807                existingConnection = (TransactionalConnection)m_connection.get();
808                
809                // The returned connection is not the one we think we issued so this
810
// is error
811
assert (existingConnection != null)
812                       && (existingConnection == connection) // this should be ==
813
: "The returned connection is not the one we think we issued: " +
814                         " existing connection is " + existingConnection +
815                         " returned connection is " + connection;
816                assert existingConnection.verifyConnection(realConnection)
817                       : "The real database connection is not the one which is wrapped" +
818                         " in transactional wrapper.";
819                // This should be ==
820
assert connectionFactory == existingConnection.getConnectionFactory()
821                       : "Cannot issue connection from the factory for a different" +
822                         " connection factory that the factory for which the" +
823                         " connection is already issued.";
824             }
825       
826             // Mark it as inactive since we are returning it
827
connection.setActive(false);
828             // Since the connection could have been requested multiple times, it can
829
// still be active (it has to be returned the same amount of times)
830
if ((!connection.isInTransaction()) && (!connection.isActive()))
831             {
832                // This connection is not part of the transaction and it is not
833
// active anymore so return it back to the real factory by calling
834
// the nontransactional method
835
super.returnTransactionalConnection(realConnection, connectionFactory);
836                // Also since this connection is completely released, release it
837
m_connection.set(null);
838                m_realConnection.set(null);
839             }
840             else
841             {
842                // The connection is part of the transaction so the transaction will
843
// return it when it is done or if the connection is still active
844
// even after it was set inactive that means that the connection was
845
// requested multiple times and must be returned multiple times.
846
}
847          }
848          else
849          {
850             // This is just a regular connection which may have been allocated
851
// before the transaction factory was created so just return it
852
super.returnTransactionalConnection(cntDBConnection, connectionFactory);
853          }
854       }
855    }
856    
857    /**
858     * End active transaction by commit or rollback.
859     *
860     * @param bCommit - if true then transaction will be commited otherwise
861     * it will be rollbacked.
862     * @throws SystemException - and error has occured during commit/rollback
863     */

864    protected void endTransaction(
865       boolean bCommit
866    ) throws SystemException JavaDoc
867    {
868       Integer JavaDoc iTransactionStatus = (Integer JavaDoc)m_transaction.get();
869       
870       if (iTransactionStatus != null)
871       {
872          if (iTransactionStatus.intValue() == Status.STATUS_MARKED_ROLLBACK)
873          {
874             // We have to rollback.
875
bCommit = false;
876          }
877
878          if (iTransactionStatus.intValue() == Status.STATUS_ACTIVE)
879          {
880             try
881             {
882                TransactionalConnection existingConnection;
883                
884                existingConnection = (TransactionalConnection)m_connection.get();
885                if (existingConnection != null)
886                {
887                   // There was connection associated with this transaction
888
if (GlobalConstants.ERROR_CHECKING)
889                   {
890                      assert existingConnection.isInTransaction()
891                             : "The connection is not associated to transaction.";
892                   }
893                   
894                   // The connection was used inside of transaction so lets
895
// disassociate it with transaction which will allow us
896
// to commit/rollback on the connection
897
try
898                   {
899                      existingConnection.setInTransaction(false);
900                      // Now commit/rollback
901
if (bCommit)
902                      {
903                         if (existingConnection.isUsed())
904                         {
905                            // Commit the connection only if it was used in transaction
906
existingConnection.commit();
907                         }
908                         // We have to always change the state of the transaction
909
m_transaction.set(STATUS_COMMITTED_OBJ);
910                      }
911                      else
912                      {
913                         if (existingConnection.isUsed())
914                         {
915                            // Rollback the connection only if it was used in transaction
916
existingConnection.rollback();
917                         }
918                         // We have to always change the state of the transaction
919
m_transaction.set(STATUS_ROLLEDBACK_OBJ);
920                      }
921                      // Since we flushed all operations, the connection is not
922
// used anymore
923
existingConnection.setUsed(false);
924                   }
925                   catch (SQLException JavaDoc sqleExc)
926                   {
927                      SystemException JavaDoc sysExc;
928                      if (bCommit)
929                      {
930                         sysExc = new SystemException JavaDoc(
931                                      "An error has occured during commit.");
932                      }
933                      else
934                      {
935                         sysExc = new SystemException JavaDoc(
936                                      "An error has occured during rollback.");
937                      }
938                      sysExc.initCause(sqleExc);
939                      throw sysExc;
940                   }
941                   if (!existingConnection.isActive())
942                   {
943                      // The connection is no longer active so we need to
944
// return it back to the factory
945
Connection JavaDoc realConnection;
946                
947                      realConnection = (Connection JavaDoc)m_realConnection.get();
948                      if (GlobalConstants.ERROR_CHECKING)
949                      {
950                         assert existingConnection.verifyConnection(realConnection)
951                                : "The real database connection is not the one which is wrapped" +
952                                  " in transactional wrapper.";
953                      }
954                      // This connection is not part of the transaction and it
955
// is not active anymore so return it back to the real factory.
956
existingConnection.getConnectionFactory().returnNonTransactionalConnection(
957                                                                   realConnection);
958                      // Also since this connection is completely released, release it
959
m_connection.set(null);
960                      m_realConnection.set(null);
961                   }
962                }
963                else
964                {
965                   // If there wasn't any active connection that means nothing was
966
// done inside of the transaction so we just need to reset
967
// the status
968
if (bCommit)
969                   {
970                      m_transaction.set(STATUS_COMMITTED_OBJ);
971                   }
972                   else
973                   {
974                      m_transaction.set(STATUS_ROLLEDBACK_OBJ);
975                   }
976                }
977             }
978             finally
979             {
980                // This is here just to check that once we are done with the code
981
// above we have somehow completed transaction status (that mean
982
// every branch somehow sets correctly) transaction status. The
983
// reason is that we had a bug here when the transaction wasn't
984
// reset if there was no work performed in the transaction
985
if (GlobalConstants.ERROR_CHECKING)
986                {
987                   iTransactionStatus = (Integer JavaDoc)m_transaction.get();
988                   // We can use == since only these two constants can be set
989
assert ((iTransactionStatus == STATUS_ROLLEDBACK_OBJ)
990                          || (iTransactionStatus == STATUS_COMMITTED_OBJ))
991                          : "Transaction wasn't commited nor rollbacked.";
992                }
993             }
994          }
995          else
996          {
997             if (bCommit)
998             {
999                throw new IllegalStateException JavaDoc(
1000                            "Transaction cannot be commited if it wasn't started.");
1001            }
1002            else
1003            {
1004               throw new IllegalStateException JavaDoc(
1005                            "Transaction cannot be rollbacked if it wasn't started.");
1006            }
1007         }
1008      }
1009      else
1010      {
1011         if (bCommit)
1012         {
1013            throw new IllegalStateException JavaDoc(
1014                         "Transaction cannot be commited if it wasn't started.");
1015         }
1016         else
1017         {
1018            throw new IllegalStateException JavaDoc(
1019                         "Transaction cannot be rollbacked if it wasn't started.");
1020         }
1021      }
1022   }
1023}
1024
Popular Tags