1. /*
  2. * Copyright 2002-2004 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. package org.springframework.jdbc.support;
  17. import java.sql.SQLException;
  18. import java.util.Arrays;
  19. import javax.sql.DataSource;
  20. import org.apache.commons.logging.Log;
  21. import org.apache.commons.logging.LogFactory;
  22. import org.springframework.dao.DataAccessException;
  23. import org.springframework.dao.DataAccessResourceFailureException;
  24. import org.springframework.dao.DataIntegrityViolationException;
  25. import org.springframework.dao.DataRetrievalFailureException;
  26. import org.springframework.dao.OptimisticLockingFailureException;
  27. import org.springframework.jdbc.BadSqlGrammarException;
  28. /**
  29. * Implementation of SQLExceptionTranslator that uses specific vendor codes.
  30. * More precise than SQLState implementation, but vendor-specific.
  31. *
  32. * <p>This class applies the following matching rules:
  33. * <ul>
  34. * <li>Try custom translation implemented by any subclass. Note that this class is
  35. * concrete and is typically used itself, in which case this rule doesn't apply.
  36. * <li>Apply error code matching. Error codes are obtained from the SQLErrorCodesFactory
  37. * by default. This looks up error codes from the classpath and keys into them from the
  38. * database name from the database metadata.
  39. * <li>Fallback to fallback translator. SQLStateSQLExceptionTranslator is the default
  40. * fallback translator.
  41. * </ul>
  42. *
  43. * @author Rod Johnson
  44. * @author Thomas Risberg
  45. * @version $Id: SQLErrorCodeSQLExceptionTranslator.java,v 1.8 2004/05/27 14:46:26 jhoeller Exp $
  46. * @see org.springframework.jdbc.support.SQLErrorCodesFactory
  47. */
  48. public class SQLErrorCodeSQLExceptionTranslator implements SQLExceptionTranslator {
  49. protected final Log logger = LogFactory.getLog(getClass());
  50. /** Error codes available to subclasses */
  51. protected SQLErrorCodes sqlErrorCodes;
  52. /** Fallback translator to use if SQLError code matching doesn't work */
  53. private SQLExceptionTranslator fallback = new SQLStateSQLExceptionTranslator();
  54. /**
  55. * Constructor for use as a JavaBean.
  56. * The SqlErrorCodes or DataSource property must be set.
  57. */
  58. public SQLErrorCodeSQLExceptionTranslator() {
  59. }
  60. /**
  61. * Create a SQLErrorCode translator given these error codes.
  62. * Does not require a database metadata lookup to be performed using a connection.
  63. * @param sec error codes
  64. */
  65. public SQLErrorCodeSQLExceptionTranslator(SQLErrorCodes sec) {
  66. this.sqlErrorCodes = sec;
  67. }
  68. /**
  69. * Create a SQLErrorCode translator for the given DataSource.
  70. * Invoking this constructor will cause a connection to be obtained from the
  71. * DataSource to get the metadata
  72. * @param ds DataSource to use to find metadata and establish which error
  73. * codes are usable
  74. */
  75. public SQLErrorCodeSQLExceptionTranslator(DataSource ds) {
  76. setDataSource(ds);
  77. }
  78. /**
  79. * Set custom error codes to be used for translation
  80. * @param sec custom error codes to use
  81. */
  82. public void setSqlErrorCodes(SQLErrorCodes sec) {
  83. this.sqlErrorCodes = sec;
  84. }
  85. /**
  86. * Set the DataSource.
  87. * <p>Setting this property will cause a connection to be obtained
  88. * from the DataSource to get the metadata.
  89. * @param ds DataSource to use to find metadata and establish which error
  90. * codes are usable
  91. */
  92. public void setDataSource(DataSource ds) {
  93. this.sqlErrorCodes = SQLErrorCodesFactory.getInstance().getErrorCodes(ds);
  94. }
  95. /**
  96. * Override the default SQLState fallback translator
  97. * @param fallback custom fallback exception translator to use if error code
  98. * translation fails
  99. */
  100. public void setFallbackTranslator(SQLExceptionTranslator fallback) {
  101. this.fallback = fallback;
  102. }
  103. public DataAccessException translate(String task, String sql, SQLException sqlex) {
  104. // first, try custom translation
  105. DataAccessException dex = customTranslate(task, sql, sqlex);
  106. if (dex != null) {
  107. return dex;
  108. }
  109. // now try error code
  110. String errorCode;
  111. if (this.sqlErrorCodes != null && this.sqlErrorCodes.isUseSqlStateForTranslation()) {
  112. errorCode = sqlex.getSQLState();
  113. }
  114. else {
  115. errorCode = Integer.toString(sqlex.getErrorCode());
  116. }
  117. if (this.sqlErrorCodes != null && errorCode != null) {
  118. if (Arrays.binarySearch(this.sqlErrorCodes.getBadSqlGrammarCodes(), errorCode) >= 0) {
  119. logTranslation(task, sql, sqlex);
  120. return new BadSqlGrammarException(task, sql, sqlex);
  121. }
  122. else if (Arrays.binarySearch(this.sqlErrorCodes.getDataIntegrityViolationCodes() , errorCode) >= 0) {
  123. logTranslation(task, sql, sqlex);
  124. return new DataIntegrityViolationException(task + ": " + sqlex.getMessage(), sqlex);
  125. }
  126. else if (Arrays.binarySearch(this.sqlErrorCodes.getDataRetrievalFailureCodes() , errorCode) >= 0) {
  127. logTranslation(task, sql, sqlex);
  128. return new DataRetrievalFailureException(task + ": " + sqlex.getMessage(), sqlex);
  129. }
  130. else if (Arrays.binarySearch(this.sqlErrorCodes.getOptimisticLockingFailureCodes() , errorCode) >= 0) {
  131. logTranslation(task, sql, sqlex);
  132. return new OptimisticLockingFailureException(task + ": " + sqlex.getMessage(), sqlex);
  133. }
  134. else if (Arrays.binarySearch(this.sqlErrorCodes.getDataAccessResourceFailureCodes() , errorCode) >= 0) {
  135. logTranslation(task, sql, sqlex);
  136. return new DataAccessResourceFailureException(task + ": " + sqlex.getMessage(), sqlex);
  137. }
  138. }
  139. // we couldn't identify it more precisely - let's hand it over to the SQLState fallback translator
  140. logger.warn("Unable to translate SQLException with errorCode '" + sqlex.getErrorCode() +
  141. "', will now try the fallback translator");
  142. return this.fallback.translate(task, sql, sqlex);
  143. }
  144. /**
  145. * Subclasses can override this method to attempt a custom mapping from SQLException to DataAccessException
  146. * @param task task being attempted
  147. * @param sql SQL that caused the problem
  148. * @param sqlex offending SQLException
  149. * @return null if no custom translation was possible, otherwise a DataAccessException
  150. * resulting from custom translation. This exception should include the sqlex parameter
  151. * as a nested root cause. This implementation always returns null, meaning that
  152. * the translator always falls back to the default error codes.
  153. */
  154. protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) {
  155. return null;
  156. }
  157. private void logTranslation(String task, String sql, SQLException sqlex) {
  158. logger.warn("Translating SQLException with SQLState '" + sqlex.getSQLState() + "' and errorCode '" + sqlex.getErrorCode() +
  159. "' and message [" + sqlex.getMessage() + "]; SQL was [" + sql + "] for task [" + task + "]");
  160. }
  161. }