KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > jdbc > support > SQLErrorCodeSQLExceptionTranslator


1 /*
2  * Copyright 2002-2006 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.jdbc.support;
18
19 import java.lang.reflect.Constructor JavaDoc;
20 import java.sql.SQLException JavaDoc;
21 import java.util.Arrays JavaDoc;
22
23 import javax.sql.DataSource JavaDoc;
24
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27
28 import org.springframework.dao.CannotAcquireLockException;
29 import org.springframework.dao.CannotSerializeTransactionException;
30 import org.springframework.dao.DataAccessException;
31 import org.springframework.dao.DataAccessResourceFailureException;
32 import org.springframework.dao.DataIntegrityViolationException;
33 import org.springframework.dao.DeadlockLoserDataAccessException;
34 import org.springframework.dao.PermissionDeniedDataAccessException;
35 import org.springframework.jdbc.BadSqlGrammarException;
36 import org.springframework.jdbc.InvalidResultSetAccessException;
37
38 /**
39  * Implementation of SQLExceptionTranslator that analyzes vendor-specific error codes.
40  * More precise than an implementation based on SQL state, but vendor-specific.
41  *
42  * <p>This class applies the following matching rules:
43  * <ul>
44  * <li>Try custom translation implemented by any subclass. Note that this class is
45  * concrete and is typically used itself, in which case this rule doesn't apply.
46  * <li>Apply error code matching. Error codes are obtained from the SQLErrorCodesFactory
47  * by default. This factory loads a "sql-error-codes.xml" file from the class path,
48  * defining error code mappings for database names from database metadata.
49  * <li>Fallback to a fallback translator. SQLStateSQLExceptionTranslator is the
50  * default fallback translator, analyzing the exception's SQL state only.
51  * </ul>
52  *
53  * <p>The configuration file named "sql-error-codes.xml" is by default read from
54  * this package. It can be overridden through a file of the same name in the root
55  * of the class path (e.g. in the "/WEB-INF/classes" directory).
56  *
57  * @author Rod Johnson
58  * @author Thomas Risberg
59  * @author Juergen Hoeller
60  * @see SQLErrorCodesFactory
61  * @see SQLStateSQLExceptionTranslator
62  */

63 public class SQLErrorCodeSQLExceptionTranslator implements SQLExceptionTranslator {
64
65     private static final int MESSAGE_ONLY_CONSTRUCTOR = 1;
66     private static final int MESSAGE_THROWABLE_CONSTRUCTOR = 2;
67     private static final int MESSAGE_SQLEX_CONSTRUCTOR = 3;
68     private static final int MESSAGE_SQL_THROWABLE_CONSTRUCTOR = 4;
69     private static final int MESSAGE_SQL_SQLEX_CONSTRUCTOR = 5;
70
71
72     /** Logger available to subclasses */
73     protected final Log logger = LogFactory.getLog(getClass());
74
75     /** Error codes used by this translator */
76     private SQLErrorCodes sqlErrorCodes;
77     
78     /** Fallback translator to use if SQL error code matching doesn't work */
79     private SQLExceptionTranslator fallbackTranslator = new SQLStateSQLExceptionTranslator();
80
81
82     /**
83      * Constructor for use as a JavaBean.
84      * The SqlErrorCodes or DataSource property must be set.
85      */

86     public SQLErrorCodeSQLExceptionTranslator() {
87     }
88
89     /**
90      * Create a SQL error code translator for the given DataSource.
91      * Invoking this constructor will cause a Connection to be obtained
92      * from the DataSource to get the metadata.
93      * @param dataSource DataSource to use to find metadata and establish
94      * which error codes are usable
95      * @see SQLErrorCodesFactory
96      */

97     public SQLErrorCodeSQLExceptionTranslator(DataSource JavaDoc dataSource) {
98         setDataSource(dataSource);
99     }
100
101     /**
102      * Create a SQL error code translator for the given database product name.
103      * Invoking this constructor will avoid obtaining a Connection from the
104      * DataSource to get the metadata.
105      * @param dbName the database product name that identifies the error codes entry
106      * @see SQLErrorCodesFactory
107      * @see java.sql.DatabaseMetaData#getDatabaseProductName()
108      */

109     public SQLErrorCodeSQLExceptionTranslator(String JavaDoc dbName) {
110         setDatabaseProductName(dbName);
111     }
112
113     /**
114      * Create a SQLErrorCode translator given these error codes.
115      * Does not require a database metadata lookup to be performed using a connection.
116      * @param sec error codes
117      */

118     public SQLErrorCodeSQLExceptionTranslator(SQLErrorCodes sec) {
119         this.sqlErrorCodes = sec;
120     }
121
122
123     /**
124      * Set the DataSource for this translator.
125      * <p>Setting this property will cause a Connection to be obtained from
126      * the DataSource to get the metadata.
127      * @param dataSource DataSource to use to find metadata and establish
128      * which error codes are usable
129      * @see SQLErrorCodesFactory#getErrorCodes(javax.sql.DataSource)
130      * @see java.sql.DatabaseMetaData#getDatabaseProductName()
131      */

132     public void setDataSource(DataSource JavaDoc dataSource) {
133         this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dataSource);
134     }
135
136     /**
137      * Set the database product name for this translator.
138      * <p>Setting this property will avoid obtaining a Connection from the DataSource
139      * to get the metadata.
140      * @param dbName the database product name that identifies the error codes entry
141      * @see SQLErrorCodesFactory#getErrorCodes(String)
142      * @see java.sql.DatabaseMetaData#getDatabaseProductName()
143      */

