KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > access > Transaction


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56 package org.objectstyle.cayenne.access;
57
58 import java.sql.Connection JavaDoc;
59 import java.sql.SQLException JavaDoc;
60 import java.util.ArrayList JavaDoc;
61 import java.util.Collection JavaDoc;
62 import java.util.Iterator JavaDoc;
63 import java.util.List JavaDoc;
64
65 import org.apache.log4j.Level;
66 import org.apache.log4j.Logger;
67 import org.objectstyle.cayenne.CayenneException;
68 import org.objectstyle.cayenne.CayenneRuntimeException;
69 import org.objectstyle.cayenne.query.QueryExecutionPlan;
70
71 /**
72  * Class responsible for transaction management within Cayenne.
73  *
74  * @author Andrei Adamchik
75  * @since 1.1
76  */

77 public abstract class Transaction {
78     private static final Logger logObj = Logger.getLogger(Transaction.class);
79     
80     private static final Transaction NO_TRANSACTION = new Transaction() {
81         public void begin() {
82
83         }
84
85         public void addConnection(Connection JavaDoc connection) {
86
87         }
88
89         public void commit() {
90
91         }
92
93         public void rollback() {
94
95         }
96     };
97
98     public static final int STATUS_ACTIVE = 1;
99     public static final int STATUS_COMMITTING = 2;
100     public static final int STATUS_COMMITTED = 3;
101     public static final int STATUS_ROLLEDBACK = 4;
102     public static final int STATUS_ROLLING_BACK = 5;
103     public static final int STATUS_NO_TRANSACTION = 6;
104     public static final int STATUS_MARKED_ROLLEDBACK = 7;
105
106     protected List JavaDoc connections;
107     protected int status;
108     protected TransactionDelegate delegate;
109     protected Level logLevel;
110
111     static String JavaDoc decodeStatus(int status) {
112         switch (status) {
113             case STATUS_ACTIVE :
114                 return "STATUS_ACTIVE";
115             case STATUS_COMMITTING :
116                 return "STATUS_COMMITTING";
117             case STATUS_COMMITTED :
118                 return "STATUS_COMMITTED";
119             case STATUS_ROLLEDBACK :
120                 return "STATUS_ROLLEDBACK";
121             case STATUS_ROLLING_BACK :
122                 return "STATUS_ROLLING_BACK";
123             case STATUS_NO_TRANSACTION :
124                 return "STATUS_NO_TRANSACTION";
125             case STATUS_MARKED_ROLLEDBACK :
126                 return "STATUS_MARKED_ROLLEDBACK";
127             default :
128                 return "Unknown Status - " + status;
129         }
130     }
131
132     /**
133      * Factory method returning a new transaction instance that would
134      * propagate commit/rollback to participating connections. Connections
135      * will be closed when commit or rollback is called.
136      */

137     public static Transaction internalTransaction(TransactionDelegate delegate) {
138         return new InternalTransaction(delegate);
139     }
140
141     /**
142      * Factory method returning a new transaction instance that would NOT
143      * propagate commit/rollback to participating connections. Connections
144      * will still be closed when commit or rollback is called.
145      */

146     public static Transaction externalTransaction(TransactionDelegate delegate) {
147         return new ExternalTransaction(delegate);
148     }
149
150     /**
151      * Factory method returning a transaction instance that does not alter the
152      * state of participating connections in any way. Commit and rollback methods
153      * do not do anything.
154      */

155     public static Transaction noTransaction() {
156         return NO_TRANSACTION;
157     }
158
159     /**
160       * Creates new inactive transaction.
161       */

162     protected Transaction() {
163         status = STATUS_NO_TRANSACTION;
164     }
165
166     /**
167      * Helper method that wraps a number of queries in this transaction,
168      * runs them, and commits or rolls back depending on
169      * the outcome. This method allows users to define their own custom
170      * Transactions and easily wrap Cayenne queries in them.
171      */

172     public void performQueries(
173         QueryEngine engine,
174         Collection JavaDoc queries,
175         OperationObserver observer)
176         throws CayenneRuntimeException {
177
178         try {
179             // implicit begin..
180
engine.performQueries(queries, observer, this);
181
182             // don't commit iterated queries - leave it up to the caller
183
// at the same time rollbacks of iterated queries must be processed here,
184
// since caller will no longer be processing stuff on exception
185
if (!observer.isIteratedResult()
186                 && (getStatus() == Transaction.STATUS_ACTIVE)) {
187                 commit();
188             }
189         }
190         catch (Exception JavaDoc ex) {
191             setRollbackOnly();
192
193             // must rethrow
194
if (ex instanceof CayenneRuntimeException) {
195                 throw (CayenneRuntimeException) ex;
196             }
197             else {
198                 throw new CayenneRuntimeException(ex);
199             }
200         }
201         finally {
202             if (getStatus() == Transaction.STATUS_MARKED_ROLLEDBACK) {
203                 try {
204                     rollback();
205                 }
206                 catch (Exception JavaDoc rollbackEx) {
207                 }
208             }
209         }
210     }
211     
212
213     /**
214      * Helper method that runs a query, wrapping it in this transaction, and then commits
215      * or rolls back depending on the outcome. Query can internally be a chain of multiple
216      * queries.
217      *
218      * @since 1.2
219      */

220     public void performQuery(
221             PersistenceContext context,
222             QueryExecutionPlan query,
223             OperationObserver observer)
224             throws CayenneRuntimeException {
225
226         try {
227             // implicit begin..
228
context.performQuery(query, observer, this);
229
230             // don't commit iterated queries - leave it up to the caller
231
// at the same time rollbacks of iterated queries must be processed here,
232
// since caller will no longer be processing stuff on exception
233
if (!observer.isIteratedResult()
234                     && (getStatus() == Transaction.STATUS_ACTIVE)) {
235                 commit();
236             }
237         }
238         catch (Exception JavaDoc ex) {
239             setRollbackOnly();
240
241             // must rethrow
242
if (ex instanceof CayenneRuntimeException) {
243                 throw (CayenneRuntimeException) ex;
244             }
245             else {
246                 throw new CayenneRuntimeException(ex);
247             }
248         }
249         finally {
250             if (getStatus() == Transaction.STATUS_MARKED_ROLLEDBACK) {
251                 try {
252                     rollback();
253                 }
254                 catch (Exception JavaDoc rollbackEx) {
255                 }
256             }
257         }
258     }
259
260     public Level getLogLevel() {
261         return logLevel != null ? logLevel : QueryLogger.DEFAULT_LOG_LEVEL;
262     }
263
264     public void setLogLevel(Level logLevel) {
265         this.logLevel = logLevel;
266     }
267
268     public TransactionDelegate getDelegate() {
269         return delegate;
270     }
271
272     public void setDelegate(TransactionDelegate delegate) {
273         this.delegate = delegate;
274     }
275
276     public int getStatus() {
277         return status;
278     }
279
280     public synchronized void setRollbackOnly() {
281         setStatus(STATUS_MARKED_ROLLEDBACK);
282     }
283
284     public synchronized void setStatus(int status) {
285         if (delegate != null
286             && status == STATUS_MARKED_ROLLEDBACK
287             && !delegate.willMarkAsRollbackOnly(this)) {
288             return;
289         }
290
291         this.status = status;
292     }
293
294     /**
295      * Starts a Transaction. If Transaction is not started explicitly,
296      * it will be started when the first connection is added.
297      */

298     public abstract void begin();
299
300     public abstract void addConnection(Connection JavaDoc connection)
301         throws IllegalStateException JavaDoc, SQLException JavaDoc, CayenneException;
302
303     public abstract void commit()
304         throws IllegalStateException JavaDoc, SQLException JavaDoc, CayenneException;
305
306     public abstract void rollback()
307         throws IllegalStateException JavaDoc, SQLException JavaDoc, CayenneException;
308
309     // transaction managed by container, or no transaction at all.
310
// this implementation will simply close the connections on commit
311
// or rollback, and notify the delegate
312
static class ExternalTransaction extends Transaction {
313         ExternalTransaction() {
314         }
315
316         ExternalTransaction(TransactionDelegate delegate) {
317             setDelegate(delegate);
318         }
319
320         public synchronized void begin() {
321             if (status != STATUS_NO_TRANSACTION) {
322                 throw new IllegalStateException JavaDoc(
323                     "Transaction must have 'STATUS_NO_TRANSACTION' to begin. "
324                         + "Current status: "
325                         + decodeStatus(status));
326             }
327
328             status = STATUS_ACTIVE;
329             // most Cayenne apps are single datanode,
330
// there will be few that have more than 2, esp. in a single tran
331
connections = new ArrayList JavaDoc(2);
332         }
333
334         public synchronized void addConnection(Connection JavaDoc connection)
335             throws IllegalStateException JavaDoc, SQLException JavaDoc, CayenneException {
336
337             // implicitly begin transaction
338
if (status == STATUS_NO_TRANSACTION) {
339                 begin();
340             }
341
342             if (delegate != null && !delegate.willAddConnection(this, connection)) {
343                 return;
344             }
345
346             if (status != STATUS_ACTIVE) {
347                 throw new IllegalStateException JavaDoc(
348                     "Transaction must have 'STATUS_ACTIVE' to add a connection. "
349                         + "Current status: "
350                         + decodeStatus(status));
351             }
352
353             if (!connections.contains(connection)) {
354                 // may need to fix connection properties
355
fixConnectionState(connection);
356                 connections.add(connection);
357             }
358         }
359
360         public void commit()
361             throws IllegalStateException JavaDoc, SQLException JavaDoc, CayenneException {
362
363             if (status == STATUS_NO_TRANSACTION) {
364                 return;
365             }
366
367             if (delegate != null && !delegate.willCommit(this)) {
368                 return;
369             }
370
371             try {
372                 if (status != STATUS_ACTIVE) {
373                     throw new IllegalStateException JavaDoc(
374                         "Transaction must have 'STATUS_ACTIVE' to be committed. "
375                             + "Current status: "
376                             + decodeStatus(status));
377                 }
378
379                 processCommit();
380
381                 status = STATUS_COMMITTED;
382                 if (delegate != null) {
383                     delegate.didCommit(this);
384                 }
385             }
386             finally {
387                 close();
388             }
389         }
390
391         public void rollback()
392             throws IllegalStateException JavaDoc, SQLException JavaDoc, CayenneException {
393
394             if (status == STATUS_NO_TRANSACTION
395                 || status == STATUS_ROLLEDBACK
396                 || status == STATUS_ROLLING_BACK) {
397                 return;
398             }
399
400             if (delegate != null && !delegate.willRollback(this)) {
401                 return;
402             }
403
404             try {
405                 if (status != STATUS_ACTIVE) {
406                     throw new IllegalStateException JavaDoc(
407                         "Transaction must have 'STATUS_ACTIVE' to be rolled back. "
408                             + "Current status: "
409                             + decodeStatus(status));
410                 }
411
412                 processRollback();
413
414                 status = STATUS_ROLLEDBACK;
415                 if (delegate != null) {
416                     delegate.didRollback(this);
417                 }
418             }
419             finally {
420                 close();
421             }
422         }
423
424         protected void fixConnectionState(Connection JavaDoc connection) throws SQLException JavaDoc {
425             // NOOP
426
}
427
428         protected void processCommit() throws SQLException JavaDoc, CayenneException {
429             QueryLogger.logCommitTransaction(
430                 getLogLevel(),
431                 "no commit - transaction controlled externally.");
432         }
433
434         protected void processRollback() throws SQLException JavaDoc, CayenneException {
435             QueryLogger.logRollbackTransaction(
436                 getLogLevel(),
437                 "no rollback - transaction controlled externally.");
438         }
439
440         /**
441          * Closes all connections associated with transaction.
442          */

443         protected void close() {
444             if (connections == null || connections.isEmpty()) {
445                 return;
446             }
447
448             Iterator JavaDoc it = connections.iterator();
449             while (it.hasNext()) {
450                 try {
451
452                     ((Connection JavaDoc) it.next()).close();
453                 }
454                 catch (Throwable JavaDoc th) {
455                     // TODO: chain exceptions...
456
// ignore for now
457
}
458             }
459         }
460     }
461
462     // transaction managed by Cayenne
463
static class InternalTransaction extends ExternalTransaction {
464
465         InternalTransaction(TransactionDelegate delegate) {
466             super(delegate);
467         }
468
469         public void begin() {
470             super.begin();
471             QueryLogger.logBeginTransaction(getLogLevel(), "transaction started.");
472         }
473
474         protected void fixConnectionState(Connection JavaDoc connection) throws SQLException JavaDoc {
475             if (connection.getAutoCommit()) {
476                 // some DBs are very particular about that, (e.g. Informix SE 7.0 per
477
// CAY-179), so do a try-catch and ignore exception
478

479                 // TODO: maybe allow adapter to provide transaction instance?
480

481                 try {
482                     connection.setAutoCommit(false);
483                 }
484                 catch (SQLException JavaDoc cay179Ex) {
485                     logObj.info("Can't set connection autocommit to false, ignoring. Message: " + cay179Ex.getMessage());
486                 }
487             }
488         }
489
490         protected void processCommit() throws SQLException JavaDoc, CayenneException {
491             status = STATUS_COMMITTING;
492
493             if (connections != null && connections.size() > 0) {
494                 Throwable JavaDoc deferredException = null;
495                 Iterator JavaDoc it = connections.iterator();
496                 while (it.hasNext()) {
497                     Connection JavaDoc connection = (Connection JavaDoc) it.next();
498                     try {
499
500                         if (deferredException == null) {
501                             connection.commit();
502                         }
503                         else {
504                             // we must do a partial rollback if only to cleanup uncommitted
505
// connections.
506
connection.rollback();
507                         }
508
509                     }
510                     catch (Throwable JavaDoc th) {
511                         // there is no such thing as "partial" rollback in real transactions,
512
// so we can't set any meaningful status.
513
// status = ?;
514
setRollbackOnly();
515
516                         // stores last exception
517
// TODO: chain exceptions...
518
deferredException = th;
519                     }
520                 }
521
522                 if (deferredException != null) {
523                     QueryLogger.logRollbackTransaction(
524                         getLogLevel(),
525                         "transaction rolledback.");
526                     if (deferredException instanceof SQLException JavaDoc) {
527                         throw (SQLException JavaDoc) deferredException;
528                     }
529                     else {
530                         throw new CayenneException(deferredException);
531                     }
532                 }
533                 else {
534                     QueryLogger.logCommitTransaction(
535                         getLogLevel(),
536                         "transaction committed.");
537                 }
538             }
539         }
540
541         protected void processRollback() throws SQLException JavaDoc, CayenneException {
542             status = STATUS_ROLLING_BACK;
543
544             if (connections != null && connections.size() > 0) {
545                 Throwable JavaDoc deferredException = null;
546
547                 Iterator JavaDoc it = connections.iterator();
548                 while (it.hasNext()) {
549                     Connection JavaDoc connection = (Connection JavaDoc) it.next();
550
551                     try {
552                         // continue with rollback even if an exception was thrown before
553
connection.rollback();
554                     }
555                     catch (Throwable JavaDoc th) {
556                         // stores last exception
557
// TODO: chain exceptions...
558
deferredException = th;
559                     }
560                 }
561
562                 if (deferredException != null) {
563                     if (deferredException instanceof SQLException JavaDoc) {
564                         throw (SQLException JavaDoc) deferredException;
565                     }
566                     else {
567                         throw new CayenneException(deferredException);
568                     }
569                 }
570             }
571         }
572     }
573 }
574
Popular Tags