KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > logicalcobwebs > proxool > ConnectionResetter


1 /*
2  * This software is released under a licence similar to the Apache Software Licence.
3  * See org.logicalcobwebs.proxool.package.html for details.
4  * The latest version is available at http://proxool.sourceforge.net
5  */

6 package org.logicalcobwebs.proxool;
7
8 import org.apache.commons.logging.Log;
9 import org.apache.commons.logging.Log;
10
11 import java.lang.reflect.Method JavaDoc;
12 import java.sql.Connection JavaDoc;
13 import java.sql.SQLException JavaDoc;
14 import java.util.HashMap JavaDoc;
15 import java.util.HashSet JavaDoc;
16 import java.util.Iterator JavaDoc;
17 import java.util.Map JavaDoc;
18 import java.util.Set JavaDoc;
19
20 /**
21  * Responsible for resetting a Connection to its default state when it is
22  * returned to the pool. It must be initialised by the first Connection that
23  * is made (for each pool) so that we don't make any assumptions about
24  * what the default values are.
25  *
26  * @version $Revision: 1.16 $, $Date: 2006/01/18 14:40:01 $
27  * @author Bill Horsman (bill@logicalcobwebs.co.uk)
28  * @author $Author: billhorsman $ (current maintainer)
29  * @since Proxool 0.5
30  */