144     public void setDatabaseProductName(String JavaDoc dbName) {
145         this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(dbName);
146     }
147
148     /**
149      * Set custom error codes to be used for translation.
150      * @param sec custom error codes to use
151      */

152     public void setSqlErrorCodes(SQLErrorCodes sec) {
153         this.sqlErrorCodes = sec;
154     }
155
156     /**
157      * Return the error codes used by this translator.
158      * Usually determined via a DataSource.
159      * @see #setDataSource
160      */

161     public SQLErrorCodes getSqlErrorCodes() {
162         return sqlErrorCodes;
163     }
164
165     /**
166      * Override the default SQL state fallback translator.
167      * @param fallback custom fallback exception translator to use if error code
168      * translation fails
169      * @see SQLStateSQLExceptionTranslator
170      */

171     public void setFallbackTranslator(SQLExceptionTranslator fallback) {
172         this.fallbackTranslator = fallback;
173     }
174
175     /**
176      * Return the fallback exception translator.
177      */

178     public SQLExceptionTranslator getFallbackTranslator() {
179         return fallbackTranslator;
180     }
181
182
183     public DataAccessException translate(String JavaDoc task, String JavaDoc sql, SQLException JavaDoc sqlEx) {
184         if (task == null) {
185             task = "";
186         }
187         if (sql == null) {
188             sql = "";
189         }
190         
191         // First, try custom translation from overridden method.
192
DataAccessException dex = customTranslate(task, sql, sqlEx);
193         if (dex != null) {
194             return dex;
195         }
196
197         // Check SQLErrorCodes with corresponding error code, if available.
198
if (this.sqlErrorCodes != null) {
199             String JavaDoc errorCode = null;
200             if (this.sqlErrorCodes.isUseSqlStateForTranslation()) {
201                 errorCode = sqlEx.getSQLState();
202             }
203             else {
204                 errorCode = Integer.toString(sqlEx.getErrorCode());
205             }
206
207             if (errorCode != null) {
208
209                 // Look for defined custom translations first.
210
CustomSQLErrorCodesTranslation[] customTranslations = this.sqlErrorCodes.getCustomTranslations();
211                 if (customTranslations != null) {
212                     for (int i = 0; i < customTranslations.length; i++) {
213                         CustomSQLErrorCodesTranslation customTranslation = customTranslations[i];
214                         if (Arrays.binarySearch(customTranslation.getErrorCodes(), errorCode) >= 0) {
215                             if (customTranslation.getExceptionClass() != null) {
216                                 DataAccessException customException = createCustomException(
217                                         task, sql, sqlEx, customTranslation.getExceptionClass());
218                                 if (customException != null) {
219                                     logTranslation(task, sql, sqlEx, true);
220                                     return customException;
221                                 }
222                             }
223                         }
224                     }
225                 }
226
227                 // Next, look for grouped error codes.
228
if (Arrays.binarySearch(this.sqlErrorCodes.getBadSqlGrammarCodes(), errorCode) >= 0) {
229                     logTranslation(task, sql, sqlEx, false);
230                     return new BadSqlGrammarException(task, sql, sqlEx);
231                 }
232                 else if (Arrays.binarySearch(this.sqlErrorCodes.getInvalidResultSetAccessCodes(), errorCode) >= 0) {
233                     logTranslation(task, sql, sqlEx, false);
234                     return new InvalidResultSetAccessException(task, sql, sqlEx);
235                 }
236                 else if (Arrays.binarySearch(this.sqlErrorCodes.getDataAccessResourceFailureCodes(), errorCode) >= 0) {
237                     logTranslation(task, sql, sqlEx, false);
238                     return new DataAccessResourceFailureException(buildMessage(task, sql, sqlEx), sqlEx);
239                 }
240                 else if (Arrays.binarySearch(this.sqlErrorCodes.getPermissionDeniedCodes(), errorCode) >= 0) {
241                     logTranslation(task, sql, sqlEx, false);
242                     return new PermissionDeniedDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
243                 }
244                 else if (Arrays.binarySearch(this.sqlErrorCodes.getDataIntegrityViolationCodes(), errorCode) >= 0) {
245                     logTranslation(task, sql, sqlEx, false);
246                     return new DataIntegrityViolationException(buildMessage(task, sql, sqlEx), sqlEx);
247                 }
248                 else if (Arrays.binarySearch(this.sqlErrorCodes.getCannotAcquireLockCodes(), errorCode) >= 0) {
249                     logTranslation(task, sql, sqlEx, false);
250                     return new CannotAcquireLockException(buildMessage(task, sql, sqlEx), sqlEx);
251                 }
252                 else if (Arrays.binarySearch(this.sqlErrorCodes.getDeadlockLoserCodes(), errorCode) >= 0) {
253                     logTranslation(task, sql, sqlEx, false);
254                     return new DeadlockLoserDataAccessException(buildMessage(task, sql, sqlEx), sqlEx);
255                 }
256                 else if (Arrays.binarySearch(this.sqlErrorCodes.getCannotSerializeTransactionCodes(), errorCode) >= 0) {
257                     logTranslation(task, sql, sqlEx, false);
258                     return new CannotSerializeTransactionException(buildMessage(task, sql, sqlEx), sqlEx);
259                 }
260             }
261         }
262
263         // We couldn't identify it more precisely - let's hand it over to the SQLState fallback translator.
264
if (logger.isDebugEnabled()) {
265             String JavaDoc codes = null;
266             if (this.sqlErrorCodes.isUseSqlStateForTranslation()) {
267                 codes = "SQL state '" + sqlEx.getSQLState() +
268                     "', error code '" + sqlEx.getErrorCode();
269             }
270             else {
271                 codes = "Error code '" + sqlEx.getErrorCode() + "'";
272             }
273             logger.debug("Unable to translate SQLException with " + codes +
274                     ", will now try the fallback translator");
275         }
276         return this.fallbackTranslator.translate(task, sql, sqlEx);
277     }
278
279     /**
280      * Build a message String for the given SQLException.
281      * Called when creating an instance of a generic DataAccessException class.
282      * @param task readable text describing the task being attempted
283      * @param sql SQL query or update that caused the problem. May be <code>null</code>.
284      * @param sqlEx the offending SQLException
285      * @return the message String to use
286      */

