001/*
002 * HA-JDBC: High-Availability JDBC
003 * Copyright (C) 2012  Paul Ferraro
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Lesser General Public License as published by
007 * the Free Software Foundation, either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Lesser General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018package net.sf.hajdbc.sql;
019
020import java.sql.BatchUpdateException;
021import java.sql.ClientInfoStatus;
022import java.sql.DataTruncation;
023import java.sql.SQLClientInfoException;
024import java.sql.SQLException;
025import java.util.Arrays;
026import java.util.Map;
027
028import net.sf.hajdbc.AbstractExceptionFactory;
029import net.sf.hajdbc.ExceptionType;
030import net.sf.hajdbc.dialect.Dialect;
031
032/**
033 * @author Paul Ferraro
034 */
035public class SQLExceptionFactory extends AbstractExceptionFactory<SQLException>
036{
037        private static final long serialVersionUID = -7352436527312370925L;
038
039        public SQLExceptionFactory()
040        {
041                super(SQLException.class);      
042        }
043        
044        /**
045         * {@inheritDoc}
046         * @see net.sf.hajdbc.ExceptionFactory#createException(java.lang.String)
047         */
048        @Override
049        public SQLException createException(String message)
050        {
051                return new SQLException(message);
052        }
053
054        /**
055         * {@inheritDoc}
056         * @see net.sf.hajdbc.ExceptionFactory#equals(java.lang.Exception, java.lang.Exception)
057         */
058        @Override
059        public boolean equals(SQLException exception1, SQLException exception2)
060        {
061                // Terminator for exception chain recursion
062                if ((exception1 == null) || (exception2 == null))
063                {
064                        return exception1 == exception2;
065                }
066                
067                // Fast-fail for mismatched Java 1.6 SQLException subclasses
068                if (!exception1.getClass().equals(exception2.getClass()))
069                {
070                        return false;
071                }
072                
073                // Ensure BatchUpdateExceptions have matching update counts
074                if ((exception1 instanceof BatchUpdateException) && (exception2 instanceof BatchUpdateException))
075                {
076                        BatchUpdateException e1 = (BatchUpdateException) exception1;
077                        BatchUpdateException e2 = (BatchUpdateException) exception2;
078                        
079                        int[] counts1 = e1.getUpdateCounts();
080                        int[] counts2 = e2.getUpdateCounts();
081                        
082                        if ((counts1 != null) && (counts2 != null) ? !Arrays.equals(counts1, counts2) : (counts1 != counts2))
083                        {
084                                return false;
085                        }
086                }
087                else if ((exception1 instanceof SQLClientInfoException) && (exception2 instanceof SQLClientInfoException))
088                {
089                        SQLClientInfoException e1 = (SQLClientInfoException) exception1;
090                        SQLClientInfoException e2 = (SQLClientInfoException) exception2;
091                        
092                        Map<String, ClientInfoStatus> map1 = e1.getFailedProperties();
093                        Map<String, ClientInfoStatus> map2 = e2.getFailedProperties();
094                        
095                        return (map1 != null) && (map2 != null) ? map1.equals(map2) : (map1 != map2);
096                }
097                else if ((exception1 instanceof DataTruncation) && (exception2 instanceof DataTruncation))
098                {
099                        DataTruncation e1 = (DataTruncation) exception1;
100                        DataTruncation e2 = (DataTruncation) exception2;
101                        
102                        return (e1.getDataSize() == e2.getDataSize()) && (e1.getIndex() == e2.getIndex()) && (e1.getParameter() == e2.getParameter()) && (e1.getRead() == e2.getRead()) && (e1.getTransferSize() == e2.getTransferSize());
103                }
104                
105                SQLException nextException1 = exception1.getNextException();
106                SQLException nextException2 = exception2.getNextException();
107                
108                int code1 = exception1.getErrorCode();
109                int code2 = exception2.getErrorCode();
110
111                // Match by vendor code, if defined
112                if ((code1 != 0) || (code2 != 0))
113                {
114                        return (code1 == code2) ? this.equals(nextException1, nextException2) : false;
115                }
116                
117                String state1 = exception1.getSQLState();
118                String state2 = exception2.getSQLState();
119
120                boolean hasState1 = (state1 != null);
121                boolean hasState2 = (state2 != null);
122
123                // Match by SQLState, if defined
124                if (hasState1 || hasState2)
125                {
126                        return (state1 == state2) || (hasState1 && hasState2 && state1.equals(state2)) ? this.equals(nextException1, nextException2) : false;
127                }
128
129                // Fallback to match by reason
130                String reason1 = exception1.getMessage();
131                String reason2 = exception2.getMessage();
132                
133                return ((reason1 == reason2) || ((reason1 != null) && (reason2 != null) && reason1.equals(reason2))) ? this.equals(nextException1, nextException2) : false;
134        }
135
136        /**
137         * {@inheritDoc}
138         * @see net.sf.hajdbc.ExceptionFactory#indicatesFailure(java.lang.Exception, net.sf.hajdbc.dialect.Dialect)
139         */
140        @Override
141        public boolean indicatesFailure(SQLException exception, Dialect dialect)
142        {
143                SQLException nextException = exception.getNextException();
144                
145                return dialect.indicatesFailure(exception) || ((nextException != null) && this.indicatesFailure(nextException, dialect));
146        }
147
148        /**
149         * {@inheritDoc}
150         * @see net.sf.hajdbc.ExceptionFactory#getType()
151         */
152        @Override
153        public ExceptionType getType()
154        {
155                return ExceptionType.SQL;
156        }
157}