001: /*
002: * Copyright 2002-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.jdbc.support;
018:
019: import org.apache.commons.logging.Log;
020: import org.apache.commons.logging.LogFactory;
021: import org.springframework.dao.ConcurrencyFailureException;
022: import org.springframework.dao.DataAccessException;
023: import org.springframework.dao.DataAccessResourceFailureException;
024: import org.springframework.dao.DataIntegrityViolationException;
025: import org.springframework.jdbc.BadSqlGrammarException;
026: import org.springframework.jdbc.UncategorizedSQLException;
027: import org.springframework.util.Assert;
028:
029: import java.sql.SQLException;
030: import java.util.HashSet;
031: import java.util.Set;
032:
033: /**
034: * {@link SQLExceptionTranslator} implementation that analyzes the SQL state
035: * in the {@link SQLException}.
036: *
037: * <p>Not able to diagnose all problems, but is portable between databases and
038: * does not require special initialization (no database vendor detection, etc.).
039: * For more precise translation, consider {@link SQLErrorCodeSQLExceptionTranslator}.
040: *
041: * @author Rod Johnson
042: * @author Juergen Hoeller
043: * @see java.sql.SQLException#getSQLState()
044: * @see SQLErrorCodeSQLExceptionTranslator
045: */
046: public class SQLStateSQLExceptionTranslator implements
047: SQLExceptionTranslator {
048:
049: /**
050: * Set of well-known String 2-digit codes that indicate bad SQL
051: */
052: private static final Set BAD_SQL_CODES = new HashSet(6);
053:
054: /**
055: * Set of well-known String 2-digit codes that indicate RDBMS integrity violation
056: */
057: private static final Set INTEGRITY_VIOLATION_CODES = new HashSet(4);
058:
059: /**
060: * Set of String 2-digit codes that indicate communication errors
061: */
062: private static final Set RESOURCE_FAILURE_CODES = new HashSet(3);
063:
064: /**
065: * Set of String 2-digit codes that indicate concurrency errors
066: */
067: private static final Set CONCURRENCY_CODES = new HashSet(1);
068:
069: // Populate reference data.
070: static {
071: BAD_SQL_CODES.add("07");
072: BAD_SQL_CODES.add("37");
073: BAD_SQL_CODES.add("42");
074: BAD_SQL_CODES.add("2A");
075: BAD_SQL_CODES.add("65"); // Oracle throws this on unknown identifier
076: BAD_SQL_CODES.add("S0"); // MySQL uses this - from ODBC error codes?
077:
078: INTEGRITY_VIOLATION_CODES.add("22"); // Integrity constraint violation
079: INTEGRITY_VIOLATION_CODES.add("23"); // Integrity constraint violation
080: INTEGRITY_VIOLATION_CODES.add("27"); // Triggered data change violation
081: INTEGRITY_VIOLATION_CODES.add("44"); // With check violation
082:
083: CONCURRENCY_CODES.add("40"); // Transaction rollback
084:
085: RESOURCE_FAILURE_CODES.add("08"); // Connection exception
086: RESOURCE_FAILURE_CODES.add("53"); // PostgreSQL uses this - insufficient resources (e.g. disk full)
087: RESOURCE_FAILURE_CODES.add("54"); // PostgreSQL uses this - program limit exceeded (e.g. statement too complex)
088: }
089:
090: /** Logger available to subclasses */
091: protected final Log logger = LogFactory.getLog(getClass());
092:
093: public DataAccessException translate(String task, String sql,
094: SQLException ex) {
095: Assert.notNull(ex, "Cannot translate a null SQLException.");
096: if (task == null) {
097: task = "";
098: }
099: if (sql == null) {
100: sql = "";
101: }
102: String sqlState = getSqlState(ex);
103: if (sqlState != null && sqlState.length() >= 2) {
104: String classCode = sqlState.substring(0, 2);
105: if (BAD_SQL_CODES.contains(classCode)) {
106: return new BadSqlGrammarException(task, sql, ex);
107: } else if (INTEGRITY_VIOLATION_CODES.contains(classCode)) {
108: return new DataIntegrityViolationException(
109: buildMessage(task, sql, ex), ex);
110: } else if (RESOURCE_FAILURE_CODES.contains(classCode)) {
111: return new DataAccessResourceFailureException(
112: buildMessage(task, sql, ex), ex);
113: } else if (CONCURRENCY_CODES.contains(classCode)) {
114: return new ConcurrencyFailureException(buildMessage(
115: task, sql, ex), ex);
116: }
117: }
118: // We couldn't identify it more precisely.
119: return new UncategorizedSQLException(task, sql, ex);
120: }
121:
122: /**
123: * Build a message <code>String</code> for the given {@link SQLException}.
124: * <p>Called when creating an instance of a generic
125: * {@link DataAccessException} class.
126: * @param task readable text describing the task being attempted
127: * @param sql the SQL statement that caused the problem. May be <code>null</code>.
128: * @param ex the offending <code>SQLException</code>
129: * @return the message <code>String</code> to use
130: */
131: protected String buildMessage(String task, String sql,
132: SQLException ex) {
133: return task + "; SQL [" + sql + "]; " + ex.getMessage();
134: }
135:
136: /**
137: * Gets the SQL state code from the supplied {@link SQLException exception}.
138: * <p>Some JDBC drivers nest the actual exception from a batched update, so we
139: * might need to dig down into the nested exception.
140: * @param ex the exception from which the {@link SQLException#getSQLState() SQL state} is to be extracted
141: * @return the SQL state code
142: */
143: private String getSqlState(SQLException ex) {
144: String sqlState = ex.getSQLState();
145: if (sqlState == null) {
146: SQLException nestedEx = ex.getNextException();
147: if (nestedEx != null) {
148: sqlState = nestedEx.getSQLState();
149: }
150: }
151: return sqlState;
152: }
153:
154: }
|