287     protected String JavaDoc buildMessage(String JavaDoc task, String JavaDoc sql, SQLException JavaDoc sqlEx) {
288         return task + "; SQL [" + sql + "]; " + sqlEx.getMessage();
289     }
290
291     /**
292      * Subclasses can override this method to attempt a custom mapping from SQLException
293      * to DataAccessException.
294      * @param task readable text describing the task being attempted
295      * @param sql SQL query or update that caused the problem. May be <code>null</code>.
296      * @param sqlEx the offending SQLException
297      * @return null if no custom translation was possible, otherwise a DataAccessException
298      * resulting from custom translation. This exception should include the sqlEx parameter
299      * as a nested root cause. This implementation always returns null, meaning that
300      * the translator always falls back to the default error codes.
301      */

302     protected DataAccessException customTranslate(String JavaDoc task, String JavaDoc sql, SQLException JavaDoc sqlEx) {
303         return null;
304     }
305
306     /**
307      * Create a custom DataAccessException, based on a given exception
308      * class from a CustomSQLErrorCodesTranslation definition.
309      * @param task readable text describing the task being attempted
310      * @param sql SQL query or update that caused the problem. May be <code>null</code>.
311      * @param sqlEx the offending SQLException
312      * @param exceptionClass the exception class to use, as defined in the
313      * CustomSQLErrorCodesTranslation definition
314      * @return null if the custom exception could not be created, otherwise
315      * the resulting DataAccessException. This exception should include the
316      * sqlEx parameter as a nested root cause.
317      * @see CustomSQLErrorCodesTranslation#setExceptionClass
318      */

