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
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 }