View Javadoc

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             // Enables to keep the same attitude in case of multiple
355             // close. For instance Sybase throws an SQLException when
356             // a Connection is closed more than once.
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 }