View Javadoc
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  
23  import java.sql.Connection;
24  import java.sql.DatabaseMetaData;
25  import java.sql.SQLException;
26  import java.util.Collection;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.Map;
30  
31  import org.dbunit.DatabaseUnitRuntimeException;
32  import org.dbunit.database.DatabaseConfig;
33  import org.dbunit.database.IDatabaseConnection;
34  import org.dbunit.dataset.datatype.IDataTypeFactory;
35  import org.dbunit.dataset.datatype.IDbProductRelatable;
36  import org.dbunit.dataset.filter.IColumnFilter;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  /**
41   * @author Manuel Laflamme
42   * @author Last changed by: $Author$
43   * @version $Revision$ $Date$
44   * @since 1.0 (Mar 8, 2002)
45   */
46  public abstract class AbstractTableMetaData implements ITableMetaData
47  {
48  
49  	private Map _columnsToIndexes;
50  	
51      /**
52       * Logger for this class
53       */
54      private static final Logger logger = LoggerFactory.getLogger(AbstractTableMetaData.class);
55  
56      /**
57       * Default constructor
58       */
59      public AbstractTableMetaData()
60      {
61      }
62      
63      /**
64       * @param columns
65       * @param keyNames
66       * @return The primary key columns
67       * @deprecated since 2.3.0 - use {@link Columns#getColumns(String[], Column[])}
68       */
69      protected static Column[] getPrimaryKeys(Column[] columns, String[] keyNames)
70      {
71          logger.debug("getPrimaryKeys(columns={}, keyNames={}) - start", columns, keyNames);
72          return Columns.getColumns(keyNames, columns);
73      }
74  
75      /**
76       * @param tableName
77       * @param columns
78       * @param columnFilter
79       * @return The filtered primary key columns
80       * @deprecated since 2.3.0 - use {@link Columns#getColumns(String[], Column[])}
81       */
82      protected static Column[] getPrimaryKeys(String tableName, Column[] columns,
83              IColumnFilter columnFilter)
84      {
85      	if (logger.isDebugEnabled())
86      	{
87      		logger.debug("getPrimaryKeys(tableName={}, columns={}, columnFilter={}) - start",
88      				new Object[]{ tableName, columns, columnFilter });
89      	}
90      	return Columns.getColumns(tableName, columns, columnFilter);
91      }
92  
93  	/**
94  	 * Provides the index of the column with the given name within this table.
95  	 * Uses method {@link ITableMetaData#getColumns()} to retrieve all available columns.
96  	 * @throws DataSetException 
97  	 * @see org.dbunit.dataset.ITableMetaData#getColumnIndex(java.lang.String)
98  	 */
99  	public int getColumnIndex(String columnName) throws DataSetException 
100 	{
101         logger.debug("getColumnIndex(columnName={}) - start", columnName);
102 
103         if(this._columnsToIndexes == null) 
104 		{
105 			// lazily create the map
106 			this._columnsToIndexes = createColumnIndexesMap(this.getColumns());
107 		}
108 		
109         String columnNameUpperCase = columnName.toUpperCase();
110 		Integer colIndex = (Integer) this._columnsToIndexes.get(columnNameUpperCase);
111 		if(colIndex != null) 
112 		{
113 			return colIndex.intValue();
114 		}
115 		else 
116 		{
117 			throw new NoSuchColumnException(this.getTableName(), columnNameUpperCase,
118 					" (Non-uppercase input column: "+columnName+") in ColumnNameToIndexes cache map. " +
119 					"Note that the map's column names are NOT case sensitive.");
120 		}
121 	}
122 
123 	/**
124 	 * @param columns The columns to be put into the hash table
125 	 * @return A map having the key value pair [columnName, columnIndexInInputArray]
126 	 */
127 	private Map createColumnIndexesMap(Column[] columns) 
128 	{
129 		Map colsToIndexes = new HashMap(columns.length);
130 		for (int i = 0; i < columns.length; i++) 
131 		{
132 			colsToIndexes.put(columns[i].getColumnName().toUpperCase(), new Integer(i));
133 		}
134 		return colsToIndexes;
135 	}
136 
137 	/**
138 	 * Validates and returns the datatype factory of the given connection
139 	 * @param connection The connection providing the {@link IDataTypeFactory}
140 	 * @return The datatype factory of the given connection
141 	 * @throws SQLException
142 	 */
143 	public IDataTypeFactory getDataTypeFactory(IDatabaseConnection connection) 
144 	throws SQLException 
145 	{
146 		DatabaseConfig config = connection.getConfig();
147 		Object factoryObj = config.getProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY);
148 		if(!IDataTypeFactory.class.isAssignableFrom(factoryObj.getClass())) {
149 		    String msg = "Invalid datatype factory configured. Class '" + 
150                         factoryObj.getClass() + "' does not implement '" + IDataTypeFactory.class + "'.";
151 		    if(factoryObj instanceof String){
152 		        msg += " Ensure not to specify the fully qualified class name as String but the concrete " +
153 		        		"instance of the datatype factory (for example 'new OracleDataTypeFactory()').";
154 		    }
155 		    // TODO Would a "DatabaseUnitConfigurationException make more sense?
156 		    throw new DatabaseUnitRuntimeException(msg);
157 		}
158         IDataTypeFactory dataTypeFactory = (IDataTypeFactory)factoryObj;
159         
160     	// Validate, e.g. oracle metaData + oracleDataTypeFactory ==> OK
161         Connection jdbcConnection = connection.getConnection();
162         DatabaseMetaData metaData = jdbcConnection.getMetaData();
163     	String validationMessage = validateDataTypeFactory(dataTypeFactory, metaData);
164     	if(validationMessage!=null){
165             // Inform the user that we think he could get trouble with the current configuration
166             logger.warn("Potential problem found: " + validationMessage);
167     	}
168 
169     	return dataTypeFactory;
170 	}
171 
172 	/**
173 	 * Verifies that the data type factory supports the database product on the connection.
174 	 * If the data type factory is not valid for the connection, a warning is logged.
175 	 * @param dataTypeFactory The data type factory to validate.
176 	 * @param metaData The {@link DatabaseMetaData} needed to get the DB product name of the connection RDBMS.
177 	 * @return A validation message if there is a potential problem or <code>null</code> if everything is fine.
178 	 * @throws java.sql.SQLException A database problem.
179 	 */
180 	String validateDataTypeFactory(IDataTypeFactory dataTypeFactory, DatabaseMetaData metaData)
181 	throws SQLException
182 	{
183 	    if (!(dataTypeFactory instanceof IDbProductRelatable))
184 	    {
185 	        return null;
186 	    }
187 	    IDbProductRelatable productRelatable = (IDbProductRelatable) dataTypeFactory;
188 	    String databaseProductName = metaData.getDatabaseProductName();
189 
190 	    Collection validDbProductCollection = productRelatable.getValidDbProducts();
191 	    if (validDbProductCollection != null)
192 	    {
193 	        String lowerCaseDbProductName = databaseProductName.toLowerCase();
194 	        for (Iterator iterator = validDbProductCollection.iterator(); iterator.hasNext();) {
195 	            String validDbProduct = ((String) iterator.next()).toLowerCase();
196 	            if(lowerCaseDbProductName.indexOf(validDbProduct) > -1) {
197 	                logger.debug("The current database '{}' fits to the configured data type factory '{}'. Validation successful.",
198 	                        databaseProductName, dataTypeFactory);
199 	                return null;
200 	            }
201 	        }
202 	    }
203 
204 	    // If we get here, the validation failed
205 	    String validationMessage = "The configured data type factory '" + dataTypeFactory.getClass() +
206     	    "' might cause problems with the current database '" + databaseProductName +
207     	    "' (e.g. some datatypes may not be supported properly). " +
208     	    "In rare cases you might see this message because the list of supported database " +
209     	    "products is incomplete (list=" + validDbProductCollection + "). " +
210     	    "If so please request a java-class update via the forums." +
211     	    "If you are using your own IDataTypeFactory extending " +
212     	    "DefaultDataTypeFactory, ensure that you override getValidDbProducts() " +
213     	    "to specify the supported database products.";
214 	    return validationMessage;
215 	}
216 }