319     protected DataAccessException createCustomException(
320             String JavaDoc task, String JavaDoc sql, SQLException JavaDoc sqlEx, Class JavaDoc exceptionClass) {
321
322         // find appropriate constructor
323
try {
324             int constructorType = 0;
325             Constructor JavaDoc[] constructors = exceptionClass.getConstructors();
326             for (int i = 0; i < constructors.length; i++) {
327                 Class JavaDoc[] parameterTypes = constructors[i].getParameterTypes();
328                 if (parameterTypes.length == 1 && parameterTypes[0].equals(String JavaDoc.class)) {
329                     if (constructorType < MESSAGE_ONLY_CONSTRUCTOR)
330                         constructorType = MESSAGE_ONLY_CONSTRUCTOR;
331                 }
332                 if (parameterTypes.length == 2 && parameterTypes[0].equals(String JavaDoc.class) &&
333                         parameterTypes[1].equals(Throwable JavaDoc.class)) {
334                     if (constructorType < MESSAGE_THROWABLE_CONSTRUCTOR)
335                         constructorType = MESSAGE_THROWABLE_CONSTRUCTOR;
336                 }
337                 if (parameterTypes.length == 2 && parameterTypes[0].equals(String JavaDoc.class) &&
338                         parameterTypes[1].equals(SQLException JavaDoc.class)) {
339                     if (constructorType < MESSAGE_SQLEX_CONSTRUCTOR)
340                         constructorType = MESSAGE_SQLEX_CONSTRUCTOR;
341                 }
342                 if (parameterTypes.length == 3 && parameterTypes[0].equals(String JavaDoc.class) &&
343                         parameterTypes[1].equals(String JavaDoc.class) && parameterTypes[2].equals(Throwable JavaDoc.class)) {
344                     if (constructorType < MESSAGE_SQL_THROWABLE_CONSTRUCTOR)
345                         constructorType = MESSAGE_SQL_THROWABLE_CONSTRUCTOR;
346                 }
347                 if (parameterTypes.length == 3 && parameterTypes[0].equals(String JavaDoc.class) &&
348                         parameterTypes[1].equals(String JavaDoc.class) && parameterTypes[2].equals(SQLException JavaDoc.class)) {
349                     if (constructorType < MESSAGE_SQL_SQLEX_CONSTRUCTOR)
350                         constructorType = MESSAGE_SQL_SQLEX_CONSTRUCTOR;
351                 }
352             }
353
354             // invoke constructor
355
Constructor JavaDoc exceptionConstructor = null;
356             switch (constructorType) {
357                 case MESSAGE_SQL_SQLEX_CONSTRUCTOR:
358                     Class JavaDoc[] messageAndSqlAndSqlExArgsClass = new Class JavaDoc[] {String JavaDoc.class, String JavaDoc.class, SQLException JavaDoc.class};
359                     Object JavaDoc[] messageAndSqlAndSqlExArgs = new Object JavaDoc[] {task, sql, sqlEx};
360                     exceptionConstructor = exceptionClass.getConstructor(messageAndSqlAndSqlExArgsClass);
361                     return (DataAccessException) exceptionConstructor.newInstance(messageAndSqlAndSqlExArgs);
362                 case MESSAGE_SQL_THROWABLE_CONSTRUCTOR:
363                     Class JavaDoc[] messageAndSqlAndThrowableArgsClass = new Class JavaDoc[] {String JavaDoc.class, String JavaDoc.class, Throwable JavaDoc.class};
364                     Object JavaDoc[] messageAndSqlAndThrowableArgs = new Object JavaDoc[] {task, sql, sqlEx};
365                     exceptionConstructor = exceptionClass.getConstructor(messageAndSqlAndThrowableArgsClass);
366                     return (DataAccessException) exceptionConstructor.newInstance(messageAndSqlAndThrowableArgs);
367                 case MESSAGE_SQLEX_CONSTRUCTOR:
368                     Class JavaDoc[] messageAndSqlExArgsClass = new Class JavaDoc[] {String JavaDoc.class, SQLException JavaDoc.class};
369                     Object JavaDoc[] messageAndSqlExArgs = new Object JavaDoc[] {task + ": " + sqlEx.getMessage(), sqlEx};
370                     exceptionConstructor = exceptionClass.getConstructor(messageAndSqlExArgsClass);
371                     return (DataAccessException) exceptionConstructor.newInstance(messageAndSqlExArgs);
372                 case MESSAGE_THROWABLE_CONSTRUCTOR:
373                     Class JavaDoc[] messageAndThrowableArgsClass = new Class JavaDoc[] {String JavaDoc.class, Throwable JavaDoc.class};
374                     Object JavaDoc[] messageAndThrowableArgs = new Object JavaDoc[] {task + ": " + sqlEx.getMessage(), sqlEx};
375                     exceptionConstructor = exceptionClass.getConstructor(messageAndThrowableArgsClass);
376                     return (DataAccessException)exceptionConstructor.newInstance(messageAndThrowableArgs);
377                 case MESSAGE_ONLY_CONSTRUCTOR:
378                     Class JavaDoc[] messageOnlyArgsClass = new Class JavaDoc[] {String JavaDoc.class};
379                     Object JavaDoc[] messageOnlyArgs = new Object JavaDoc[] {task + ": " + sqlEx.getMessage()};
380                     exceptionConstructor = exceptionClass.getConstructor(messageOnlyArgsClass);
381                     return (DataAccessException) exceptionConstructor.newInstance(messageOnlyArgs);
382                 default:
383                     logger.warn("Unable to find appropriate constructor of custom exception class [" +
384                             exceptionClass.getName() + "]");
385                     return null;
386                 }
387         }
388         catch (Throwable JavaDoc ex) {
389             if (logger.isWarnEnabled()) {
390                 logger.warn("Unable to instantiate custom exception class [" + exceptionClass.getName() + "]", ex);
391             }
392             return null;
393         }
394     }
395
396     private void logTranslation(String JavaDoc task, String JavaDoc sql, SQLException JavaDoc sqlEx, boolean custom) {
397         if (logger.isDebugEnabled()) {
398             String JavaDoc intro = custom ? "Custom translation of" : "Translating";
399             logger.debug(intro + " SQLException with SQL state '" + sqlEx.getSQLState() +
400                     "', error code '" + sqlEx.getErrorCode() + "', message [" + sqlEx.getMessage() +
401                     "]; SQL was [" + sql + "] for task [" + task + "]");
402         }
403     }
404
405 }
406
Popular Tags