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"); // Dynamic SQL error
072: BAD_SQL_CODES.add("21"); // Cardinality violation
073: BAD_SQL_CODES.add("37"); // Syntax error dynamic SQL
074: BAD_SQL_CODES.add("42"); // Syntax error
075: BAD_SQL_CODES.add("2A"); // Syntax error direct SQL
076: BAD_SQL_CODES.add("65"); // Oracle throws this on unknown identifier
077: BAD_SQL_CODES.add("S0"); // MySQL uses this - from ODBC error codes?
078:
079: INTEGRITY_VIOLATION_CODES.add("22"); // Integrity constraint violation
080: INTEGRITY_VIOLATION_CODES.add("23"); // Integrity constraint violation
081: INTEGRITY_VIOLATION_CODES.add("27"); // Triggered data change violation
082: INTEGRITY_VIOLATION_CODES.add("44"); // With check violation
083:
084: CONCURRENCY_CODES.add("40"); // Transaction rollback
085:
086: RESOURCE_FAILURE_CODES.add("08"); // Connection exception
087: RESOURCE_FAILURE_CODES.add("53"); // PostgreSQL uses this - insufficient resources (e.g. disk full)
088: RESOURCE_FAILURE_CODES.add("54"); // PostgreSQL uses this - program limit exceeded (e.g. statement too complex)
089: }
090:
091: /** Logger available to subclasses */
092: protected final Log logger = LogFactory.getLog(getClass());
093:
094: public DataAccessException translate(String task, String sql,
095: SQLException ex) {
096: Assert.notNull(ex, "Cannot translate a null SQLException.");
097: if (task == null) {
098: task = "";
099: }
100: if (sql == null) {
101: sql = "";
102: }
103: String sqlState = getSqlState(ex);
104: if (sqlState != null && sqlState.length() >= 2) {
105: String classCode = sqlState.substring(0, 2);
106: if (BAD_SQL_CODES.contains(classCode)) {
107: return new BadSqlGrammarException(task, sql, ex);
108: } else if (INTEGRITY_VIOLATION_CODES.contains(classCode)) {
109: return new DataIntegrityViolationException(
110: buildMessage(task, sql, ex), ex);
111: } else if (RESOURCE_FAILURE_CODES.contains(classCode)) {
112: return new DataAccessResourceFailureException(
113: buildMessage(task, sql, ex), ex);
114: } else if (CONCURRENCY_CODES.contains(classCode)) {
115: return new ConcurrencyFailureException(buildMessage(
116: task, sql, ex), ex);
117: }
118: }
119: // We couldn't identify it more precisely.
120: return new UncategorizedSQLException(task, sql, ex);
121: }
122:
123: /**
124: * Build a message <code>String</code> for the given {@link SQLException}.
125: * <p>Called when creating an instance of a generic
126: * {@link DataAccessException} class.
127: * @param task readable text describing the task being attempted
128: * @param sql the SQL statement that caused the problem. May be <code>null</code>.
129: * @param ex the offending <code>SQLException</code>
130: * @return the message <code>String</code> to use
131: */
132: protected String buildMessage(String task, String sql,
133: SQLException ex) {
134: return task + "; SQL [" + sql + "]; " + ex.getMessage();
135: }
136:
137: /**
138: * Gets the SQL state code from the supplied {@link SQLException exception}.
139: * <p>Some JDBC drivers nest the actual exception from a batched update, so we
140: * might need to dig down into the nested exception.
141: * @param ex the exception from which the {@link SQLException#getSQLState() SQL state} is to be extracted
142: * @return the SQL state code
143: */
144: private String getSqlState(SQLException ex) {
145: String sqlState = ex.getSQLState();
146: if (sqlState == null) {
147: SQLException nestedEx = ex.getNextException();
148: if (nestedEx != null) {
149: sqlState = nestedEx.getSQLState();
150: }
151: }
152: return sqlState;
153: }
154:
155: }
|