View Javadoc

1   /***
2    * DefaultSQLExceptionHandler.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.InvocationTargetException;
30  import java.sql.SQLException;
31  
32  /***
33   * This generic class enables to handle SQLExceptions. The class is able to
34   * handle differently Losses of Connection and Dead locks.
35   */
36  public class DefaultSQLExceptionHandler implements SQLExceptionHandler {
37      /***
38       * As specified in SQL92.
39       */
40      public static final String SQL92_CONNECTION_ERROR_SQLSTATE_CLASS = "08";
41  
42      /***
43       * As specified in SQL92.
44       */
45      public static final String SQL92_SERIALIZATION_FAILURE = "40001";
46  
47      /***
48       * This flag to prevent from recursive exception handling in case of loss of
49       * connection.
50       */
51      private boolean handlingInProgress = false;
52  
53      /***
54       * The number of attempts to retry to escape a Deadlock.
55       */
56      private int numberOfTries = 10;
57  
58      /***
59       * The time to wait between each attempt to escape a deadlock.
60       */
61      private int deadlockTimeout = 10;
62  
63      /***
64       * This method handles an SQLException. There is no special handling of
65       * deadlocks.
66       * 
67       * @param e
68       *            the exception to handle.
69       * @return the type of the exception e.
70       */
71      public int handleException(SQLException e) {
72          int i = 0;
73          if (e != null) {
74              i = EXCEPTION_TYPE_UNKNOWN;
75              if (isLossOfConnection(e)) {
76                  i = EXCEPTION_TYPE_LOSS_OF_CONNECTION;
77              }
78              if (isRolledBackDeadLock(e)) {
79                  i = EXCEPTION_TYPE_ROLLEDBACK_DEADLOCK;
80              }
81              if (isPlainDeadLock(e)) {
82                  i = EXCEPTION_TYPE_PLAIN_DEADLOCK;
83              }
84              i |= handleException(e.getNextException());
85          }
86          return i;
87      }
88  
89      /***
90       * This method handles an SQLException. The rollback action is invoked if
91       * the exception is a rolled back deadlock.
92       * 
93       * @param e
94       *            the exception to handle.
95       * @param rollback
96       *            the action to invoke to rollback.
97       * @param lossOfConnection
98       *            the action to invoke in case of a loss of connection.
99       * @return the type of the exception e.
100      */
101     public int handleException(SQLException e, CremeAction rollback,
102             CremeAction lossOfConnection) {
103         int i = 0;
104         if (e != null) {
105             i = EXCEPTION_TYPE_UNKNOWN;
106             if (isLossOfConnection(e)) {
107                 i = EXCEPTION_TYPE_LOSS_OF_CONNECTION;
108                 if (lossOfConnection != null) {
109                     i |= handleConnectionClosed(lossOfConnection);
110                 }
111             }
112             if (isRolledBackDeadLock(e)) {
113                 i = EXCEPTION_TYPE_ROLLEDBACK_DEADLOCK;
114                 if (rollback != null) {
115                     i |= handleRolledBackDeadLock(rollback);
116                 }
117             }
118             if (isPlainDeadLock(e)) {
119                 i = EXCEPTION_TYPE_PLAIN_DEADLOCK;
120             }
121             i |= handleException(e.getNextException(), rollback,
122                     lossOfConnection);
123         }
124         return i;
125     }
126 
127     /***
128      * This method handles an SQLException. The rollback action is invoked if
129      * the exception is a rolled back deadlock. The redo action is invoked if
130      * the exception is a simple deadlock.
131      * 
132      * @param e
133      *            the exception to handle.
134      * @param rollback
135      *            the action to invoke to rollback.
136      * @param lossOfConnection
137      *            the action to invoke in case of a loss of connection.
138      * @param redo
139      *            the action to invoke in case of a simple deadlock.
140      * @return the type of the exception e.
141      */
142     public int handleException(SQLException e, CremeAction rollback,
143             CremeAction lossOfConnection, CremeAction redo) {
144         if (redo == null) {
145             return handleException(e, rollback, lossOfConnection);
146         }
147         int i = 0;
148         if (e != null) {
149             i = EXCEPTION_TYPE_UNKNOWN;
150             if (isLossOfConnection(e)) {
151                 i = EXCEPTION_TYPE_LOSS_OF_CONNECTION;
152                 i |= handleConnectionClosed(lossOfConnection);
153             }
154             if (isRolledBackDeadLock(e)) {
155                 i = EXCEPTION_TYPE_ROLLEDBACK_DEADLOCK;
156                 i |= handleRolledBackDeadLock(rollback);
157             }
158             if (isPlainDeadLock(e)) {
159                 i = EXCEPTION_TYPE_PLAIN_DEADLOCK;
160                 i |= handlePlainDeadLock(redo);
161             }
162             i |= handleException(e.getNextException(), rollback,
163                     lossOfConnection, redo);
164         }
165         return i;
166     }
167 
168     /***
169      * This method enables to execute when the connection is closed.
170      * 
171      * @param lossOfConnection
172      *            the action to apply when the connection is closed.
173      * @return WAS_HANDLED if the action succeeded, 0 otherwise.
174      */
175     protected synchronized int handleConnectionClosed(
176             CremeAction lossOfConnection) {
177         int result = 0;
178         if ((!this.handlingInProgress) && (lossOfConnection != null)) {
179             this.handlingInProgress = true;
180             try {
181                 if (lossOfConnection.runAction()) {
182                     result = WAS_HANDLED;
183                 }
184             } catch (InvocationTargetException e) {
185                 e.printStackTrace();
186             }
187             this.handlingInProgress = false;
188         }
189         return result;
190     }
191 
192     /***
193      * This method enables to execute the rollback.
194      * 
195      * @param rollback
196      *            the action to apply when a rollback occured.
197      * @return WAS_HANDLED if the action succeeded, 0 otherwise.
198      */
199     protected int handleRolledBackDeadLock(CremeAction rollback) {
200         int result = 0;
201         try {
202             if ((rollback != null) && (rollback.runAction())) {
203                 result = WAS_HANDLED;
204             }
205         } catch (InvocationTargetException e) {
206             e.printStackTrace();
207         }
208         return result;
209     }
210 
211     /***
212      * This method enables to apply the redo action for numberOfTries times
213      * until it succeeds. Each attempt is preceded by a waiting period of
214      * deadlockTimeout milliseconds.
215      * 
216      * @param redo
217      *            the action to repeat.
218      * @return WAS_HANDLED if the action succeeded, 0 otherwise.
219      */
220     protected int handlePlainDeadLock(CremeAction redo) {
221         int i = 0;
222         int result = 0;
223         while ((redo != null) && (result == 0) && (i < this.numberOfTries)) {
224             try {
225                 Thread.sleep(this.deadlockTimeout);
226                 if (redo.runAction()) {
227                     result = WAS_HANDLED;
228                 }
229             } catch (Exception e) {
230                 // An exception occured that is not related to SQL at all !
231                 if (!((e instanceof SQLException) || ((e instanceof InvocationTargetException) && (((InvocationTargetException) e)
232                         .getTargetException() instanceof SQLException)))) {
233                     i = this.numberOfTries;
234                 }
235             }
236             i++;
237         }
238         return result;
239     }
240 
241     /***
242      * This method can be used to test an SQLException.
243      * 
244      * @param e
245      *            an SQLException to test.
246      * @return true if the exception is a loss of connection, false otherwise.
247      */
248     public boolean isLossOfConnection(SQLException e) {
249         String state = e.getSQLState();
250         return (state != null)
251                 && (state.startsWith(SQL92_CONNECTION_ERROR_SQLSTATE_CLASS));
252     }
253 
254     /***
255      * This method can be used to test an SQLException.
256      * 
257      * @param e
258      *            an SQLException to test.
259      * @return true if the exception is a deadlock and the transaction was
260      *         rolled back, false otherwise.
261      */
262     public boolean isRolledBackDeadLock(SQLException e) {
263         String state = e.getSQLState();
264         return ((state != null) && (state.equals(SQL92_SERIALIZATION_FAILURE)));
265     }
266 
267     /***
268      * This method can be used to test an SQLException.
269      * 
270      * @param e
271      *            an SQLException to test.
272      * @return true if the exception is a simple deadlock, false otherwise.
273      */
274     public boolean isPlainDeadLock(SQLException e) {
275         return false;
276     }
277 
278     /***
279      * This method can be used to test a result returned by a handleException
280      * method call.
281      * 
282      * @param i
283      *            an int produced by a handleException method call.
284      * @return true if the exception was handled, false otherwise.
285      */
286     public final boolean wasHandled(int i) {
287         return ((i & WAS_HANDLED) != 0);
288     }
289 
290     /***
291      * This method can be used to test a result returned by a handleException
292      * method call.
293      * 
294      * @param i
295      *            an int produced by a handleException method call.
296      * @return true if the type of the exception is not known, false otherwise.
297      */
298     public final boolean isUnknown(int i) {
299         return ((i & EXCEPTION_TYPE_UNKNOWN) != 0);
300     }
301 
302     /***
303      * This method can be used to test a result returned by a handleException
304      * method call.
305      * 
306      * @param i
307      *            an int produced by a handleException method call.
308      * @return true if the exception is a loss of connection, false otherwise.
309      */
310     public final boolean isLossOfConnection(int i) {
311         return ((i & EXCEPTION_TYPE_LOSS_OF_CONNECTION) != 0);
312     }
313 
314     /***
315      * This method can be used to test a result returned by a handleException
316      * method call.
317      * 
318      * @param i
319      *            an int produced by a handleException method call.
320      * @return true if the exception is a deadlock and the transaction was
321      *         rolled back, false otherwise.
322      */
323     public final boolean isRolledBackDeadLock(int i) {
324         return ((i & EXCEPTION_TYPE_ROLLEDBACK_DEADLOCK) != 0);
325     }
326 
327     /***
328      * This method can be used to test a result returned by a handleException
329      * method call.
330      * 
331      * @param i
332      *            an int produced by a handleException method call.
333      * @return true if the exception is a simple deadlock, false otherwise.
334      */
335     public final boolean isPlainDeadLock(int i) {
336         return ((i & EXCEPTION_TYPE_PLAIN_DEADLOCK) != 0);
337     }
338 
339     /***
340      * This method can be used to test an SQLException.
341      * 
342      * @param e
343      *            an SQLException to test.
344      * @return true if the exception is a deadlock (simple or not), false
345      *         otherwise.
346      */
347     public final boolean isDeadLock(SQLException e) {
348         return (isRolledBackDeadLock(e)) || (isPlainDeadLock(e));
349     }
350 
351     /***
352      * This method gives access to the number of milliseconds that the thread
353      * will wait for before making a new attempt to resolve a deadlock.
354      * 
355      * @return the number of milliseconds that the thread will wait for before
356      *         making a new attempt to resolve a deadlock.
357      */
358     public int getDeadlockTimeout() {
359         return this.deadlockTimeout;
360     }
361 
362     /***
363      * This method gives access to the number of attempts that will be done to
364      * resolve a deadlock (number of calls to the redo action).
365      * 
366      * @return the number of attempts that will be done to resolve a deadlock.
367      */
368     public int getNumberOfTries() {
369         return this.numberOfTries;
370     }
371 
372     /***
373      * This method enables to change the number of milliseconds that the thread
374      * will wait for before making a new attempt to resolve a deadlock.
375      * 
376      * @param timeout
377      *            the number of milliseconds that the thread will wait for
378      *            before making a new attempt to resolve a deadlock.
379      */
380     public void setDeadlockTimeout(int timeout) {
381         this.deadlockTimeout = timeout;
382     }
383 
384     /***
385      * This method enables to change the number of tries attempts that will be
386      * done to resolve a deadlock (number of calls to the redo action).
387      * 
388      * @param number
389      *            the number of attempts that will be done
390      */
391     public void setNumberOfTries(int number) {
392         this.numberOfTries = number;
393     }
394 
395 }