1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.jdbc.support;
18
19 import java.lang.reflect.Constructor;
20 import java.sql.BatchUpdateException;
21 import java.sql.SQLException;
22 import java.util.Arrays;
23 import javax.sql.DataSource;
24
25 import org.springframework.dao.CannotAcquireLockException;
26 import org.springframework.dao.CannotSerializeTransactionException;
27 import org.springframework.dao.DataAccessException;
28 import org.springframework.dao.DataAccessResourceFailureException;
29 import org.springframework.dao.DataIntegrityViolationException;
30 import org.springframework.dao.DeadlockLoserDataAccessException;
31 import org.springframework.dao.DuplicateKeyException;
32 import org.springframework.dao.PermissionDeniedDataAccessException;
33 import org.springframework.dao.TransientDataAccessResourceException;
34 import org.springframework.jdbc.BadSqlGrammarException;
35 import org.springframework.jdbc.InvalidResultSetAccessException;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 public class SQLErrorCodeSQLExceptionTranslator extends AbstractFallbackSQLExceptionTranslator {
67
68 private static final int MESSAGE_ONLY_CONSTRUCTOR = 1;
69 private static final int MESSAGE_THROWABLE_CONSTRUCTOR = 2;
70 private static final int MESSAGE_SQLEX_CONSTRUCTOR = 3;
71 private static final int MESSAGE_SQL_THROWABLE_CONSTRUCTOR = 4;
72 private static final int MESSAGE_SQL_SQLEX_CONSTRUCTOR = 5;
73
74
75
76 private SQLErrorCodes sqlErrorCodes;
77
78
79
80
81
82
83 public SQLErrorCodeSQLExceptionTranslator() {
84 setFallbackTranslator(new SQLExceptionSubclassTranslator());
85 }
86
87
88
89
90
91
92
93
94
95 public SQLErrorCodeSQLExceptionTranslator(DataSource dataSource) {
96 this();
97 setDataSource(dataSource);
98 }
99
100
101
102
103
104
105
106
107
108 public SQLErrorCodeSQLExceptionTranslator(String dbName) {
109 this();
110 setDatabaseProductName(dbName);
111 }
112
113
114
115
116
117
118 public SQLErrorCodeSQLExceptionTranslator(SQLErrorCodes sec) {
119 this();
120 this.sqlErrorCodes = sec;
121 }
122
123
124
125
126
127
128
129
130
131
132
133 public void setDataSource(DataSource dataSource) {
134 this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource);
135 }
136
137
138
139
140
141
142
143
144
145 public void setDatabaseProductName(String dbName) {
146 this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dbName);
147 }
148
149
150
151
152
153 public void setSqlErrorCodes(SQLErrorCodes sec) {
154 this.sqlErrorCodes = sec;
155 }
156
157
158
159
160
161
162 public SQLErrorCodes getSqlErrorCodes() {
163 return this.sqlErrorCodes;
164 }
165
166
167 @Override
168 protected DataAccessException doTranslate(String task, String sql, SQLException ex) {
169 SQLException sqlEx = ex;
170 if (sqlEx instanceof BatchUpdateException && sqlEx.getNextException() != null) {
171 SQLException nestedSqlEx = sqlEx.getNextException();
172 if (nestedSqlEx.getErrorCode() > 0 || nestedSqlEx.getSQLState() != null) {
173 logger.debug("Using nested SQLException from the BatchUpdateException");
174 sqlEx = nestedSqlEx;
175 }
176 }
177
178
179 DataAccessException dex = customTranslate(task, sql, sqlEx);
180 if (dex != null) {
181 return dex;
182 }
183
184
185 if (this.sqlErrorCodes != null) {
186 SQLExceptionTranslator customTranslator = this.sqlErrorCodes.getCustomSqlExceptionTranslator();
187 if (customTranslator != null) {
188 DataAccessException customDex = customTranslator.translate(task, sql, sqlEx);
189 if (customDex != null) {
190 return customDex;
191 }
192 }
193 }
194
195
196 if (this.sqlErrorCodes != null) {
197 String errorCode;
198 if (this.sqlErrorCodes.isUseSqlStateForTranslation()) {
199 errorCode = sqlEx.getSQLState();
200 }
201 else {
202
203
204 SQLException current = sqlEx;
205 while (current.getErrorCode() == 0 && current.getCause() instanceof SQLException) {
206 current = (SQLException) current.getCause();
207 }
208 errorCode = Integer.toString(current.getErrorCode());
209 }
210
211 if (errorCode != null) {
212
213 CustomSQLErrorCodesTranslation[] customTranslations = this.sqlErrorCodes.getCustomTranslations();
214 if (customTranslations != null) {
215 for (CustomSQLErrorCodesTranslation customTranslation : customTranslations) {
216 if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0) {
217 if (customTranslation.getExceptionClass() != null) {
218 DataAccessException customException = createCustomException(
219 task, sql, sqlEx, customTranslation.getExceptionClass());
220 if (customException != null) {
221 logTranslation(task, sql, sqlEx, true);
222 return customException;
223 }
224 }
225 }
226 }
227 }
228
229 if (Arrays.binarySearch(this.sqlErrorCodes.getBadSqlGrammarCodes(), errorCode) >= 0) {
230 logTranslation(task, sql, sqlEx, false);
231 return new BadSqlGrammarException(task, sql, sqlEx);
232 }
233 else if (Arrays.binarySearch(this.sqlErrorCodes.getInvalidResultSetAccessCodes(), errorCode) >= 0) {
234 logTranslation(task, sql, sqlEx, false);
235 return new InvalidResultSetAccessException(task, sql, sqlEx);
236 }
237 else if (Arrays.binarySearch(this.sqlErrorCodes.getDuplicateKeyCodes(), errorCode) >= 0) {
238 logTranslation(task, sql, sqlEx, false);
239 return new DuplicateKeyException(buildMessage(task, sql, sqlEx), sqlEx);
240 }
241 else if (Arrays.binarySearch(this.sqlErrorCodes.getDataIntegrityViolationCodes(), errorCode) >= 0) {
242 logTranslation(task, sql, sqlEx, false);
243 return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);
244 }
245 else if (Arrays.binarySearch(this.sqlErrorCodes.getPermissionDeniedCodes(), errorCode) >= 0) {
246 logTranslation(task, sql, sqlEx, false);
247 return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
248 }
249 else if (Arrays.binarySearch(this.sqlErrorCodes.getDataAccessResourceFailureCodes(), errorCode) >= 0) {
250 logTranslation(task, sql, sqlEx, false);
251 return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx);
252 }
253 else if (Arrays.binarySearch(this.sqlErrorCodes.getTransientDataAccessResourceCodes(), errorCode) >= 0) {
254 logTranslation(task, sql, sqlEx, false);
255 return new TransientDataAccessResourceException(buildMessage(task, sql, sqlEx), sqlEx);
256 }
257 else if (Arrays.binarySearch(this.sqlErrorCodes.getCannotAcquireLockCodes(), errorCode) >= 0) {
258 logTranslation(task, sql, sqlEx, false);
259 return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx);
260 }
261 else if (Arrays.binarySearch(this.sqlErrorCodes.getDeadlockLoserCodes(), errorCode) >= 0) {
262 logTranslation(task, sql, sqlEx, false);
263 return new DeadlockLoserDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
264 }
265 else if (Arrays.binarySearch(this.sqlErrorCodes.getCannotSerializeTransactionCodes(), errorCode) >= 0) {
266 logTranslation(task, sql, sqlEx, false);
267 return new CannotSerializeTransactionException(buildMessage(task, sql, sqlEx), sqlEx);
268 }
269 }
270 }
271
272
273 if (logger.isDebugEnabled()) {
274 String codes;
275 if (this.sqlErrorCodes != null && this.sqlErrorCodes.isUseSqlStateForTranslation()) {
276 codes = "SQL state '" + sqlEx.getSQLState() + "', error code '" + sqlEx.getErrorCode();
277 }
278 else {
279 codes = "Error code '" + sqlEx.getErrorCode() + "'";
280 }
281 logger.debug("Unable to translate SQLException with " + codes + ", will now try the fallback translator");
282 }
283
284 return null;
285 }
286
287
288
289
290
291
292
293
294
295
296
297
298 protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) {
299 return null;
300 }
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315 protected DataAccessException createCustomException(
316 String task, String sql, SQLException sqlEx, Class<?> exceptionClass) {
317
318
319 try {
320 int constructorType = 0;
321 Constructor<?>[] constructors = exceptionClass.getConstructors();
322 for (Constructor<?> constructor : constructors) {
323 Class<?>[] parameterTypes = constructor.getParameterTypes();
324 if (parameterTypes.length == 1 && parameterTypes[0].equals(String.class)) {
325 if (constructorType < MESSAGE_ONLY_CONSTRUCTOR)
326 constructorType = MESSAGE_ONLY_CONSTRUCTOR;
327 }
328 if (parameterTypes.length == 2 && parameterTypes[0].equals(String.class) &&
329 parameterTypes[1].equals(Throwable.class)) {
330 if (constructorType < MESSAGE_THROWABLE_CONSTRUCTOR)
331 constructorType = MESSAGE_THROWABLE_CONSTRUCTOR;
332 }
333 if (parameterTypes.length == 2 && parameterTypes[0].equals(String.class) &&
334 parameterTypes[1].equals(SQLException.class)) {
335 if (constructorType < MESSAGE_SQLEX_CONSTRUCTOR)
336 constructorType = MESSAGE_SQLEX_CONSTRUCTOR;
337 }
338 if (parameterTypes.length == 3 && parameterTypes[0].equals(String.class) &&
339 parameterTypes[1].equals(String.class) && parameterTypes[2].equals(Throwable.class)) {
340 if (constructorType < MESSAGE_SQL_THROWABLE_CONSTRUCTOR)
341 constructorType = MESSAGE_SQL_THROWABLE_CONSTRUCTOR;
342 }
343 if (parameterTypes.length == 3 && parameterTypes[0].equals(String.class) &&
344 parameterTypes[1].equals(String.class) && parameterTypes[2].equals(SQLException.class)) {
345 if (constructorType < MESSAGE_SQL_SQLEX_CONSTRUCTOR)
346 constructorType = MESSAGE_SQL_SQLEX_CONSTRUCTOR;
347 }
348 }
349
350
351 Constructor<?> exceptionConstructor;
352 switch (constructorType) {
353 case MESSAGE_SQL_SQLEX_CONSTRUCTOR:
354 Class<?>[] messageAndSqlAndSqlExArgsClass = new Class<?>[] {String.class, String.class, SQLException.class};
355 Object[] messageAndSqlAndSqlExArgs = new Object[] {task, sql, sqlEx};
356 exceptionConstructor = exceptionClass.getConstructor(messageAndSqlAndSqlExArgsClass);
357 return (DataAccessException) exceptionConstructor.newInstance(messageAndSqlAndSqlExArgs);
358 case MESSAGE_SQL_THROWABLE_CONSTRUCTOR:
359 Class<?>[] messageAndSqlAndThrowableArgsClass = new Class<?>[] {String.class, String.class, Throwable.class};
360 Object[] messageAndSqlAndThrowableArgs = new Object[] {task, sql, sqlEx};
361 exceptionConstructor = exceptionClass.getConstructor(messageAndSqlAndThrowableArgsClass);
362 return (DataAccessException) exceptionConstructor.newInstance(messageAndSqlAndThrowableArgs);
363 case MESSAGE_SQLEX_CONSTRUCTOR:
364 Class<?>[] messageAndSqlExArgsClass = new Class<?>[] {String.class, SQLException.class};
365 Object[] messageAndSqlExArgs = new Object[] {task + ": " + sqlEx.getMessage(), sqlEx};
366 exceptionConstructor = exceptionClass.getConstructor(messageAndSqlExArgsClass);
367 return (DataAccessException) exceptionConstructor.newInstance(messageAndSqlExArgs);
368 case MESSAGE_THROWABLE_CONSTRUCTOR:
369 Class<?>[] messageAndThrowableArgsClass = new Class<?>[] {String.class, Throwable.class};
370 Object[] messageAndThrowableArgs = new Object[] {task + ": " + sqlEx.getMessage(), sqlEx};
371 exceptionConstructor = exceptionClass.getConstructor(messageAndThrowableArgsClass);
372 return (DataAccessException)exceptionConstructor.newInstance(messageAndThrowableArgs);
373 case MESSAGE_ONLY_CONSTRUCTOR:
374 Class<?>[] messageOnlyArgsClass = new Class<?>[] {String.class};
375 Object[] messageOnlyArgs = new Object[] {task + ": " + sqlEx.getMessage()};
376 exceptionConstructor = exceptionClass.getConstructor(messageOnlyArgsClass);
377 return (DataAccessException) exceptionConstructor.newInstance(messageOnlyArgs);
378 default:
379 if (logger.isWarnEnabled()) {
380 logger.warn("Unable to find appropriate constructor of custom exception class [" +
381 exceptionClass.getName() + "]");
382 }
383 return null;
384 }
385 }
386 catch (Throwable ex) {
387 if (logger.isWarnEnabled()) {
388 logger.warn("Unable to instantiate custom exception class [" + exceptionClass.getName() + "]", ex);
389 }
390 return null;
391 }
392 }
393
394 private void logTranslation(String task, String sql, SQLException sqlEx, boolean custom) {
395 if (logger.isDebugEnabled()) {
396 String intro = custom ? "Custom translation of" : "Translating";
397 logger.debug(intro + " SQLException with SQL state '" + sqlEx.getSQLState() +
398 "', error code '" + sqlEx.getErrorCode() + "', message [" + sqlEx.getMessage() +
399 "]; SQL was [" + sql + "] for task [" + task + "]");
400 }
401 }
402
403 }