SQLHelper.java

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

  21. package org.dbunit.util;

  22. import java.io.PrintStream;
  23. import java.sql.Connection;
  24. import java.sql.DatabaseMetaData;
  25. import java.sql.ResultSet;
  26. import java.sql.SQLException;
  27. import java.sql.Statement;
  28. import java.util.Locale;

  29. import org.dbunit.DatabaseUnitRuntimeException;
  30. import org.dbunit.database.IMetadataHandler;
  31. import org.dbunit.dataset.Column;
  32. import org.dbunit.dataset.datatype.DataType;
  33. import org.dbunit.dataset.datatype.DataTypeException;
  34. import org.dbunit.dataset.datatype.IDataTypeFactory;
  35. import org.slf4j.Logger;
  36. import org.slf4j.LoggerFactory;

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

  47.     /**
  48.      * The database product name reported by Sybase JDBC drivers.
  49.      */
  50.     public static final String DB_PRODUCT_SYBASE = "Sybase";

  51.     /**
  52.      * Logger for this class
  53.      */
  54.     private static final Logger logger = LoggerFactory.getLogger(SQLHelper.class);

  55.     // class is "static"
  56.     private SQLHelper() {}

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

  66.         DatabaseMetaData metadata = conn.getMetaData();
  67.         ResultSet rs = metadata.getPrimaryKeys( null, null, table );
  68.         rs.next();
  69.         String pkColumn = rs.getString(4);
  70.         return pkColumn;
  71.     }

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

  80.         try {
  81.             SQLHelper.close(rs);
  82.         } finally {
  83.             SQLHelper.close( stmt );
  84.         }
  85.     }

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

  93.         if ( stmt != null ) {
  94.             stmt.close();
  95.         }
  96.     }

  97.     /**
  98.      * Closes the given result set in a null-safe way
  99.      * @param resultSet
  100.      * @throws SQLException
  101.      */
  102.     public static void close(ResultSet resultSet) throws SQLException {
  103.         logger.debug("close(resultSet={}) - start", resultSet);

  104.         if(resultSet != null) {
  105.             resultSet.close();
  106.         }
  107.     }

  108.     /**
  109.      * Returns <code>true</code> if the given schema exists for the given connection.
  110.      * @param connection The connection to a database
  111.      * @param schema The schema to be searched
  112.      * @return Returns <code>true</code> if the given schema exists for the given connection.
  113.      * @throws SQLException
  114.      * @since 2.3.0
  115.      */
  116.     public static boolean schemaExists(Connection connection, String schema)
  117.             throws SQLException
  118.             {
  119.         logger.trace("schemaExists(connection={}, schema={}) - start", connection, schema);

  120.         if(schema == null)
  121.         {
  122.             throw new NullPointerException("The parameter 'schema' must not be null");
  123.         }

  124.         DatabaseMetaData metaData = connection.getMetaData();
  125.         ResultSet rs = metaData.getSchemas(); //null, schemaPattern);
  126.         try
  127.         {
  128.             while(rs.next())
  129.             {
  130.                 String foundSchema = rs.getString("TABLE_SCHEM");
  131.                 if(foundSchema.equals(schema))
  132.                 {
  133.                     return true;
  134.                 }
  135.             }

  136.             // Especially for MySQL check the catalog
  137.             if(catalogExists(connection, schema))
  138.             {
  139.                 logger.debug("Found catalog with name {}. Returning true because DB is probably on MySQL", schema);
  140.                 return true;
  141.             }

  142.             return false;
  143.         }
  144.         finally
  145.         {
  146.             rs.close();
  147.         }
  148.             }

  149.     /**
  150.      * Checks via {@link DatabaseMetaData#getCatalogs()} whether or not the given catalog exists.
  151.      * @param connection
  152.      * @param catalog
  153.      * @return
  154.      * @throws SQLException
  155.      * @since 2.4.4
  156.      */
  157.     private static boolean catalogExists(Connection connection, String catalog) throws SQLException
  158.     {
  159.         logger.trace("catalogExists(connection={}, catalog={}) - start", connection, catalog);

  160.         if(catalog == null)
  161.         {
  162.             throw new NullPointerException("The parameter 'catalog' must not be null");
  163.         }

  164.         DatabaseMetaData metaData = connection.getMetaData();
  165.         ResultSet rs = metaData.getCatalogs();
  166.         try
  167.         {
  168.             while(rs.next())
  169.             {
  170.                 String foundCatalog = rs.getString("TABLE_CAT");
  171.                 if(foundCatalog.equals(catalog))
  172.                 {
  173.                     return true;
  174.                 }
  175.             }
  176.             return false;
  177.         }
  178.         finally
  179.         {
  180.             rs.close();
  181.         }

  182.     }

  183.     /**
  184.      * Checks if the given table exists.
  185.      * @param metaData The database meta data
  186.      * @param schema The schema in which the table should be searched. If <code>null</code>
  187.      * the schema is not used to narrow the table name.
  188.      * @param tableName The table name to be searched
  189.      * @return Returns <code>true</code> if the given table exists in the given schema.
  190.      * Else returns <code>false</code>.
  191.      * @throws SQLException
  192.      * @since 2.3.0
  193.      * @deprecated since 2.4.5 - use {@link IMetadataHandler#tableExists(DatabaseMetaData, String, String)}
  194.      */
  195.     public static boolean tableExists(DatabaseMetaData metaData, String schema,
  196.             String tableName)
  197.                     throws SQLException
  198.                     {
  199.         ResultSet tableRs = metaData.getTables(null, schema, tableName, null);
  200.         try
  201.         {
  202.             return tableRs.next();
  203.         }
  204.         finally
  205.         {
  206.             SQLHelper.close(tableRs);
  207.         }
  208.                     }

  209.     /**
  210.      * Utility method for debugging to print all tables of the given metadata on the given stream
  211.      * @param metaData
  212.      * @param outputStream
  213.      * @throws SQLException
  214.      */
  215.     public static void printAllTables(DatabaseMetaData metaData, PrintStream outputStream) throws SQLException
  216.     {
  217.         ResultSet rs = metaData.getTables(null, null, null, null);
  218.         try
  219.         {
  220.             while (rs.next())
  221.             {
  222.                 String catalog = rs.getString("TABLE_CAT");
  223.                 String schema = rs.getString("TABLE_SCHEM");
  224.                 String table = rs.getString("TABLE_NAME");
  225.                 final StringBuilder tableInfo = new StringBuilder();
  226.                 if(catalog!=null) tableInfo.append(catalog).append(".");
  227.                 if(schema!=null) tableInfo.append(schema).append(".");
  228.                 tableInfo.append(table);
  229.                 // Print the info
  230.                 outputStream.println(tableInfo);
  231.             }
  232.             outputStream.flush();
  233.         }
  234.         finally
  235.         {
  236.             SQLHelper.close(rs);
  237.         }

  238.     }

  239.     /**
  240.      * Returns the database and JDBC driver information as pretty formatted string
  241.      * @param metaData The JDBC database metadata needed to retrieve database information
  242.      * @return The database information as formatted string
  243.      */
  244.     public static String getDatabaseInfo(DatabaseMetaData metaData)
  245.     {
  246.         final StringBuilder sb = new StringBuilder();
  247.         sb.append("\n");

  248.         String dbInfo = null;

  249.         dbInfo = new ExceptionWrapper(){
  250.             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
  251.                 return metaData.getDatabaseProductName();
  252.             }
  253.         }.executeWrappedCall(metaData);
  254.         sb.append("\tdatabase product name=").append(dbInfo).append("\n");

  255.         dbInfo = new ExceptionWrapper(){
  256.             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
  257.                 return metaData.getDatabaseProductVersion();
  258.             }
  259.         }.executeWrappedCall(metaData);
  260.         sb.append("\tdatabase version=").append(dbInfo).append("\n");

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

  267.         dbInfo = new ExceptionWrapper(){
  268.             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
  269.                 return String.valueOf(metaData.getDatabaseMinorVersion());
  270.             }
  271.         }.executeWrappedCall(metaData);
  272.         sb.append("\tdatabase minor version=").append(dbInfo).append("\n");

  273.         dbInfo = new ExceptionWrapper(){
  274.             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
  275.                 return metaData.getDriverName();
  276.             }
  277.         }.executeWrappedCall(metaData);
  278.         sb.append("\tjdbc driver name=").append(dbInfo).append("\n");

  279.         dbInfo = new ExceptionWrapper(){
  280.             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
  281.                 return metaData.getDriverVersion();
  282.             }
  283.         }.executeWrappedCall(metaData);
  284.         sb.append("\tjdbc driver version=").append(dbInfo).append("\n");

  285.         dbInfo = new ExceptionWrapper(){
  286.             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
  287.                 return String.valueOf(metaData.getDriverMajorVersion());
  288.             }
  289.         }.executeWrappedCall(metaData);
  290.         sb.append("\tjdbc driver major version=").append(dbInfo).append("\n");

  291.         dbInfo = new ExceptionWrapper(){
  292.             public String wrappedCall(DatabaseMetaData metaData) throws Exception {
  293.                 return String.valueOf(metaData.getDriverMinorVersion());
  294.             }
  295.         }.executeWrappedCall(metaData);
  296.         sb.append("\tjdbc driver minor version=").append(dbInfo).append("\n");

  297.         return sb.toString();
  298.     }

  299.     /**
  300.      * Prints the database and JDBC driver information to the given output stream
  301.      * @param metaData The JDBC database metadata needed to retrieve database information
  302.      * @param outputStream The stream to which the information is printed
  303.      * @throws SQLException
  304.      */
  305.     public static void printDatabaseInfo(DatabaseMetaData metaData, PrintStream outputStream) throws SQLException
  306.     {
  307.         String dbInfo = getDatabaseInfo(metaData);
  308.         try {
  309.             outputStream.println(dbInfo);
  310.         }
  311.         finally {
  312.             outputStream.flush();
  313.         }
  314.     }

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


  328.     /**
  329.      * Utility method to create a {@link Column} object from a SQL {@link ResultSet} object.
  330.      *
  331.      * @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
  332.      * @param dataTypeFactory The factory used to lookup the {@link DataType} for this column
  333.      * @param datatypeWarning Whether or not a warning should be printed if the column could not
  334.      * be created because of an unknown datatype.
  335.      * @return The {@link Column} or <code>null</code> if the column could not be initialized because of an
  336.      * unknown datatype.
  337.      * @throws SQLException
  338.      * @throws DataTypeException
  339.      * @since 2.4.0
  340.      */
  341.     public static final Column createColumn(ResultSet resultSet,
  342.             IDataTypeFactory dataTypeFactory, boolean datatypeWarning)
  343.                     throws SQLException, DataTypeException
  344.                     {
  345.         String tableName = resultSet.getString(3);
  346.         String columnName = resultSet.getString(4);
  347.         int sqlType = resultSet.getInt(5);
  348.         //If Types.DISTINCT like SQL DOMAIN, then get Source Date Type of SQL-DOMAIN
  349.         if(sqlType == java.sql.Types.DISTINCT)
  350.         {
  351.             sqlType = resultSet.getInt("SOURCE_DATA_TYPE");
  352.         }

  353.         String sqlTypeName = resultSet.getString(6);
  354.         //        int columnSize = resultSet.getInt(7);
  355.         int nullable = resultSet.getInt(11);
  356.         String remarks = resultSet.getString(12);
  357.         String columnDefaultValue = resultSet.getString(13);
  358.         String isAutoIncrement = resultSet.getString(23);
  359.         // some JDBC drivers do not have this column even though they claim to be compliant with JDBC 4.1 or later
  360.         String isGenerated = resultSet.getMetaData().getColumnCount() >= 24 ? resultSet.getString(24) : null;

  361.         // Convert SQL type to DataType
  362.         DataType dataType =
  363.                 dataTypeFactory.createDataType(sqlType, sqlTypeName, tableName, columnName);
  364.         if (dataType != DataType.UNKNOWN)
  365.         {
  366.             Column column = new Column(columnName, dataType,
  367.                     sqlTypeName, Column.nullableValue(nullable), columnDefaultValue, remarks,
  368.                     Column.AutoIncrement.autoIncrementValue(isAutoIncrement),
  369.                     Column.convertMetaDataBoolean(isGenerated));
  370.             return column;
  371.         }
  372.         else
  373.         {
  374.             if (datatypeWarning)
  375.                 logger.warn(
  376.                         tableName + "." + columnName +
  377.                         " data type (" + sqlType + ", '" + sqlTypeName +
  378.                         "') not recognized and will be ignored. See FAQ for more information.");

  379.             // datatype unknown - column not created
  380.             return null;
  381.         }
  382.                     }

  383.     /**
  384.      * Checks if the given <code>resultSet</code> matches the given schema and table name.
  385.      * The comparison is <b>case sensitive</b>.
  386.      * @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
  387.      * @param schema The name of the schema to check. If <code>null</code> it is ignored in the comparison
  388.      * @param table The name of the table to check. If <code>null</code> it is ignored in the comparison
  389.      * @param caseSensitive Whether or not the comparison should be case sensitive or not
  390.      * @return <code>true</code> if the column metadata of the given <code>resultSet</code> matches
  391.      * the given schema and table parameters.
  392.      * @throws SQLException
  393.      * @since 2.4.0
  394.      * @deprecated since 2.4.4 - use {@link IMetadataHandler#matches(ResultSet, String, String, String, String, boolean)}
  395.      */
  396.     public static boolean matches(ResultSet resultSet,
  397.             String schema, String table, boolean caseSensitive)
  398.                     throws SQLException
  399.                     {
  400.         return matches(resultSet, null, schema, table, null, caseSensitive);
  401.                     }


  402.     /**
  403.      * Checks if the given <code>resultSet</code> matches the given schema and table name.
  404.      * The comparison is <b>case sensitive</b>.
  405.      * @param resultSet A result set produced via {@link DatabaseMetaData#getColumns(String, String, String, String)}
  406.      * @param catalog The name of the catalog to check. If <code>null</code> it is ignored in the comparison
  407.      * @param schema The name of the schema to check. If <code>null</code> it is ignored in the comparison
  408.      * @param table The name of the table to check. If <code>null</code> it is ignored in the comparison
  409.      * @param column The name of the column to check. If <code>null</code> it is ignored in the comparison
  410.      * @param caseSensitive Whether or not the comparison should be case sensitive or not
  411.      * @return <code>true</code> if the column metadata of the given <code>resultSet</code> matches
  412.      * the given schema and table parameters.
  413.      * @throws SQLException
  414.      * @since 2.4.0
  415.      * @deprecated since 2.4.4 - use {@link IMetadataHandler#matches(ResultSet, String, String, String, String, boolean)}
  416.      */
  417.     public static boolean matches(ResultSet resultSet,
  418.             String catalog, String schema,
  419.             String table, String column, boolean caseSensitive)
  420.                     throws SQLException
  421.                     {
  422.         String catalogName = resultSet.getString(1);
  423.         String schemaName = resultSet.getString(2);
  424.         String tableName = resultSet.getString(3);
  425.         String columnName = resultSet.getString(4);

  426.         // MYSQL provides only a catalog but no schema
  427.         if(schema != null && schemaName == null && catalog==null && catalogName != null){
  428.             logger.debug("Switching catalog/schema because the are mutually null");
  429.             schemaName = catalogName;
  430.             catalogName = null;
  431.         }

  432.         boolean areEqual =
  433.                 areEqualIgnoreNull(catalog, catalogName, caseSensitive) &&
  434.                 areEqualIgnoreNull(schema, schemaName, caseSensitive) &&
  435.                 areEqualIgnoreNull(table, tableName, caseSensitive) &&
  436.                 areEqualIgnoreNull(column, columnName, caseSensitive);
  437.         return areEqual;
  438.                     }

  439.     /**
  440.      * Compares the given values and returns true if they are equal.
  441.      * If the first value is <code>null</code> or empty String it always
  442.      * returns <code>true</code> which is the way of ignoring <code>null</code>s
  443.      * for this specific case.
  444.      * @param value1 The first value to compare. Is ignored if null or empty String
  445.      * @param value2 The second value to be compared
  446.      * @return <code>true</code> if both values are equal or if the first value
  447.      * is <code>null</code> or empty string.
  448.      * @since 2.4.4
  449.      */
  450.     public static final boolean areEqualIgnoreNull(String value1, String value2, boolean caseSensitive)
  451.     {
  452.         if(value1==null || value1.equals(""))
  453.         {
  454.             return true;
  455.         }
  456.         else
  457.         {
  458.             if(caseSensitive && value1.equals(value2))
  459.             {
  460.                 return true;
  461.             }
  462.             else if(!caseSensitive && value1.equalsIgnoreCase(value2))
  463.             {
  464.                 return true;
  465.             }
  466.             else
  467.             {
  468.                 return false;
  469.             }
  470.         }
  471.     }

  472.     /**
  473.      * Corrects the case of the given String according to the way in which the database stores metadata.
  474.      * @param databaseIdentifier A database identifier such as a table name or a schema name for
  475.      * which the case should be corrected.
  476.      * @param connection The connection used to lookup the database metadata. This is needed to determine
  477.      * the way in which the database stores its metadata.
  478.      * @return The database identifier in the correct case for the RDBMS
  479.      * @since 2.4.4
  480.      */
  481.     public static final String correctCase(final String databaseIdentifier, Connection connection)
  482.     {
  483.         logger.trace("correctCase(tableName={}, connection={}) - start", databaseIdentifier, connection);

  484.         try
  485.         {
  486.             return correctCase(databaseIdentifier, connection.getMetaData());
  487.         }
  488.         catch (SQLException e)
  489.         {
  490.             throw new DatabaseUnitRuntimeException("Exception while trying to access database metadata", e);
  491.         }
  492.     }

  493.     /**
  494.      * Corrects the case of the given String according to the way in which the database stores metadata.
  495.      * @param databaseIdentifier A database identifier such as a table name or a schema name for
  496.      * which the case should be corrected.
  497.      * @param databaseMetaData The database metadata needed to determine the way in which the database stores
  498.      * its metadata.
  499.      * @return The database identifier in the correct case for the RDBMS
  500.      * @since 2.4.4
  501.      */
  502.     public static final String correctCase(final String databaseIdentifier, DatabaseMetaData databaseMetaData)
  503.     {
  504.         logger.trace("correctCase(tableName={}, databaseMetaData={}) - start", databaseIdentifier, databaseMetaData);

  505.         if (databaseIdentifier == null) {
  506.             throw new NullPointerException(
  507.                     "The parameter 'databaseIdentifier' must not be null");
  508.         }
  509.         if (databaseMetaData == null) {
  510.             throw new NullPointerException(
  511.                     "The parameter 'databaseMetaData' must not be null");
  512.         }

  513.         try {
  514.             String resultTableName = databaseIdentifier;
  515.             String dbIdentifierQuoteString = databaseMetaData.getIdentifierQuoteString();
  516.             if(!isEscaped(databaseIdentifier, dbIdentifierQuoteString)){
  517.                 if(databaseMetaData.storesLowerCaseIdentifiers())
  518.                 {
  519.                     resultTableName = databaseIdentifier.toLowerCase(Locale.ENGLISH);
  520.                 }
  521.                 else if(databaseMetaData.storesUpperCaseIdentifiers())
  522.                 {
  523.                     resultTableName = databaseIdentifier.toUpperCase(Locale.ENGLISH);
  524.                 }
  525.                 else
  526.                 {
  527.                     logger.debug("Database does not store upperCase or lowerCase identifiers. " +
  528.                             "Will not correct case of the table names.");
  529.                 }
  530.             }
  531.             else
  532.             {
  533.                 if(logger.isDebugEnabled())
  534.                     logger.debug("The tableName '{}' is escaped. Will not correct case.", databaseIdentifier);
  535.                 }
  536.             return resultTableName;
  537.         }
  538.         catch (SQLException e)
  539.         {
  540.             throw new DatabaseUnitRuntimeException("Exception while trying to access database metadata", e);
  541.         }
  542.     }

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

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

  575.     /**
  576.      * @param tableName
  577.      * @param dbIdentifierQuoteString
  578.      * @return
  579.      * @since 2.4.4
  580.      */
  581.     private static final boolean isEscaped(String tableName, String dbIdentifierQuoteString)
  582.     {
  583.         logger.trace("isEscaped(tableName={}, dbIdentifierQuoteString={}) - start", tableName, dbIdentifierQuoteString);

  584.         if (dbIdentifierQuoteString == null) {
  585.             throw new NullPointerException(
  586.                     "The parameter 'dbIdentifierQuoteString' must not be null");
  587.         }
  588.         boolean isEscaped = tableName!=null && (tableName.startsWith(dbIdentifierQuoteString));
  589.         if(logger.isDebugEnabled())
  590.             logger.debug("isEscaped returns '{}' for tableName={} (dbIdentifierQuoteString={})",
  591.                     new Object[]{Boolean.valueOf(isEscaped), tableName, dbIdentifierQuoteString} );
  592.         return isEscaped;
  593.     }

  594.     /**
  595.      * Performs a method invocation and catches all exceptions that occur during the invocation.
  596.      * Utility which works similar to a closure, just a bit less elegant.
  597.      * @author gommma (gommma AT users.sourceforge.net)
  598.      * @author Last changed by: $Author$
  599.      * @version $Revision$ $Date$
  600.      * @since 2.4.6
  601.      */
  602.     static abstract class ExceptionWrapper{

  603.         public static final String NOT_AVAILABLE_TEXT = "<not available>";

  604.         /**
  605.          * Default constructor
  606.          */
  607.         public ExceptionWrapper()
  608.         {
  609.         }

  610.         /**
  611.          * Executes the call and catches all exception that might occur.
  612.          * @param metaData
  613.          * @return The result of the call
  614.          */
  615.         public final String executeWrappedCall(DatabaseMetaData metaData) {
  616.             try{
  617.                 String result = wrappedCall(metaData);
  618.                 return result;
  619.             }
  620.             catch(Exception e){
  621.                 logger.trace("Problem retrieving DB information via DatabaseMetaData", e);
  622.                 return NOT_AVAILABLE_TEXT;
  623.             }
  624.         }
  625.         /**
  626.          * Calls the method that might throw an exception to be handled
  627.          * @param metaData
  628.          * @return The result of the call as human readable string
  629.          * @throws Exception Any exception that might occur during the method invocation
  630.          */
  631.         public abstract String wrappedCall(DatabaseMetaData metaData) throws Exception;
  632.     }

  633. }