Package org.dbunit.util

Source Code of org.dbunit.util.SQLHelper$ExceptionWrapper

/*
*
* The DbUnit Database Testing Framework
* Copyright (C)2005, DbUnit.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*/

package org.dbunit.util;

import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Locale;

import org.dbunit.DatabaseUnitRuntimeException;
import org.dbunit.database.IMetadataHandler;
import org.dbunit.dataset.Column;
import org.dbunit.dataset.datatype.DataType;
import org.dbunit.dataset.datatype.DataTypeException;
import org.dbunit.dataset.datatype.IDataTypeFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Helper for SQL-related stuff.
* <br>
* TODO: testcases, also think about refactoring so that methods are not static anymore (for better extensibility)
* @author Felipe Leme (dbunit@felipeal.net)
* @version $Revision: 1134 $
* @since Nov 5, 2005
*
*/
public class SQLHelper {

    /**
     * The database product name reported by Sybase JDBC drivers.
     */
    public static final String DB_PRODUCT_SYBASE = "Sybase";
   
    /**
     * Logger for this class
     */
    private static final Logger logger = LoggerFactory.getLogger(SQLHelper.class);
 
    // class is "static"
    private SQLHelper() {}

    /**
     * Gets the primary column for a table.
     * @param conn connection with the database
     * @param table table name
     * @return name of primary column for a table (assuming it's just 1 column).
     * @throws SQLException raised while getting the meta data
     */
    public static String getPrimaryKeyColumn( Connection conn, String table ) throws SQLException {
        logger.debug("getPrimaryKeyColumn(conn={}, table={}) - start", conn, table);

        DatabaseMetaData metadata = conn.getMetaData();
        ResultSet rs = metadata.getPrimaryKeys( null, null, table );
        rs.next();
        String pkColumn = rs.getString(4);
        return pkColumn;   
    }

    /**
     * Close a result set and a prepared statement, checking for null references.
     * @param rs result set to be closed
     * @param stmt prepared statement to be closed
     * @throws SQLException exception raised in either close() method
     */
    public static void close(ResultSet rs, Statement stmt) throws SQLException {
        logger.debug("close(rs={}, stmt={}) - start", rs, stmt);

        try {
          SQLHelper.close(rs);
        } finally {
          SQLHelper.close( stmt );
        }   
    }

    /**
     * Close a SQL statement, checking for null references.
     * @param stmt statement to be closed
     * @throws SQLException exception raised while closing the statement
     */
    public static void close(Statement stmt) throws SQLException {
        logger.debug("close(stmt={}) - start", stmt);

        if ( stmt != null ) {
            stmt.close();
        }
    }

  /**
   * Closes the given result set in a null-safe way
   * @param resultSet
   * @throws SQLException
   */
  public static void close(ResultSet resultSet) throws SQLException {
        logger.debug("close(resultSet={}) - start", resultSet);
       
        if(resultSet != null) {
            resultSet.close();
        }
  }

    /**
     * Returns <code>true</code> if the given schema exists for the given connection.
     * @param connection The connection to a database
     * @param schema The schema to be searched
     * @return Returns <code>true</code> if the given schema exists for the given connection.
     * @throws SQLException
     * @since 2.3.0
     */
    public static boolean schemaExists(Connection connection, String schema)
    throws SQLException
    {
        logger.trace("schemaExists(connection={}, schema={}) - start", connection, schema);

        if(schema == null)
        {
            throw new NullPointerException("The parameter 'schema' must not be null");
        }
       
        DatabaseMetaData metaData = connection.getMetaData();
        ResultSet rs = metaData.getSchemas(); //null, schemaPattern);
        try
        {
            while(rs.next())
            {
                String foundSchema = rs.getString("TABLE_SCHEM");
                if(foundSchema.equals(schema))
                {
                    return true;
                }
            }
           
            // Especially for MySQL check the catalog
            if(catalogExists(connection, schema))
            {
                logger.debug("Found catalog with name {}. Returning true because DB is probably on MySQL", schema);
                return true;
            }
           
            return false;
        }
        finally
        {
            rs.close();
        }
    }

    /**
     * Checks via {@link DatabaseMetaData#getCatalogs()} whether or not the given catalog exists.
     * @param connection
     * @param catalog
     * @return
     * @throws SQLException
     * @since 2.4.4
     */
    private static boolean catalogExists(Connection connection, String catalog) throws SQLException
    {
        logger.trace("catalogExists(connection={}, catalog={}) - start", connection, catalog);

        if(catalog == null)
        {
            throw new NullPointerException("The parameter 'catalog' must not be null");
        }

        DatabaseMetaData metaData = connection.getMetaData();
        ResultSet rs = metaData.getCatalogs();
        try
        {
            while(rs.next())
            {
                String foundCatalog = rs.getString("TABLE_CAT");
                if(foundCatalog.equals(catalog))
                {
                    return true;
                }
            }
            return false;
        }
        finally
        {
            rs.close();
        }

    }
   
