/*
 
   Derby - Class org.apache.derby.impl.jdbc.SQLExceptionFactory
 
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to you under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at
 
      http://www.apache.org/licenses/LICENSE-2.0
 
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 
 */

package org.apache.derby.impl.jdbc;

import java.sql.SQLDataException;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.sql.SQLInvalidAuthorizationSpecException;
import java.sql.SQLNonTransientConnectionException;
import java.sql.SQLSyntaxErrorException;
import java.sql.SQLTimeoutException;
import java.sql.SQLTransactionRollbackException;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.jdbc.ExceptionFactory;
import org.apache.derby.iapi.services.i18n.MessageService;
import org.apache.derby.shared.common.reference.SQLState;
import org.apache.derby.shared.common.error.DerbySQLIntegrityConstraintViolationException;

/**
 *Class to create SQLException
 *
 */
public class SQLExceptionFactory extends ExceptionFactory {
    /**
     * <p>
     * method to construct SQLException
     * version specific drivers can overload this method to create
     * version specific exceptions
     * </p>
     *
     * <p>
     * This implementation creates JDBC 4 exceptions.
     * </p>
     *
     * <pre>
     * SQLSTATE CLASS (prefix)     Exception
     * 0A                          java.sql.SQLFeatureNotSupportedException
     * 08                          java.sql.SQLNonTransientConnectionException
     * 22                          java.sql.SQLDataException
     * 28                          java.sql.SQLInvalidAuthorizationSpecException
     * 40                          java.sql.SQLTransactionRollbackException
     * 42                          java.sql.SQLSyntaxErrorException
     * </pre>
     */
    @Override
    public SQLException getSQLException(String message, String messageId,
            SQLException next, int severity, Throwable t, Object... args) {
        String sqlState = StandardException.getSQLStateFromIdentifier(messageId);

        //
        // Create dummy exception which ferries arguments needed to serialize
        // SQLExceptions across the DRDA network layer.
        //
        StandardException ferry =
                wrapArgsForTransportAcrossDRDA(messageId, t, args);

        final SQLException ex;
        if (sqlState.startsWith(SQLState.CONNECTIVITY_PREFIX)) {
            //no derby sqlstate belongs to
            //TransientConnectionException DERBY-3074
            ex = new SQLNonTransientConnectionException(
                    message, sqlState, severity, ferry);
        } else if (sqlState.startsWith(SQLState.SQL_DATA_PREFIX)) {
            ex = new SQLDataException(message, sqlState, severity, ferry);
        } else if (sqlState.startsWith(SQLState.INTEGRITY_VIOLATION_PREFIX)) {
            if ( sqlState.equals( SQLState.LANG_NULL_INTO_NON_NULL ) )
                ex = new SQLIntegrityConstraintViolationException(message, sqlState,
                    severity, ferry);
            else if ( sqlState.equals( SQLState.LANG_CHECK_CONSTRAINT_VIOLATED ) )
                ex = new DerbySQLIntegrityConstraintViolationException(message, sqlState,
                    severity, ferry, args[1], args[0]);
            else
                ex = new DerbySQLIntegrityConstraintViolationException(message, sqlState,
                    severity, ferry, args[0], args[1]);
        } else if (sqlState.startsWith(SQLState.AUTHORIZATION_SPEC_PREFIX)) {
            ex = new SQLInvalidAuthorizationSpecException(message, sqlState,
                    severity, ferry);
        }
        else if (sqlState.startsWith(SQLState.TRANSACTION_PREFIX)) {
            ex = new SQLTransactionRollbackException(message, sqlState,
                    severity, ferry);
        } else if (sqlState.startsWith(SQLState.LSE_COMPILATION_PREFIX)) {
            ex = new SQLSyntaxErrorException(
                    message, sqlState, severity, ferry);
        } else if (sqlState.startsWith(SQLState.UNSUPPORTED_PREFIX)) {
            ex = new SQLFeatureNotSupportedException(
                    message, sqlState, severity, ferry);
        } else if
                (
                 sqlState.equals(SQLState.LANG_STATEMENT_CANCELLED_OR_TIMED_OUT.substring(0, 5)) ||
                 sqlState.equals(SQLState.LOGIN_TIMEOUT.substring(0, 5))
                 ) {
            ex = new SQLTimeoutException(message, sqlState, severity, ferry);
        } else {
            ex = new SQLException(message, sqlState, severity, ferry);
        }

        // If the argument ferry has recorded any extra next exceptions,
        // graft them into the parent exception.
        SQLException ferriedExceptions = ferry.getNextException();
        if (ferriedExceptions != null) {
            ex.setNextException(ferriedExceptions);
        }

        if (next != null) {
            ex.setNextException(next);
        }

        return ex;
    }

    /**
     * Construct an SQLException whose message and severity are derived from
     * the message id.
     */
    @Override
    public final SQLException getSQLException(String messageId,
            SQLException next, Throwable cause, Object... args) {
        String message = MessageService.getTextMessage(messageId, args);
        int severity = StandardException.getSeverityFromIdentifier(messageId);
        return getSQLException(message, messageId, next, severity, cause, args);
    }

	/**
     * <p>
     * The following method helps handle DERBY-1178. The problem is that we may
     * need to serialize our final SQLException across the DRDA network layer.
     * That serialization involves some clever encoding of the Derby messageID and
     * arguments. Unfortunately, once we create one of the
     * JDBC4-specific subclasses of SQLException, we lose the messageID and
     * args. This method creates a dummy StandardException which preserves that
     * information, unless the cause is already a StandardException which
     * contains the necessary information for serializing the exception.
     * </p>
	 */
    private StandardException wrapArgsForTransportAcrossDRDA(
            String messageId, Throwable cause, Object[] args) {

        // If the cause is a StandardException with the same message id, we
        // already have what we need. Just return that exception.
        if (cause instanceof StandardException) {
            StandardException se = (StandardException) cause;
            if (messageId.equals(se.getMessageId())) {
                return se;
            }
        }

        // Otherwise, we create a new StandardException that carries the
        // message id and arguments.
        return StandardException.newException(messageId, cause, args);
    }
}
