org.hibernate.engine.jdbc.spi.SqlExceptionHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.engine.jdbc.spi.SqlExceptionHelper.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.engine.jdbc.spi;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import org.hibernate.JDBCException;
import org.hibernate.exception.internal.SQLStateConverter;
import org.hibernate.exception.spi.SQLExceptionConverter;
import org.hibernate.exception.spi.ViolatedConstraintNameExtracter;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;

import org.jboss.logging.Logger;
import org.jboss.logging.Logger.Level;

/**
 * Helper for handling SQLExceptions in various manners.
 *
 * @author Steve Ebersole
 */
public class SqlExceptionHelper {
    private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class,
            SqlExceptionHelper.class.getName());

    private static final String DEFAULT_EXCEPTION_MSG = "SQL Exception";
    private static final String DEFAULT_WARNING_MSG = "SQL Warning";
    private final boolean logWarnings;

    private static final SQLExceptionConverter DEFAULT_CONVERTER = new SQLStateConverter(
            new ViolatedConstraintNameExtracter() {
                public String extractConstraintName(SQLException e) {
                    return null;
                }
            });

    private SQLExceptionConverter sqlExceptionConverter;

    /**
     * Create an exception helper with a default exception converter.
     */
    public SqlExceptionHelper(boolean logWarnings) {
        this(DEFAULT_CONVERTER, logWarnings);
    }

    /**
     * Create an exception helper with a specific exception converter.
     *
     * @param sqlExceptionConverter The exception converter to use.
     */
    public SqlExceptionHelper(SQLExceptionConverter sqlExceptionConverter, boolean logWarnings) {
        this.sqlExceptionConverter = sqlExceptionConverter;
        this.logWarnings = logWarnings;
    }

    /**
     * Access the current exception converter being used internally.
     *
     * @return The current exception converter.
     */
    public SQLExceptionConverter getSqlExceptionConverter() {
        return sqlExceptionConverter;
    }

    /**
     * Inject the exception converter to use.
     * <p/>
     * NOTE : <tt>null</tt> is allowed and signifies to use the default.
     *
     * @param sqlExceptionConverter The converter to use.
     */
    public void setSqlExceptionConverter(SQLExceptionConverter sqlExceptionConverter) {
        this.sqlExceptionConverter = (sqlExceptionConverter == null ? DEFAULT_CONVERTER : sqlExceptionConverter);
    }

    // SQLException ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /**
     * Convert an SQLException using the current converter, doing some logging first.
     *
     * @param sqlException The exception to convert
     * @param message An error message.
     *
     * @return The converted exception
     */
    public JDBCException convert(SQLException sqlException, String message) {
        return convert(sqlException, message, "n/a");
    }

    /**
     * Convert an SQLException using the current converter, doing some logging first.
     *
     * @param sqlException The exception to convert
     * @param message An error message.
     * @param sql The SQL being executed when the exception occurred
     *
     * @return The converted exception
     */
    public JDBCException convert(SQLException sqlException, String message, String sql) {
        logExceptions(sqlException, message + " [" + sql + "]");
        return sqlExceptionConverter.convert(sqlException, message, sql);
    }

    /**
     * Log the given (and any nested) exception.
     *
     * @param sqlException The exception to log
     * @param message The message text to use as a preamble.
     */
    public void logExceptions(SQLException sqlException, String message) {
        if (LOG.isEnabled(Level.ERROR)) {
            if (LOG.isDebugEnabled()) {
                message = StringHelper.isNotEmpty(message) ? message : DEFAULT_EXCEPTION_MSG;
                LOG.debug(message, sqlException);
            }
            final boolean warnEnabled = LOG.isEnabled(Level.WARN);

            List<String> previousWarnMessages = new ArrayList<>();
            List<String> previousErrorMessages = new ArrayList<>();

            while (sqlException != null) {
                if (warnEnabled) {
                    String warnMessage = "SQL Error: " + sqlException.getErrorCode() + ", SQLState: "
                            + sqlException.getSQLState();
                    if (!previousWarnMessages.contains(warnMessage)) {
                        LOG.warn(warnMessage);
                        previousWarnMessages.add(warnMessage);
                    }
                }
                if (!previousErrorMessages.contains(sqlException.getMessage())) {
                    LOG.error(sqlException.getMessage());
                    previousErrorMessages.add(sqlException.getMessage());
                }
                sqlException = sqlException.getNextException();
            }
        }
    }

    // SQLWarning ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    /**
     * Contract for handling {@link SQLWarning warnings}
     */
    public interface WarningHandler {
        /**
         * Should processing be done? Allows short-circuiting if not.
         *
         * @return True to process warnings, false otherwise.
         */
        boolean doProcess();

        /**
         * Prepare for processing of a {@link SQLWarning warning} stack.
         * <p/>
         * Note that the warning here is also the first passed to {@link #handleWarning}
         *
         * @param warning The first warning in the stack.
         */
        void prepare(SQLWarning warning);

        /**
         * Handle an individual warning in the stack.
         *
         * @param warning The warning to handle.
         */
        void handleWarning(SQLWarning warning);
    }

    /**
     * Basic support for {@link WarningHandler} implementations which handle {@link SQLWarning warnings}
     */
    public abstract static class WarningHandlerLoggingSupport implements WarningHandler {
        @Override
        public final void handleWarning(SQLWarning warning) {
            logWarning("SQL Warning Code: " + warning.getErrorCode() + ", SQLState: " + warning.getSQLState(),
                    warning.getMessage());
        }

        /**
         * Delegate to log common details of a {@link SQLWarning warning}
         *
         * @param description A description of the warning
         * @param message The warning message
         */
        protected abstract void logWarning(String description, String message);
    }

    /**
     * Standard SQLWarning handler for logging warnings
     */
    public static class StandardWarningHandler extends WarningHandlerLoggingSupport {
        private final String introMessage;

        /**
         * Creates a StandardWarningHandler
         *
         * @param introMessage The introduction message for the hierarchy
         */
        public StandardWarningHandler(String introMessage) {
            this.introMessage = introMessage;
        }

        @Override
        public boolean doProcess() {
            return LOG.isEnabled(Level.WARN);
        }

        @Override
        public void prepare(SQLWarning warning) {
            LOG.debug(introMessage, warning);
        }

        @Override
        protected void logWarning(String description, String message) {
            LOG.warn(description);
            LOG.warn(message);
        }
    }

    /**
     * Static access to the standard handler for logging warnings
     */
    public static final StandardWarningHandler STANDARD_WARNING_HANDLER = new StandardWarningHandler(
            DEFAULT_WARNING_MSG);

    /**
     * Generic algorithm to walk the hierarchy of SQLWarnings
     *
     * @param warning The warning to walk
     * @param handler The handler
     */
    public void walkWarnings(SQLWarning warning, WarningHandler handler) {
        if (warning == null || !handler.doProcess()) {
            return;
        }
        handler.prepare(warning);
        while (warning != null) {
            handler.handleWarning(warning);
            warning = warning.getNextWarning();
        }
    }

    /**
     * Standard (legacy) behavior for logging warnings associated with a JDBC {@link Connection} and clearing them.
     * <p/>
     * Calls {@link #handleAndClearWarnings(Connection, WarningHandler)} using {@link #STANDARD_WARNING_HANDLER}
     *
     * @param connection The JDBC connection potentially containing warnings
     */
    public void logAndClearWarnings(Connection connection) {
        handleAndClearWarnings(connection, STANDARD_WARNING_HANDLER);
    }

    public void logAndClearWarnings(Statement statement) {
        handleAndClearWarnings(statement, STANDARD_WARNING_HANDLER);
    }

    /**
     * General purpose handling of warnings associated with a JDBC {@link Connection}.
     *
     * @param connection The JDBC connection potentially containing warnings
     * @param handler The handler for each individual warning in the stack.
     *
     * @see #walkWarnings
     */
    @SuppressWarnings({ "ThrowableResultOfMethodCallIgnored" })
    public void handleAndClearWarnings(Connection connection, WarningHandler handler) {
        try {
            if (logWarnings) {
                walkWarnings(connection.getWarnings(), handler);
            }
        } catch (SQLException sqle) {
            // workaround for WebLogic
            LOG.debug("could not log warnings", sqle);
        }
        try {
            // Sybase fail if we don't do that, sigh...
            connection.clearWarnings();
        } catch (SQLException sqle) {
            LOG.debug("could not clear warnings", sqle);
        }
    }

    /**
     * General purpose handling of warnings associated with a JDBC {@link Statement}.
     *
     * @param statement The JDBC statement potentially containing warnings
     * @param handler The handler for each individual warning in the stack.
     *
     * @see #walkWarnings
     */
    @SuppressWarnings({ "ThrowableResultOfMethodCallIgnored" })
    public void handleAndClearWarnings(Statement statement, WarningHandler handler) {
        // See HHH-9174.  Statement#getWarnings can be an expensive call for many JDBC libs.  Don't do it unless
        // the log level would actually allow a warning to be logged.
        if (logWarnings) {
            try {
                walkWarnings(statement.getWarnings(), handler);
            } catch (SQLException sqlException) {
                // workaround for WebLogic
                LOG.debug("could not log warnings", sqlException);
            }
        }
        try {
            // Sybase fail if we don't do that, sigh...
            statement.clearWarnings();
        } catch (SQLException sqle) {
            LOG.debug("could not clear warnings", sqle);
        }
    }
}