    /**
     * Checks if the given table exists.
     * @param metaData The database meta data
     * @param schema The schema in which the table should be searched. If <code>null</code>
     * the schema is not used to narrow the table name.
     * @param tableName The table name to be searched
     * @return Returns <code>true</code> if the given table exists in the given schema.
     * Else returns <code>false</code>.
     * @throws SQLException
     * @since 2.3.0
     * @deprecated since 2.4.5 - use {@link IMetadataHandler#tableExists(DatabaseMetaData, String, String)}
     */
    public static boolean tableExists(DatabaseMetaData metaData, String schema,
            String tableName)
    throws SQLException
    {
      ResultSet tableRs = metaData.getTables(null, schema, tableName, null);
        try
        {
            return tableRs.next();
        }
        finally
        {
          SQLHelper.close(tableRs);
        }
    }

    /**
     * Utility method for debugging to print all tables of the given metadata on the given stream
     * @param metaData
     * @param outputStream
     * @throws SQLException
     */
    public static void printAllTables(DatabaseMetaData metaData, PrintStream outputStream) throws SQLException
    {
        ResultSet rs = metaData.getTables(null, null, null, null);
        try
        {
          while (rs.next())
         {
            String catalog = rs.getString("TABLE_CAT");
            String schema = rs.getString("TABLE_SCHEM");
            String table = rs.getString("TABLE_NAME");
           StringBuffer tableInfo = new StringBuffer();
           if(catalog!=null) tableInfo.append(catalog).append(".");
           if(schema!=null) tableInfo.append(schema).append(".");
           tableInfo.append(table);
           // Print the info
           outputStream.println(tableInfo);
         }
          outputStream.flush();
        }
        finally
        {
          SQLHelper.close(rs);
        }
     
    }
   
    /**
     * Returns the database and JDBC driver information as pretty formatted string
     * @param metaData The JDBC database metadata needed to retrieve database information
     * @return The database information as formatted string
     */
    public static String getDatabaseInfo(DatabaseMetaData metaData)
    {
      StringBuffer sb = new StringBuffer();
      sb.append("\n");
     
      String dbInfo = null;
     
      dbInfo = new ExceptionWrapper(){
            public String wrappedCall(DatabaseMetaData metaData) throws Exception {
                return metaData.getDatabaseProductName();
            }
      }.executeWrappedCall(metaData);
        sb.append("\tdatabase product name=").append(dbInfo).append("\n");

        dbInfo = new ExceptionWrapper(){
            public String wrappedCall(DatabaseMetaData metaData) throws Exception {
                return metaData.getDatabaseProductVersion();
            }
        }.executeWrappedCall(metaData);
      sb.append("\tdatabase version=").append(dbInfo).append("\n");
     
      dbInfo = new ExceptionWrapper(){
            public String wrappedCall(DatabaseMetaData metaData) throws Exception {
                return String.valueOf(metaData.getDatabaseMajorVersion());
            }
        }.executeWrappedCall(metaData);
        sb.append("\tdatabase major version=").append(dbInfo).append("\n");

        dbInfo = new ExceptionWrapper(){
            public String wrappedCall(DatabaseMetaData metaData) throws Exception {
                return String.valueOf(metaData.getDatabaseMinorVersion());
            }
        }.executeWrappedCall(metaData);
        sb.append("\tdatabase minor version=").append(dbInfo).append("\n");
       
        dbInfo = new ExceptionWrapper(){
            public String wrappedCall(DatabaseMetaData metaData) throws Exception {
                return metaData.getDriverName();
            }
        }.executeWrappedCall(metaData);
      sb.append("\tjdbc driver name=").append(dbInfo).append("\n");
     
        dbInfo = new ExceptionWrapper(){
            public String wrappedCall(DatabaseMetaData metaData) throws Exception {
                return metaData.getDriverVersion();
            }
        }.executeWrappedCall(metaData);
        sb.append("\tjdbc driver version=").append(dbInfo).append("\n");

        dbInfo = new ExceptionWrapper(){
            public String wrappedCall(DatabaseMetaData metaData) throws Exception {
                return String.valueOf(metaData.getDriverMajorVersion());
            }
        }.executeWrappedCall(metaData);
        sb.append("\tjdbc driver major version=").append(dbInfo).append("\n");
     
        dbInfo = new ExceptionWrapper(){
            public String wrappedCall(DatabaseMetaData metaData) throws Exception {
                return String.valueOf(metaData.getDriverMinorVersion());
            }
        }.executeWrappedCall(metaData);
      sb.append("\tjdbc driver minor version=").append(dbInfo).append("\n");
     
      return sb.toString();
    }

