Package org.axonframework.eventstore.jpa

Source Code of org.axonframework.eventstore.jpa.SQLErrorCodesResolver

/*
* Copyright (c) 2010-2014. Axon Framework
*
* Licensed 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.axonframework.eventstore.jpa;

import org.axonframework.common.AxonConfigurationException;
import org.axonframework.common.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import javax.persistence.EntityExistsException;
import javax.sql.DataSource;

/**
* SQLErrorCodesResolver is an implementation of PersistenceExceptionResolver used to resolve sql error codes to see if
* it is an duplicate key constraint violation.
* <p/>
* SQL Code configuration is available for the following database engines, which may be identifier automatically via
* the data source: <ul>
* <li>HSQL DB</li>
* <li>MySQL</li>
* <li>Apache Derby</li>
* <li>DB2</li>
* <li>H2</li>
* <li>Informix Dynamic Server</li>
* <li>MS SQL Server</li>
* <li>Oracle</li>
* <li>PostgreSQL</li>
* <li>Sybase</li>
* </ul>
*
* @author Martin Tilma
* @author Allard Buijze
* @since 0.7
*/
public class SQLErrorCodesResolver implements org.axonframework.common.jdbc.PersistenceExceptionResolver {

    private static final Logger logger = LoggerFactory.getLogger(SQLErrorCodesResolver.class);
    private static final String SQL_ERROR_CODES_PROPERTIES = "SQLErrorCode.properties";
    private static final String PROPERTY_NAME_SUFFIX = ".duplicateKeyCodes";
    private static final String LIST_SEPARATOR = ",";

    private List<Integer> duplicateKeyCodes = Collections.emptyList();

    /**
     * Initializes the SQLErrorCodesResolver using the given list of SQL Codes representing Key Constraint Violations.
     *
     * @param duplicateKeyCodes A list of Integer containing SQL Codes representing Key Constraint Violations
     */
    public SQLErrorCodesResolver(List<Integer> duplicateKeyCodes) {
        this.duplicateKeyCodes = duplicateKeyCodes;
    }

    /**
     * Initialize a SQLErrorCodesResolver, automatically detecting the database name through the given dataSource. The
     * database product name is used to resolve the database error codes.
     *
     * @param dataSource The data source providing the information about the backing database.
     * @throws java.sql.SQLException      when retrieving the database product name fails
     * @throws AxonConfigurationException is the dataSource returns an unknown database product name. Use {@link
     *                                    #SQLErrorCodesResolver(java.util.Properties, javax.sql.DataSource)} instead.
     */
    public SQLErrorCodesResolver(DataSource dataSource) throws SQLException {
        Properties properties = loadDefaultPropertyFile();
        initialize(properties, getDatabaseProductNameFromDataSource(dataSource));
    }

    /**
     * Initialize a SQLErrorCodesResolver, automatically detecting the database name through the given dataSource. The
     * database product name is used to resolve the database error codes. As an alternative you could set the property
     * databaseDuplicateKeyCodes
     *
     * @param databaseProductName The product name of the database
     * @throws AxonConfigurationException is the dataSource returns an unknown database product name. Use {@link
     *                                    #SQLErrorCodesResolver(java.util.Properties, String)} instead.
     */
    public SQLErrorCodesResolver(String databaseProductName) {
        initialize(loadDefaultPropertyFile(), databaseProductName);
    }

    /**
     * Initialize a SQLErrorCodesResolver, automatically detecting the database name through the given dataSource. The
     * database product name is used to resolve the database error codes. As an alternative you could set the property
     * databaseDuplicateKeyCodes
     * <p/>
     * The form of the properties is expected to be:<br/>
     * <em><code>databaseName</code></em>.duplicateKeyCodes=<code><em>keyCode</em>[,<em>keyCode</em>]*</code><br/>
     * Where <em><code>databaseName</code></em> is the database product name as returned by the driver, with spaces ('
     * ') replaced by underscore ('_'). The key codes must be a comma separated list of SQL Error code numbers (int).
     *
     * @param properties          the properties defining SQL Error Codes for Duplicate Key violations for different
     *                            databases
     * @param databaseProductName The product name of the database
     */
    public SQLErrorCodesResolver(Properties properties, String databaseProductName) {
        initialize(properties, databaseProductName);
    }

