1 /***
2 * WrappedConnection.java
3 *
4 * This file is part of the creme library.
5 * The creme library intends to ease the development effort of large
6 * applications by providing easy to use support classes.
7 *
8 * Copyright (C) 2002 Denis Bregeon
9 *
10 *
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
15 *
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with this library; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 * contact information: dbregeon@sourceforge.net
26 */
27 package org.jcreme.sql;
28
29 import java.lang.reflect.Method;
30 import java.sql.CallableStatement;
31 import java.sql.Connection;
32 import java.sql.DatabaseMetaData;
33 import java.sql.PreparedStatement;
34 import java.sql.SQLException;
35 import java.sql.SQLWarning;
36 import java.sql.Savepoint;
37 import java.sql.Statement;
38 import java.util.Hashtable;
39 import java.util.Map;
40 import java.util.Vector;
41
42 /***
43 * This class enables to wrap an actual connection to provide a few extra
44 * services: access the active statements attached to the connection, handling
45 * of some exceptions (rollback, loss of connection). The statements provided by
46 * this class are wrapped around the actual statement.
47 *
48 * @author $Author: dbregeon $
49 * @version $Revision: 1.1 $
50 */
51 public class WrappedConnection implements Connection {
52 /***
53 * Name of the method to use in case of rollback.
54 */
55 protected static final String ROLLBACK_METHOD_NAME = "rollback";
56
57 /***
58 * Types of the parameters to the rollback method.
59 */
60 protected static final Class[] ROLLBACK_PARAMETERS = {};
61
62 /***
63 * Name of the method to use in case of loss of connection.
64 */
65 protected static final String LOSS_OF_CONNECTION_METHOD_NAME = "connectionExternallyClosed";
66
67 /***
68 * Types of the parameters to the loss of connection method.
69 */
70 protected static final Class[] LOSS_OF_CONNECTION_PARAMETERS = {};
71
72 /***
73 * The actual method to call in case of rollback.
74 */
75 private static Method rollbackMethod = null;
76
77 /***
78 * The actual method to call in case of loss of connection.
79 */
80 private static Method lossOfConnectionMethod = null;
81
82 /***
83 * This hashtable is used to store the WrappedConnection built. The key is
84 * the original connection.
85 */
86 private static final Hashtable builtConnections = new Hashtable();
87
88 /***
89 * The underlying connection.
90 */
91 private Connection realConnection = null;
92
93 /***
94 * The action that will be used by the exception handler in case of
95 * rollback.
96 */
97 private CremeAction rollBackAction = null;
98
99 /***
100 * The action that will be used by the exception handler in case of loss of
101 * connection.
102 */
103 private CremeAction lossOfConnectionAction = null;
104
105 /***
106 * The pool from which this connection originated if any.
107 */
108 private ConnectionPool parentPool = null;
109
110 /***
111 * The object that will handle the exceptions.
112 */
113 private SQLExceptionHandler exceptionHandler = null;
114
115 /***
116 * Flag that determines if the connection has been closed.
117 */
118 private boolean isClosed = false;
119
120 /***
121 * A list of the statements issued from this connection that are still
122 * active.
123 */
124 private final Vector activeStatements = new Vector();
125
126 /***
127 * Create the wrapping around a database Connection.
128 *
129 * @param connection
130 * the connection to wrap.
131 * @throws SQLException
132 * if connection is null.
133 */
134 protected WrappedConnection(Connection connection) throws SQLException {
135 if (connection == null) {
136 throw new SQLException("Null is not a valid Connection.");
137 }
138 try {
139 this.rollBackAction = new CremeAction(getRollbackMethod());
140 this.rollBackAction.setSubject(this);
141 } catch (IllegalArgumentException e) {
142 e.printStackTrace();
143 }
144 try {
145 this.lossOfConnectionAction = new CremeAction(
146 getLossOfConnectionMethod());
147 this.lossOfConnectionAction.setSubject(this);
148 } catch (IllegalArgumentException e) {
149 e.printStackTrace();
150 }
151 this.realConnection = connection;
152 builtConnections.put(connection, this);
153 }
154
155 /***
156 * This method enables to build or retrieve the WrappedConnection created
157 * from a given connection.
158 *
159 * @param connection
160 * the connection for which we want a wrapper.
161 * @return a WrappedConnection that wraps the given connection. null if
162 * connection is null.
163 */
164 public static WrappedConnection getInstance(Connection connection) {
165 if (connection == null) {
166 return null;
167 }
168 WrappedConnection conn = (WrappedConnection) builtConnections
169 .get(connection);
170 if (conn == null) {
171 try {
172 conn = new WrappedConnection(connection);
173 } catch (SQLException e) {
174 e.printStackTrace();
175 }
176 }
177 return conn;
178 }
179
180 /***
181 * Gives access to the method that is used to rollback in this class.
182 *
183 * @return the rollback method.
184 */
185 public static Method getRollbackMethod() {
186 try {
187 if (rollbackMethod == null) {
188 rollbackMethod = Connection.class.getMethod(
189 ROLLBACK_METHOD_NAME, ROLLBACK_PARAMETERS);
190 }
191 } catch (Exception e) {
192 e.printStackTrace();
193 }
194 return rollbackMethod;
195 }
196
197 /***
198 * Gives access to the method that is used to handle loss of connection in
199 * this class.
200 *
201 * @return the loss of connection method.
202 */
203 public static Method getLossOfConnectionMethod() {
204 try {
205 if (lossOfConnectionMethod == null) {
206 lossOfConnectionMethod = WrappedConnection.class.getMethod(
207 LOSS_OF_CONNECTION_METHOD_NAME,
208 LOSS_OF_CONNECTION_PARAMETERS);
209 }
210 } catch (Exception e) {
211 e.printStackTrace();
212 }
213 return lossOfConnectionMethod;
214 }
215
216 /***
217 * This method enabes to attach this WrappedConnection to a ConnectionPool.
218 * Being attached to such a pool modifies some of the behaviour of the
219 * Connection.
220 *
221 * @param pool
222 * the ConnectionPool to attach.
223 */
224 public void setParentPool(ConnectionPool pool) {
225 this.parentPool = pool;
226 }
227
228 /***
229 * Gives access to the eventual ConnectionPool to which this connection is
230 * attached.
231 *
232 * @return the parent pool of the Connection. Null if the connection is not
233 * attached to a pool.
234 */
235 public ConnectionPool getParentPool() {
236 return this.parentPool;
237 }
238
239 /***
240 * Sets the Exception Handler for this connection. The handler will be
241 * called upon anytime a SQLException is thrown in the Connection.
242 *
243 * @param handler
244 * the handler that will be used for all the SQLExceptions.
245 */
246 public void setExceptionHandler(SQLExceptionHandler handler) {
247 this.exceptionHandler = handler;
248 }
249
250 /***
251 * Gives access to the Exception handler that is used by this Connection.
252 *
253 * @return the SQLExceptionHandler used by this connection.
254 */
255 public SQLExceptionHandler getExceptionHandler() {
256 return this.exceptionHandler;
257 }
258
259 /***
260 * Gives access to the action that enables to rollback this Connection.
261 *
262 * @return the CremeAction that enables to rollback this Connection.
263 */
264 public CremeAction getRollBackAction() {
265 return this.rollBackAction;
266 }
267
268 /***
269 * Gives access to the action that enables to close this Connection in case
270 * of loss of connection.
271 *
272 * @return the CremeAction that enables to close this Connection.
273 */
274 public CremeAction getLossOfConnectionAction() {
275 return this.lossOfConnectionAction;
276 }
277
278 /***
279 * This method enables to actually close the connection when it is lost.
280 * When the Connection is attached to a Pool, the close method merely
281 * returns it to the ConnectionPool.
282 *
283 * @throws SQLException
284 * if an error occurs while closing the connection.
285 */
286 protected void connectionExternallyClosed() throws SQLException {
287 this.isClosed = true;
288 close();
289 }
290
291 /***
292 * This method enables the Connection to handle the exceptions. If an
293 * SQLExceptionHandler is defined it is called.
294 *
295 * @param e
296 * the SQLException to handle.
297 * @return the result provided by the exception handler (@see
298 * SQLExceptionHandler#handleException(SQLException, CremeAction,
299 * CremeAction))
300 */
301 protected int manageException(SQLException e) {
302 int result = 0;
303 if (this.exceptionHandler != null) {
304 result = this.exceptionHandler.handleException(e,
305 this.rollBackAction, this.lossOfConnectionAction);
306 }
307 return result;
308 }
309
310 /***
311 * This method enables the Connection to handle the exceptions. If an
312 * SQLExceptionHandler is defined it is called.
313 *
314 * @param e
315 * the SQLException to handle.
316 * @param redo
317 * the action that has provoked the exception. It will be called
318 * again if possible.
319 * @return the result provided by the exception handler (@see
320 * SQLExceptionHandler#handleException(SQLException, CremeAction,
321 * CremeAction, CremeAction))
322 */
323 protected int manageException(SQLException e, CremeAction redo) {
324 int result = 0;
325 if (this.exceptionHandler != null) {
326 result = this.exceptionHandler.handleException(e,
327 this.rollBackAction, this.lossOfConnectionAction, redo);
328 }
329 return result;
330 }
331
332 /***
333 * @see Connection#setCatalog(java.lang.String)
334 */
335 public void setCatalog(String s) throws SQLException {
336 try {
337 this.realConnection.setCatalog(s);
338 } catch (SQLException e) {
339 manageException(e);
340 throw e;
341 }
342 }
343
344 /***
345 * @see Connection#close()
346 */
347 public void close() throws SQLException {
348 try {
349 if (this.parentPool != null) {
350 this.parentPool.releaseConnection(this);
351 } else {
352 this.isClosed = true;
353 }
354
355
356
357 if (this.isClosed) {
358 this.realConnection.close();
359 }
360 } catch (SQLException e) {
361 manageException(e);
362 throw e;
363 }
364 }
365
366 /***
367 * @see Connection#clearWarnings()
368 */
369 public void clearWarnings() throws SQLException {
370 try {
371 this.realConnection.clearWarnings();
372 } catch (SQLException e) {
373 manageException(e);
374 throw e;
375 }
376 }
377
378 /***
379 * @see Connection#rollback()
380 */
381 public void rollback() throws SQLException {
382 try {
383 this.realConnection.rollback();
384 } catch (SQLException e) {
385 manageException(e);
386 throw e;
387 }
388 }
389
390 /***
391 * @see Connection#getTypeMap()
392 */
393 public Map getTypeMap() throws SQLException {
394 try {
395 return this.realConnection.getTypeMap();
396 } catch (SQLException e) {
397 manageException(e);
398 throw e;
399 }
400 }
401
402 /***
403 * @see Connection#getTransactionIsolation()
404 */
405 public int getTransactionIsolation() throws SQLException {
406 try {
407 return this.realConnection.getTransactionIsolation();
408 } catch (SQLException e) {
409 manageException(e);
410 throw e;
411 }
412 }
413
414 /***
415 * @see Connection#prepareStatement(java.lang.String)
416 */
417 public PreparedStatement prepareStatement(String sql) throws SQLException {
418 try {
419 return new WrappedPreparedStatement(this.realConnection
420 .prepareStatement(sql), sql);
421 } catch (SQLException e) {
422 manageException(e);
423 throw e;
424 }
425 }
426
427 /***
428 * @see Connection#setTransactionIsolation(int)
429 */
430 public void setTransactionIsolation(int i) throws SQLException {
431 try {
432 this.realConnection.setTransactionIsolation(i);
433 } catch (SQLException e) {
434 manageException(e);
435 throw e;
436 }
437 }
438
439 /***
440 * @see Connection#isClosed()
441 */
442 public boolean isClosed() throws SQLException {
443 try {
444 if (!this.isClosed) {
445 this.isClosed = this.realConnection.isClosed();
446 }
447 return this.isClosed;
448 } catch (SQLException e) {
449 manageException(e);
450 throw e;
451 }
452 }
453
454 /***
455 * @see Connection#createStatement()
456 */
457 public Statement createStatement() throws SQLException {
458 try {
459 return new WrappedStatement(this.realConnection.createStatement());
460 } catch (SQLException e) {
461 manageException(e);
462 throw e;
463 }
464 }
465
466 /***
467 * @see Connection#createStatement(int, int)
468 */
469 public Statement createStatement(int i, int j) throws SQLException {
470 try {
471 return new WrappedStatement(this.realConnection.createStatement(i,
472 j));
473 } catch (SQLException e) {
474 manageException(e);
475 throw e;
476 }
477 }
478
479 /***
480 * @see Connection#setAutoCommit(boolean)
481 */
482 public void setAutoCommit(boolean flag) throws SQLException {
483 try {
484 this.realConnection.setAutoCommit(flag);
485 } catch (SQLException e) {
486 manageException(e);
487 throw e;
488 }
489 }
490
491 /***
492 * @see Connection#prepareCall(java.lang.String, int, int)
493 */
494 public CallableStatement prepareCall(String sql, int i, int j)
495 throws SQLException {
496 try {
497 return new WrappedCallableStatement(this.realConnection
498 .prepareCall(sql, i, j), sql);
499 } catch (SQLException e) {
500 manageException(e);
501 throw e;
502 }
503 }
504
505 /***
506 * @see Connection#commit()
507 */
508 public void commit() throws SQLException {
509 try {
510 this.realConnection.commit();
511 } catch (SQLException e) {
512 manageException(e);
513 throw e;
514 }
515 }
516
517 /***
518 * @see Connection#getCatalog()
519 */
520 public String getCatalog() throws SQLException {
521 try {
522 return this.realConnection.getCatalog();
523 } catch (SQLException e) {
524 manageException(e);
525 throw e;
526 }
527 }
528
529 /***
530 * @see Connection#prepareStatement(java.lang.String, int, int)
531 */
532 public PreparedStatement prepareStatement(String sql, int i, int j)
533 throws SQLException {
534 try {
535 return new WrappedPreparedStatement(this.realConnection
536 .prepareStatement(sql, i, j), sql);
537 } catch (SQLException e) {
538 manageException(e);
539 throw e;
540 }
541 }
542
543 /***
544 * @see Connection#getMetaData()
545 */
546 public DatabaseMetaData getMetaData() throws SQLException {
547 try {
548 return this.realConnection.getMetaData();
549 } catch (SQLException e) {
550 manageException(e);
551 throw e;
552 }
553 }
554
555 /***
556 * @see Connection#getAutoCommit()
557 */
558 public boolean getAutoCommit() throws SQLException {
559 try {
560 return this.realConnection.getAutoCommit();
561 } catch (SQLException e) {
562 manageException(e);
563 throw e;
564 }
565 }
566
567 /***
568 * @see Connection#nativeSQL(java.lang.String)
569 */
570 public String nativeSQL(String sql) throws SQLException {
571 try {
572 return this.realConnection.nativeSQL(sql);
573 } catch (SQLException e) {
574 manageException(e);
575 throw e;
576 }
577 }
578
579 /***
580 * @see Connection#prepareCall(java.lang.String)
581 */
582 public CallableStatement prepareCall(String sql) throws SQLException {
583 try {
584 return new WrappedCallableStatement(this.realConnection
585 .prepareCall(sql), sql);
586 } catch (SQLException e) {
587 manageException(e);
588 throw e;
589 }
590 }
591
592 /***
593 * @see Connection#setTypeMap(java.util.Map)
594 */
595 public void setTypeMap(Map map) throws SQLException {
596 try {
597 this.realConnection.setTypeMap(map);
598 } catch (SQLException e) {
599 manageException(e);
600 throw e;
601 }
602 }
603
604 /***
605 * @see Connection#getWarnings()
606 */
607 public SQLWarning getWarnings() throws SQLException {
608 try {
609 return this.realConnection.getWarnings();
610 } catch (SQLException e) {
611 manageException(e);
612 throw e;
613 }
614 }
615
616 /***
617 * This method is called when a Statement is created from this Connection.
618 * It enables to keep track of the Statements that are attached to this
619 * Connection.
620 *
621 * @param statement
622 * the statement to attach to this Connection.
623 */
624 protected void registerActiveStatement(Statement statement) {
625 if ((statement != null) && (!this.activeStatements.contains(statement))) {
626 this.activeStatements.add(statement);
627 }
628 }
629
630 /***
631 * This method is called when a Statement created in this Connection is
632 * closed.
633 *
634 * @param statement
635 * the statement to unregister.
636 */
637 protected void unregisterActiveStatement(Statement statement) {
638 if (statement != null) {
639 this.activeStatements.remove(statement);
640 }
641 }
642
643 /***
644 * This method gives access to the statements that are currently registered
645 * in the Connection. These statements are currently active.
646 *
647 * @return the statements registered in the Connection.
648 */
649 public Statement[] getActiveStatements() {
650 return (Statement[]) this.activeStatements
651 .toArray(new Statement[this.activeStatements.size()]);
652 }
653
654 /***
655 * @see Connection#setHoldability(int)
656 */
657 public void setHoldability(int i) throws SQLException {
658 try {
659 this.realConnection.setHoldability(i);
660 } catch (SQLException e) {
661 manageException(e);
662 throw e;
663 }
664 }
665
666 /***
667 * @see Connection#getHoldability()
668 */
669 public int getHoldability() throws SQLException {
670 try {
671 return this.realConnection.getHoldability();
672 } catch (SQLException e) {
673 manageException(e);
674 throw e;
675 }
676 }
677
678 /***
679 * @see Connection#rollback(java.sql.Savepoint)
680 */
681 public void rollback(Savepoint savepoint) throws SQLException {
682 try {
683 this.realConnection.rollback(savepoint);
684 } catch (SQLException e) {
685 manageException(e);
686 throw e;
687 }
688 }
689
690 /***
691 * @see Connection#createStatement(int, int, int)
692 */
693 public Statement createStatement(int i, int j, int k) throws SQLException {
694 try {
695 return this.realConnection.createStatement(i, j, k);
696 } catch (SQLException e) {
697 manageException(e);
698 throw e;
699 }
700 }
701
702 /***
703 * @see Connection#prepareStatement(java.lang.String,int,int,int)
704 */
705 public PreparedStatement prepareStatement(String sql, int i, int j, int k)
706 throws SQLException {
707 try {
708 return this.realConnection.prepareStatement(sql, i, j, k);
709 } catch (SQLException e) {
710 manageException(e);
711 throw e;
712 }
713 }
714
715 /***
716 * @see Connection#prepareCall(java.lang.String, int, int, int)
717 */
718 public CallableStatement prepareCall(String sql, int i, int j, int k)
719 throws SQLException {
720 try {
721 return this.realConnection.prepareCall(sql, i, j, k);
722 } catch (SQLException e) {
723 manageException(e);
724 throw e;
725 }
726 }
727
728 /***
729 * @see Connection#prepareStatement(java.lang.String, int)
730 */
731 public PreparedStatement prepareStatement(String sql, int i)
732 throws SQLException {
733 try {
734 return this.realConnection.prepareStatement(sql, i);
735 } catch (SQLException e) {
736 manageException(e);
737 throw e;
738 }
739 }
740
741 /***
742 * @see Connection#prepareStatement(java.lang.String, int[])
743 */
744 public PreparedStatement prepareStatement(String sql, int[] i)
745 throws SQLException {
746 try {
747 return this.realConnection.prepareStatement(sql, i);
748 } catch (SQLException e) {
749 manageException(e);
750 throw e;
751 }
752 }
753
754 /***
755 * @see Connection#prepareStatement(java.lang.String, java.lang.String[])
756 */
757 public PreparedStatement prepareStatement(String sql, String[] s)
758 throws SQLException {
759 try {
760 return this.realConnection.prepareStatement(sql, s);
761 } catch (SQLException e) {
762 manageException(e);
763 throw e;
764 }
765 }
766
767 /***
768 * @see Connection#isReadOnly()
769 */
770 public boolean isReadOnly() throws SQLException {
771 try {
772 return this.realConnection.isReadOnly();
773 } catch (SQLException e) {
774 manageException(e);
775 throw e;
776 }
777 }
778
779 /***
780 * @see Connection#releaseSavepoint(java.sql.Savepoint)
781 */
782 public void releaseSavepoint(Savepoint savepoint) throws SQLException {
783 try {
784 this.realConnection.releaseSavepoint(savepoint);
785 } catch (SQLException e) {
786 manageException(e);
787 throw e;
788 }
789 }
790
791 /***
792 * @see Connection#setReadOnly(boolean)
793 */
794 public void setReadOnly(boolean readOnly) throws SQLException {
795 try {
796 this.realConnection.setReadOnly(readOnly);
797 } catch (SQLException e) {
798 manageException(e);
799 throw e;
800 }
801 }
802
803 /***
804 * @see Connection#setSavepoint()
805 */
806 public Savepoint setSavepoint() throws SQLException {
807 try {
808 return this.realConnection.setSavepoint();
809 } catch (SQLException e) {
810 manageException(e);
811 throw e;
812 }
813 }
814
815 /***
816 * @see Connection#setSavepoint(java.lang.String)
817 */
818 public Savepoint setSavepoint(String name) throws SQLException {
819 try {
820 return this.realConnection.setSavepoint(name);
821 } catch (SQLException e) {
822 manageException(e);
823 throw e;
824 }
825 }
826 }