    /**
     * Prints the database and JDBC driver information to the given output stream
     * @param metaData The JDBC database metadata needed to retrieve database information
     * @param outputStream The stream to which the information is printed
     * @throws SQLException
     */
    public static void printDatabaseInfo(DatabaseMetaData metaData, PrintStream outputStream) throws SQLException
    {
      String dbInfo = getDatabaseInfo(metaData);
      try {
        outputStream.println(dbInfo);
      }
      finally {
        outputStream.flush();
      }
    }

    /**
     * Detects whether or not the given metadata describes the connection to a Sybase database
     * or not.
     * @param metaData The metadata to be checked whether it is a Sybase connection
     * @return <code>true</code> if and only if the given metadata belongs to a Sybase database.
     * @throws SQLException
     */
    public static boolean isSybaseDb(DatabaseMetaData metaData) throws SQLException
    {
        String dbProductName = metaData.getDatabaseProductName();
        boolean isSybase = (dbProductName != null && dbProductName.equals(DB_PRODUCT_SYBASE));
        return isSybase;
    }

   
    /**
     * Utility method to create a {@link Column} object from a SQL {@link ResultSet} object.
     *
     * @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
     * @param dataTypeFactory The factory used to lookup the {@link DataType} for this column
     * @param datatypeWarning Whether or not a warning should be printed if the column could not
     * be created because of an unknown datatype.
     * @return The {@link Column} or <code>null</code> if the column could not be initialized because of an
     * unknown datatype.
     * @throws SQLException
     * @throws DataTypeException
     * @since 2.4.0
     */
    public static final Column createColumn(ResultSet resultSet,
            IDataTypeFactory dataTypeFactory, boolean datatypeWarning)
    throws SQLException, DataTypeException
    {
        String tableName = resultSet.getString(3);
        String columnName = resultSet.getString(4);
        int sqlType = resultSet.getInt(5);
        //If Types.DISTINCT like SQL DOMAIN, then get Source Date Type of SQL-DOMAIN
        if(sqlType == java.sql.Types.DISTINCT)
        {
            sqlType = resultSet.getInt("SOURCE_DATA_TYPE");
        }
       
        String sqlTypeName = resultSet.getString(6);
//        int columnSize = resultSet.getInt(7);
        int nullable = resultSet.getInt(11);
        String remarks = resultSet.getString(12);
        String columnDefaultValue = resultSet.getString(13);
        // This is only available since Java 5 - so we ca try it and if it does not work default it
        String isAutoIncrement = Column.AutoIncrement.NO.getKey();
        try {
            isAutoIncrement = resultSet.getString(23);
        }
        catch(SQLException e){
            if(logger.isDebugEnabled())
                logger.debug("Could not retrieve the 'isAutoIncrement' property because not yet running on Java 1.5 - defaulting to NO. " +
                        "Table=" + tableName + ", Column=" +columnName, e);
            // Ignore this one here
        }
       
        // Convert SQL type to DataType
        DataType dataType =
                dataTypeFactory.createDataType(sqlType, sqlTypeName, tableName, columnName);
        if (dataType != DataType.UNKNOWN)
        {
            Column column = new Column(columnName, dataType,
                    sqlTypeName, Column.nullableValue(nullable), columnDefaultValue, remarks,
                    Column.AutoIncrement.autoIncrementValue(isAutoIncrement));
            return column;
        }
        else
        {
            if (datatypeWarning)
                logger.warn(
                    tableName + "." + columnName +
                    " data type (" + sqlType + ", '" + sqlTypeName +
                    "') not recognized and will be ignored. See FAQ for more information.");
           
            // datatype unknown - column not created
            return null;
        }
    }