    /**
     * Initialize the SQLErrorCodesResolver with the given <code>properties</code> and use the <code>dataSource</code>
     * to automatically retrieve the database product name.
     * <p/>
     * The form of the properties is expected to be:<br/>
     * <em><code>databaseName</code></em>.duplicateKeyCodes=<code><em>keyCode</em>[,<em>keyCode</em>]*</code><br/>
     * Where <em><code>databaseName</code></em> is the database product name as returned by the driver, with spaces ('
     * ') replaced by underscore ('_'). The key codes must be a comma separated list of SQL Error code numbers (int).
     *
     * @param properties the properties defining SQL Error Codes for Duplicate Key violations for different databases
     * @param dataSource The data source providing the database product name
     * @throws java.sql.SQLException when retrieving the database product name fails
     */
    public SQLErrorCodesResolver(Properties properties, DataSource dataSource) throws SQLException {
        initialize(properties, getDatabaseProductNameFromDataSource(dataSource));
    }

    private void initialize(Properties properties, String databaseProductName) {
        duplicateKeyCodes = loadKeyViolationCodes(databaseProductName, properties);
    }

    @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"})
    @Override
    public boolean isDuplicateKeyViolation(Exception exception) {
        if (causeIsEntityExistsException(exception)) {
            return true;
        }
        SQLException sqlException = findSQLException(exception);
        boolean isDuplicateKey = false;
        if (sqlException != null) {
            isDuplicateKey = duplicateKeyCodes.contains(sqlException.getErrorCode());
        }
        return isDuplicateKey;
    }

    private boolean causeIsEntityExistsException(Throwable exception) {
        return exception instanceof EntityExistsException
                || (exception.getCause() != null && causeIsEntityExistsException(exception.getCause()));
    }

    private SQLException findSQLException(Throwable exception) {
        SQLException sqlException = null;
        while (sqlException == null && exception != null) {
            if (exception instanceof SQLException) {
                sqlException = (SQLException) exception;
            } else {
                exception = exception.getCause();
            }
        }

        return sqlException;
    }

    private String getDatabaseProductNameFromDataSource(DataSource dataSource) throws SQLException {
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
            return connection.getMetaData().getDatabaseProductName();
        } finally {
            try {
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                // we did our best.
                logger.warn("An error occurred while trying to close the database connection. Ignoring...", e);
            }
        }
    }

    private List<Integer> loadKeyViolationCodes(String databaseProductName, Properties properties) {
        String key = databaseProductName.replaceAll(" ", "_") + PROPERTY_NAME_SUFFIX;
        String property = properties.getProperty(key);

        List<Integer> keyCodes = new ArrayList<Integer>();

        if (property == null) {
            throw new AxonConfigurationException(String.format(
                    "The database product name '%s' is unknown. No SQLCode configuration is known for that database.",
                    databaseProductName));
        }
        String[] codes = property.split(LIST_SEPARATOR);
        for (String code : codes) {
            keyCodes.add(Integer.valueOf(code));
        }

        return keyCodes;
    }

    private Properties loadDefaultPropertyFile() {
        Properties properties = new Properties();
        InputStream resources = null;
        try {
            resources = SQLErrorCodesResolver.class.getResourceAsStream(SQL_ERROR_CODES_PROPERTIES);
            properties.load(resources);
        } catch (IOException e) {
            throw new AxonConfigurationException("Unable to read from a file that should be ", e);
        } finally {
            IOUtils.closeQuietly(resources);
        }
        return properties;
    }
}
TOP

Related Classes of org.axonframework.eventstore.jpa.SQLErrorCodesResolver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.