AbstractTableMetaData.java

  1. /*
  2.  *
  3.  * The DbUnit Database Testing Framework
  4.  * Copyright (C)2002-2004, 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.dataset;

  22. import java.sql.Connection;
  23. import java.sql.DatabaseMetaData;
  24. import java.sql.SQLException;
  25. import java.util.Collection;
  26. import java.util.HashMap;
  27. import java.util.Iterator;
  28. import java.util.Map;

  29. import org.dbunit.DatabaseUnitRuntimeException;
  30. import org.dbunit.database.DatabaseConfig;
  31. import org.dbunit.database.IDatabaseConnection;
  32. import org.dbunit.dataset.datatype.IDataTypeFactory;
  33. import org.dbunit.dataset.datatype.IDbProductRelatable;
  34. import org.dbunit.dataset.filter.IColumnFilter;
  35. import org.slf4j.Logger;
  36. import org.slf4j.LoggerFactory;

  37. /**
  38.  * @author Manuel Laflamme
  39.  * @author Last changed by: $Author$
  40.  * @version $Revision$ $Date$
  41.  * @since 1.0 (Mar 8, 2002)
  42.  */
  43. public abstract class AbstractTableMetaData implements ITableMetaData
  44. {

  45.     private Map _columnsToIndexes;
  46.    
  47.     /**
  48.      * Logger for this class
  49.      */
  50.     private static final Logger logger = LoggerFactory.getLogger(AbstractTableMetaData.class);

  51.     /**
  52.      * Default constructor
  53.      */
  54.     public AbstractTableMetaData()
  55.     {
  56.     }
  57.    
  58.     /**
  59.      * @param columns
  60.      * @param keyNames
  61.      * @return The primary key columns
  62.      * @deprecated since 2.3.0 - use {@link Columns#getColumns(String[], Column[])}
  63.      */
  64.     protected static Column[] getPrimaryKeys(Column[] columns, String[] keyNames)
  65.     {
  66.         logger.debug("getPrimaryKeys(columns={}, keyNames={}) - start", columns, keyNames);
  67.         return Columns.getColumns(keyNames, columns);
  68.     }

  69.     /**
  70.      * @param tableName
  71.      * @param columns
  72.      * @param columnFilter
  73.      * @return The filtered primary key columns
  74.      * @deprecated since 2.3.0 - use {@link Columns#getColumns(String[], Column[])}
  75.      */
  76.     protected static Column[] getPrimaryKeys(String tableName, Column[] columns,
  77.             IColumnFilter columnFilter)
  78.     {
  79.         if (logger.isDebugEnabled())
  80.         {
  81.             logger.debug("getPrimaryKeys(tableName={}, columns={}, columnFilter={}) - start",
  82.                     tableName, columns, columnFilter);
  83.         }
  84.         return Columns.getColumns(tableName, columns, columnFilter);
  85.     }

  86.     /**
  87.      * Provides the index of the column with the given name within this table.
  88.      * Uses method {@link ITableMetaData#getColumns()} to retrieve all available columns.
  89.      * @throws DataSetException
  90.      * @see org.dbunit.dataset.ITableMetaData#getColumnIndex(java.lang.String)
  91.      */
  92.     public int getColumnIndex(String columnName) throws DataSetException
  93.     {
  94.         logger.debug("getColumnIndex(columnName={}) - start", columnName);

  95.         if(this._columnsToIndexes == null)
  96.         {
  97.             // lazily create the map
  98.             this._columnsToIndexes = createColumnIndexesMap(this.getColumns());
  99.         }
  100.        
  101.         String columnNameUpperCase = columnName.toUpperCase();
  102.         Integer colIndex = (Integer) this._columnsToIndexes.get(columnNameUpperCase);
  103.         if(colIndex != null)
  104.         {
  105.             return colIndex.intValue();
  106.         }
  107.         else
  108.         {
  109.             throw new NoSuchColumnException(this.getTableName(), columnNameUpperCase,
  110.                     " (Non-uppercase input column: " + columnName + ") in ColumnNameToIndexes cache map. " +
  111.                     "Note that the map's column names are NOT case sensitive.");
  112.         }
  113.     }

  114.     /**
  115.      * @param columns The columns to be put into the hash table
  116.      * @return A map having the key value pair [columnName, columnIndexInInputArray]
  117.      */
  118.     private Map createColumnIndexesMap(Column[] columns)
  119.     {
  120.         Map colsToIndexes = new HashMap(columns.length);
  121.         for (int i = 0; i < columns.length; i++)
  122.         {
  123.             colsToIndexes.put(columns[i].getColumnName().toUpperCase(), i);
  124.         }
  125.         return colsToIndexes;
  126.     }

  127.     /**
  128.      * Validates and returns the datatype factory of the given connection
  129.      * @param connection The connection providing the {@link IDataTypeFactory}
  130.      * @return The datatype factory of the given connection
  131.      * @throws SQLException
  132.      */
  133.     public IDataTypeFactory getDataTypeFactory(IDatabaseConnection connection)
  134.     throws SQLException
  135.     {
  136.         DatabaseConfig config = connection.getConfig();
  137.         Object factoryObj = config.getProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY);
  138.         if(!IDataTypeFactory.class.isAssignableFrom(factoryObj.getClass())) {
  139.             String msg = "Invalid datatype factory configured. Class '" +
  140.                         factoryObj.getClass() + "' does not implement '" + IDataTypeFactory.class + "'.";
  141.             if(factoryObj instanceof String){
  142.                 msg += " Ensure not to specify the fully qualified class name as String but the concrete " +
  143.                         "instance of the datatype factory (for example 'new OracleDataTypeFactory()').";
  144.             }
  145.             // TODO Would a "DatabaseUnitConfigurationException make more sense?
  146.             throw new DatabaseUnitRuntimeException(msg);
  147.         }
  148.         IDataTypeFactory dataTypeFactory = (IDataTypeFactory)factoryObj;
  149.        
  150.         // Validate, e.g. oracle metaData + oracleDataTypeFactory ==> OK
  151.         Connection jdbcConnection = connection.getConnection();
  152.         DatabaseMetaData metaData = jdbcConnection.getMetaData();
  153.         String validationMessage = validateDataTypeFactory(dataTypeFactory, metaData);
  154.         if(validationMessage!=null){
  155.             // Inform the user that we think he could get trouble with the current configuration
  156.             logger.warn("Potential problem found: " + validationMessage);
  157.         }

  158.         return dataTypeFactory;
  159.     }

  160.     /**
  161.      * Verifies that the data type factory supports the database product on the connection.
  162.      * If the data type factory is not valid for the connection, a warning is logged.
  163.      * @param dataTypeFactory The data type factory to validate.
  164.      * @param metaData The {@link DatabaseMetaData} needed to get the DB product name of the connection RDBMS.
  165.      * @return A validation message if there is a potential problem or <code>null</code> if everything is fine.
  166.      * @throws java.sql.SQLException A database problem.
  167.      */
  168.     String validateDataTypeFactory(IDataTypeFactory dataTypeFactory, DatabaseMetaData metaData)
  169.     throws SQLException
  170.     {
  171.         if (!(dataTypeFactory instanceof IDbProductRelatable))
  172.         {
  173.             return null;
  174.         }
  175.         IDbProductRelatable productRelatable = (IDbProductRelatable) dataTypeFactory;
  176.         String databaseProductName = metaData.getDatabaseProductName();

  177.         Collection validDbProductCollection = productRelatable.getValidDbProducts();
  178.         if (validDbProductCollection != null)
  179.         {
  180.             String lowerCaseDbProductName = databaseProductName.toLowerCase();
  181.             for (Iterator iterator = validDbProductCollection.iterator(); iterator.hasNext();) {
  182.                 String validDbProduct = ((String) iterator.next()).toLowerCase();
  183.                 if(lowerCaseDbProductName.indexOf(validDbProduct) > -1) {
  184.                     logger.debug("The current database '{}' fits to the configured data type factory '{}'. Validation successful.",
  185.                             databaseProductName, dataTypeFactory);
  186.                     return null;
  187.                 }
  188.             }
  189.         }

  190.         // If we get here, the validation failed
  191.         String validationMessage = "The configured data type factory '" + dataTypeFactory.getClass() +
  192.             "' might cause problems with the current database '" + databaseProductName +
  193.             "' (e.g. some datatypes may not be supported properly). " +
  194.             "In rare cases you might see this message because the list of supported database " +
  195.             "products is incomplete (list=" + validDbProductCollection + "). " +
  196.             "If so please request a java-class update via the forums." +
  197.             "If you are using your own IDataTypeFactory extending " +
  198.             "DefaultDataTypeFactory, ensure that you override getValidDbProducts() " +
  199.             "to specify the supported database products.";
  200.         return validationMessage;
  201.     }
  202. }