    /**
     * Checks if the given <code>resultSet</code> matches the given schema and table name.
     * The comparison is <b>case sensitive</b>.
     * @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
     * @param schema The name of the schema to check. If <code>null</code> it is ignored in the comparison
     * @param table The name of the table to check. If <code>null</code> it is ignored in the comparison
     * @param caseSensitive Whether or not the comparison should be case sensitive or not
     * @return <code>true</code> if the column metadata of the given <code>resultSet</code> matches
     * the given schema and table parameters.
     * @throws SQLException
     * @since 2.4.0
     * @deprecated since 2.4.4 - use {@link IMetadataHandler#matches(ResultSet, String, String, String, String, boolean)}
     */
    public static boolean matches(ResultSet resultSet,
            String schema, String table, boolean caseSensitive)
    throws SQLException
    {
        return matches(resultSet, null, schema, table, null, caseSensitive);
    }
   
   
    /**
     * Checks if the given <code>resultSet</code> matches the given schema and table name.
     * The comparison is <b>case sensitive</b>.
     * @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
     * @param catalog The name of the catalog to check. If <code>null</code> it is ignored in the comparison
     * @param schema The name of the schema to check. If <code>null</code> it is ignored in the comparison
     * @param table The name of the table to check. If <code>null</code> it is ignored in the comparison
     * @param column The name of the column to check. If <code>null</code> it is ignored in the comparison
     * @param caseSensitive Whether or not the comparison should be case sensitive or not
     * @return <code>true</code> if the column metadata of the given <code>resultSet</code> matches
     * the given schema and table parameters.
     * @throws SQLException
     * @since 2.4.0
     * @deprecated since 2.4.4 - use {@link IMetadataHandler#matches(ResultSet, String, String, String, String, boolean)}
     */
    public static boolean matches(ResultSet resultSet,
            String catalog, String schema,
            String table, String column, boolean caseSensitive)
    throws SQLException
    {
        String catalogName = resultSet.getString(1);
        String schemaName = resultSet.getString(2);
        String tableName = resultSet.getString(3);
        String columnName = resultSet.getString(4);

        // MYSQL provides only a catalog but no schema
        if(schema != null && schemaName == null && catalog==null && catalogName != null){
            logger.debug("Switching catalog/schema because the are mutually null");
            schemaName = catalogName;
            catalogName = null;
        }
       
        boolean areEqual =
                areEqualIgnoreNull(catalog, catalogName, caseSensitive) &&
                areEqualIgnoreNull(schema, schemaName, caseSensitive) &&
                areEqualIgnoreNull(table, tableName, caseSensitive) &&
                areEqualIgnoreNull(column, columnName, caseSensitive);
        return areEqual;
    }