31 public class ConnectionResetter {
32
33     private Log log;
34
35     /**
36      * @see #initialise
37      */

38     private boolean initialised;
39
40     /**
41      * @see #addReset
42      * @see #reset
43      */

44     private Map JavaDoc accessorMutatorMap = new HashMap JavaDoc();
45
46     /**
47      * @see #addReset
48      * @see #reset
49      */

50     private Map JavaDoc defaultValues = new HashMap JavaDoc();
51
52     /**
53      * We use this to guess if we are changing a property that will need resetting
54      */

55     protected static final String JavaDoc MUTATOR_PREFIX = "set";
56
57     private String JavaDoc driverName;
58
59     /**
60      * @see #isTriggerResetException()
61      */

62     protected static boolean triggerResetException;
63
64     /**
65      * Pass in the log to use
66      * @param log debug information sent here
67      */

68     protected ConnectionResetter(Log log, String JavaDoc driverName) {
69         this.log = log;
70         this.driverName = driverName;
71
72         // Map all the reset methods
73
addReset("getCatalog", "setCatalog");
74         addReset("isReadOnly", "setReadOnly");
75         addReset("getTransactionIsolation", "setTransactionIsolation");
76         addReset("getTypeMap", "setTypeMap");
77         addReset("getHoldability", "setHoldability");
78     }
79
80     /**
81      * Add a pair of methods that need resetting each time a connection is
82      * put back in the pool
83      * @param accessorName the name of the "getter" method (e.g. getAutoCommit)
84      * @param mutatorName teh name of the "setter" method (e.g. setAutoCommit)
85      */

86     private void addReset(String JavaDoc accessorName, String JavaDoc mutatorName) {
87
88         try {
89
90             Method JavaDoc accessor = null;
91             Method JavaDoc mutator = null;
92
93             Method JavaDoc[] methods = Connection JavaDoc.class.getMethods();
94             for (int i = 0; i < methods.length; i++) {
95                 Method JavaDoc method = methods[i];
96                 if (method.getName().equals(accessorName)) {
97                     if (accessor == null) {
98                         accessor = method;
99                     } else {
100                         log.info("Skipping ambiguous reset method " + accessorName);
101                         return;
102                     }
103                 }
104                 if (method.getName().equals(mutatorName)) {
105                     if (mutator == null) {
106                         mutator = method;
107                     } else {
108                         log.info("Skipping ambiguous reset method " + mutatorName);
109                         return;
110                     }
111                 }
112             }
113
114             if (accessor == null) {
115                 log.debug("Ignoring attempt to map reset method " + accessorName + " (probably because it isn't implemented in this JDK)");
116             } else if (mutator == null) {
117                 log.debug("Ignoring attempt to map reset method " + mutatorName + " (probably because it isn't implemented in this JDK)");
118             } else if (accessorMutatorMap.containsKey(accessor)) {
119                 log.warn("Ignoring attempt to map duplicate reset method " + accessorName);
120             } else if (accessorMutatorMap.containsValue(mutator)) {
121                 log.warn("Ignoring attempt to map duplicate reset method " + mutatorName);
122             } else {
123
124                 if (mutatorName.indexOf(MUTATOR_PREFIX) != 0) {
125                     log.warn("Resetter mutator " + mutatorName + " does not start with " + MUTATOR_PREFIX
126                             + " as expected. Proxool maynot recognise that a reset is necessary.");
127                 }
128
129                 if (accessor.getParameterTypes().length > 0) {
130                     log.info("Ignoring attempt to map accessor method " + accessorName + ". It must have no arguments.");
131                 } else if (mutator.getParameterTypes().length != 1) {
132                     log.info("Ignoring attempt to map mutator method " + mutatorName
133                             + ". It must have exactly one argument, not " + mutator.getParameterTypes().length);
134                 } else {
135                     accessorMutatorMap.put(accessor, mutator);
136                 }
137             }
138         } catch (Exception JavaDoc e) {
139             log.error("Problem mapping " + accessorName + " and " + mutatorName, e);
140         }
141
142     }
143
144     /**
145      * This gets called every time we make a Connection. Not that often
146      * really, so it's ok to synchronize a bit.
147      * @param connection this will be used to get all the default values
148      */

149     protected void initialise(Connection JavaDoc connection) {
150         if (!initialised) {
151             synchronized (this) {
152                 if (!initialised) {
153
154                     Set JavaDoc accessorsToRemove = new HashSet JavaDoc();
155                     Iterator JavaDoc i = accessorMutatorMap.keySet().iterator();
156                     while (i.hasNext()) {
157                         Method JavaDoc accessor = (Method JavaDoc) i.next();
158                         Method JavaDoc mutator = (Method JavaDoc) accessorMutatorMap.get(accessor);
159                         Object JavaDoc value = null;
160                         try {
161                             value = accessor.invoke(connection, null);
162                             // It's perfectly ok for the default value to be null, we just
163
// don't want to add it to the map.
164
if (value != null) {
165                                 defaultValues.put(mutator, value);
166                             }
167                             if (log.isDebugEnabled()) {
168                                 log.debug("Remembering default value: " + accessor.getName() + "() = " + value);
169                             }
170
171                         } catch (Throwable JavaDoc t) {
172                             log.debug(driverName + " does not support " + accessor.getName() + ". Proxool doesn't mind.");
173                             // We will remove this later (to avoid ConcurrentModifcation)
174
accessorsToRemove.add(accessor);
175                         }
176
177                         // Just test that the mutator works too. Otherwise it's going to fall over
178
// everytime we close a connection
179
try {
180                             Object JavaDoc[] args = {value};
181                             mutator.invoke(connection, args);
182                         } catch (Throwable JavaDoc t) {
183                             log.debug(driverName + " does not support " + mutator.getName() + ". Proxool doesn't mind.");
184                             // We will remove this later (to avoid ConcurrentModifcation)
185
accessorsToRemove.add(accessor);
186                         }
187
188                     }
189
190                     // Remove all the reset methods that we had trouble configuring
191
Iterator JavaDoc j = accessorsToRemove.iterator();
192                     while (j.hasNext()) {
193                         Method JavaDoc accessor = (Method JavaDoc) j.next();
194                         Method JavaDoc mutator = (Method JavaDoc) accessorMutatorMap.get(accessor);
195                         accessorMutatorMap.remove(accessor);
196                         defaultValues.remove(mutator);
197                     }
198
199                     initialised = true;
200                 }
201             }
202         }
203     }
204
205     /**
206      * Reset this connection to its default values. If anything goes wrong, it is logged
207      * as a warning or info but it silently continues.
208      * @param connection to be reset
209      * @param id used in log messages
210      * @return true if the reset was error free, or false if it encountered errors. (in which case it should probably not be reused)
211      */

212     protected boolean reset(Connection JavaDoc connection, String JavaDoc id) {
213         boolean errorsEncountered = false;
214
215         try {
216             connection.clearWarnings();
217         } catch (SQLException JavaDoc e) {
218             errorsEncountered = true;
219             log.warn(id + " - Problem calling connection.clearWarnings()", e);
220         }
221
222         // Let's see the state of autoCommit. It will help us give better advice in the log messages
223
boolean autoCommit = true;
224         try {
225             autoCommit = connection.getAutoCommit();
226         } catch (SQLException JavaDoc e) {
227             errorsEncountered = true;
228             log.warn(id + " - Problem calling connection.getAutoCommit()", e);
229         }
230
231 /*
232          Automatically rollback if autocommit is off. If there are no pending
233          transactions then this will have no effect.
234
235          From Database Language SQL (Proposed revised text of DIS 9075),
236         July 1992 - http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
237
238             "The execution of a <rollback statement> may be initiated implicitly by
239             an implementation when it detects unrecoverable errors. When such an
240             error occurs, an exception condition is raised: transaction rollback
241             with an implementation-defined subclass code".
242
243 */

244         if (!autoCommit) {
245             try {
246                 connection.rollback();
247             } catch (SQLException JavaDoc e) {
248                 log.error("Unexpected exception whilst calling rollback during connection reset", e);
249             }
250         }
251
252         // Now let's reset each property in turn. With a bit of luck, if there is a
253
// transaction pending then setting one of these properties will throw an
254
// exception (e.g. "operation not possible when transaction is in progress"
255
// or something). We want to know about transactions that are pending.
256
// It doesn't seem like a very good idea to close a connection with
257
// pending transactions.
258
Iterator JavaDoc i = accessorMutatorMap.keySet().iterator();
259         while (i.hasNext()) {
260             Method JavaDoc accessor = (Method JavaDoc) i.next();
261             Method JavaDoc mutator = (Method JavaDoc) accessorMutatorMap.get(accessor);
262             Object JavaDoc[] args = {defaultValues.get(mutator)};
263             try {
264                 Object JavaDoc currentValue = accessor.invoke(connection, null);
265                 if (currentValue == null && args[0] == null) {
266                     // Nothing to do then
267
} else if (currentValue.equals(args[0])) {
268                     // Nothing to do here either
269
} else {
270                     mutator.invoke(connection, args);
271                     if (log.isDebugEnabled()) {
272                         log.debug(id + " - Reset: " + mutator.getName() + "(" + args[0] + ") from " + currentValue);
273                     }
274                 }
275             } catch (Throwable JavaDoc t) {
276                 errorsEncountered = true;
277                 if (log.isDebugEnabled()) {
278                     log.debug(id + " - Problem resetting: " + mutator.getName() + "(" + args[0] + ").", t);
279                 }
280             }
281         }
282
283         // Finally. reset autoCommit.
284
if (!autoCommit) {
285             try {
286                 // Setting autoCommit to true might well commit all pending
287
// transactions. But that's beyond our control.
288
connection.setAutoCommit(true);
289                 log.debug(id + " - autoCommit reset back to true");
290             } catch (Throwable JavaDoc t) {
291                 errorsEncountered = true;
292                 log.warn(id + " - Problem calling connection.commit() or connection.setAutoCommit(true)", t);
293             }
294         }
295
296         if (isTriggerResetException()) {
297             log.warn("Triggering pretend exception during reset");
298             errorsEncountered = true;
299         }
300
301         if (errorsEncountered) {
302
303             log.warn(id + " - There were some problems resetting the connection (see debug output for details). It will not be used again "
304                     + "(just in case). The thread that is responsible is named '" + Thread.currentThread().getName() + "'");
305             if (!autoCommit) {
306                 log.warn(id + " - The connection was closed with autoCommit=false. That is fine, but it might indicate that "
307                         + "the problems that happened whilst trying to reset it were because a transaction is still in progress.");
308             }
309         }
310
311         return !errorsEncountered;
312     }
313
314     private static boolean isTriggerResetException() {
315         return triggerResetException;
316     }
317
318     /**
319      * Called by a unit test.
320      * @param triggerResetException true it we should trigger a pretend exception.
321      * @see #isTriggerResetException()
322      */

323     protected static void setTriggerResetException(boolean triggerResetException) {
324         ConnectionResetter.triggerResetException = triggerResetException;
325     }
326 }
327
328 /*
329  Revision history:
330  $Log: ConnectionResetter.java,v $
331  Revision 1.16 2006/01/18 14:40:01 billhorsman
332  Unbundled Jakarta's Commons Logging.
333
334  Revision 1.15 2005/10/07 08:21:53 billhorsman
335  New hook to allow unit tests to trigger a deliberate exception during reset
336
337  Revision 1.14 2003/03/10 23:43:10 billhorsman
338  reapplied checkstyle that i'd inadvertently let
339  IntelliJ change...
340
341  Revision 1.13 2003/03/10 15:26:46 billhorsman
342  refactoringn of concurrency stuff (and some import
343  optimisation)
344
345  Revision 1.12 2003/03/03 11:11:57 billhorsman
346  fixed licence
347
348  Revision 1.11 2003/02/06 17:41:04 billhorsman
349  now uses imported logging
350
351  Revision 1.10 2003/01/07 17:21:11 billhorsman
352  If autoCommit is off, all connections are rollbacked.
353
354  Revision 1.9 2002/11/13 20:53:16 billhorsman
355  now checks to see whether is necessary for each property (better logging)
356
357  Revision 1.8 2002/11/13 18:27:59 billhorsman
358  rethink. committing automatically is bad. so now we just
359  set autoCommit back to true (which might commit anyway but
360  that's down to the driver). we do the autoCommit last so
361  that some of the other resets might throw an exception if
362  there was a pending transaction. (Throwing an exception is
363  good - pending transactions are bad.)
364
365  Revision 1.7 2002/11/12 21:12:21 billhorsman
366  automatically calls clearWarnings too
367
368  Revision 1.6 2002/11/12 21:10:41 billhorsman
369  Hmm. Now commits any pending transactions automatically when
370  you close the connection. I'm still pondering whether this is
371  wise or not. The only other sensible option is to rollback
372  since I can't find a way of determining whether either is
373  necessary.
374
375  Revision 1.5 2002/11/12 20:24:12 billhorsman
376  checkstyle
377
378  Revision 1.4 2002/11/12 20:18:23 billhorsman
379  Made connection resetter a bit more friendly. Now, if it encounters any problems during
380  reset then that connection is thrown away. This is going to cause you problems if you
381  always close connections in an unstable state (e.g. with transactions open. But then
382  again, it's better to know about that as soon as possible, right?
383
384  Revision 1.3 2002/11/07 18:55:40 billhorsman
385  demoted log message from info to debug
386
387  Revision 1.2 2002/11/07 12:38:04 billhorsman
388  performance improvement - only reset when it might be necessary
389
390  Revision 1.1 2002/11/06 20:25:08 billhorsman
391  New class responsible for resetting connections when
392  they are returned to the pool.
393
394 */

395
Popular Tags