    /**
     * Compares the given values and returns true if they are equal.
     * If the first value is <code>null</code> or empty String it always
     * returns <code>true</code> which is the way of ignoring <code>null</code>s
     * for this specific case.
     * @param value1 The first value to compare. Is ignored if null or empty String
     * @param value2 The second value to be compared
     * @return <code>true</code> if both values are equal or if the first value
     * is <code>null</code> or empty string.
     * @since 2.4.4
     */
    public static final boolean areEqualIgnoreNull(String value1, String value2, boolean caseSensitive)
    {
        if(value1==null || value1.equals(""))
        {
            return true;
        }
        else
        {
            if(caseSensitive && value1.equals(value2))
            {
                return true;
            }
            else if(!caseSensitive && value1.equalsIgnoreCase(value2))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }

    /**
     * Corrects the case of the given String according to the way in which the database stores metadata.
     * @param databaseIdentifier A database identifier such as a table name or a schema name for
     * which the case should be corrected.
     * @param connection The connection used to lookup the database metadata. This is needed to determine
     * the way in which the database stores its metadata.
     * @return The database identifier in the correct case for the RDBMS
     * @since 2.4.4
     */
    public static final String correctCase(final String databaseIdentifier, Connection connection)
    {
        logger.trace("correctCase(tableName={}, connection={}) - start", databaseIdentifier, connection);
       
        try
        {
            return correctCase(databaseIdentifier, connection.getMetaData());
        }
        catch (SQLException e)
        {
            throw new DatabaseUnitRuntimeException("Exception while trying to access database metadata", e);
        }
    }
   
    /**
     * Corrects the case of the given String according to the way in which the database stores metadata.
     * @param databaseIdentifier A database identifier such as a table name or a schema name for
     * which the case should be corrected.
     * @param databaseMetaData The database metadata needed to determine the way in which the database stores
     * its metadata.
     * @return The database identifier in the correct case for the RDBMS
     * @since 2.4.4
     */
    public static final String correctCase(final String databaseIdentifier, DatabaseMetaData databaseMetaData)
    {
        logger.trace("correctCase(tableName={}, databaseMetaData={}) - start", databaseIdentifier, databaseMetaData);
       
        if (databaseIdentifier == null) {
            throw new NullPointerException(
                    "The parameter 'databaseIdentifier' must not be null");
        }
        if (databaseMetaData == null) {
            throw new NullPointerException(
                    "The parameter 'databaseMetaData' must not be null");
        }
       
        try {
            String resultTableName = databaseIdentifier;
            String dbIdentifierQuoteString = databaseMetaData.getIdentifierQuoteString();
            if(!isEscaped(databaseIdentifier, dbIdentifierQuoteString)){
                if(databaseMetaData.storesLowerCaseIdentifiers())
                {
                    resultTableName = databaseIdentifier.toLowerCase(Locale.ENGLISH);
                }
                else if(databaseMetaData.storesUpperCaseIdentifiers())
                {
                    resultTableName = databaseIdentifier.toUpperCase(Locale.ENGLISH);
                }
                else
                {
                    logger.debug("Database does not store upperCase or lowerCase identifiers. " +
                            "Will not correct case of the table names.");
                }
            }
            else
            {
                if(logger.isDebugEnabled())
                    logger.debug("The tableName '{}' is escaped. Will not correct case.", databaseIdentifier);
            }
            return resultTableName;
        }
        catch (SQLException e)
        {
            throw new DatabaseUnitRuntimeException("Exception while trying to access database metadata", e);
        }
    }

    /**
     * Checks whether two given values are unequal and if so print a log message (level DEBUG)
     * @param oldValue The old value of a property
     * @param newValue The new value of a property
     * @param message The message to be logged
     * @param source The class which invokes this method - used for enriching the log message
     * @since 2.4.4
     */
    public static final void logInfoIfValueChanged(String oldValue, String newValue, String message, Class source)
    {
        if(logger.isInfoEnabled())
        {
            if(oldValue != null && !oldValue.equals(newValue))
                logger.debug("{}. {} oldValue={} newValue={}", new Object[] {source, message, oldValue, newValue});
        }
    }

    /**
     * Checks whether two given values are unequal and if so print a log message (level DEBUG)
     * @param oldValue The old value of a property
     * @param newValue The new value of a property
     * @param message The message to be logged
     * @param source The class which invokes this method - used for enriching the log message
     * @since 2.4.8
     */
    public static final void logDebugIfValueChanged(String oldValue, String newValue, String message, Class source)
    {
        if (logger.isDebugEnabled())
        {
            if (oldValue != null && !oldValue.equals(newValue))
                logger.debug("{}. {} oldValue={} newValue={}", new Object[] {source, message, oldValue, newValue});
        }
    }

    /**
     * @param tableName
     * @param dbIdentifierQuoteString
     * @return
     * @since 2.4.4
     */
    private static final boolean isEscaped(String tableName, String dbIdentifierQuoteString)
    {
        logger.trace("isEscaped(tableName={}, dbIdentifierQuoteString={}) - start", tableName, dbIdentifierQuoteString);
       
        if (dbIdentifierQuoteString == null) {
            throw new NullPointerException(
                    "The parameter 'dbIdentifierQuoteString' must not be null");
        }
        boolean isEscaped = tableName!=null && (tableName.startsWith(dbIdentifierQuoteString));
        if(logger.isDebugEnabled())
            logger.debug("isEscaped returns '{}' for tableName={} (dbIdentifierQuoteString={})",
                    new Object[]{Boolean.valueOf(isEscaped), tableName, dbIdentifierQuoteString} );
        return isEscaped;
    }

   
    /**
     * Performs a method invocation and catches all exceptions that occur during the invocation.
     * Utility which works similar to a closure, just a bit less elegant.
     * @author gommma (gommma AT users.sourceforge.net)
     * @author Last changed by: $Author: jbhurst $
     * @version $Revision: 1134 $ $Date: 2010-01-07 21:32:54 +0100 (gio, 07 gen 2010) $
     * @since 2.4.6
     */
    static abstract class ExceptionWrapper{

        public static final String NOT_AVAILABLE_TEXT = "<not available>";
       
        /**
         * Default constructor
         */
        public ExceptionWrapper()
        {
        }
       
        /**
         * Executes the call and catches all exception that might occur.
         * @param metaData
         * @return The result of the call
         */
        public final String executeWrappedCall(DatabaseMetaData metaData) {
            try{
                String result = wrappedCall(metaData);
                return result;
            }
            catch(Exception e){
                logger.trace("Problem retrieving DB information via DatabaseMetaData", e);
                return NOT_AVAILABLE_TEXT;
            }
        }
        /**
         * Calls the method that might throw an exception to be handled
         * @param metaData
         * @return The result of the call as human readable string
         * @throws Exception Any exception that might occur during the method invocation
         */
        public abstract String wrappedCall(DatabaseMetaData metaData) throws Exception;
    }

}
TOP

Related Classes of org.dbunit.util.SQLHelper$ExceptionWrapper

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.