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.database;
22  
23  import java.sql.SQLException;
24  import java.sql.Statement;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.Properties;
29  
30  import org.dbunit.DatabaseUnitException;
31  import org.dbunit.database.statement.IStatementFactory;
32  import org.dbunit.database.statement.PreparedStatementFactory;
33  import org.dbunit.dataset.datatype.DefaultDataTypeFactory;
34  import org.dbunit.dataset.datatype.IDataTypeFactory;
35  import org.dbunit.dataset.filter.IColumnFilter;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  /**
40   * Configuration used by the {@link DatabaseConnection}.
41   * 
42   * @author manuel.laflamme
43   * @author gommma (gommma AT users.sourceforge.net)
44   * @author Last changed by: $Author$
45   * @version $Revision$ $Date$
46   * @since 2.0
47   */
48  public class DatabaseConfig
49  {
50  
51      /**
52       * Logger for this class
53       */
54      private static final Logger logger = LoggerFactory.getLogger(DatabaseConfig.class);
55  
56      public static final String PROPERTY_STATEMENT_FACTORY =
57              "http://www.dbunit.org/properties/statementFactory";
58      public static final String PROPERTY_RESULTSET_TABLE_FACTORY =
59              "http://www.dbunit.org/properties/resultSetTableFactory";
60      public static final String PROPERTY_DATATYPE_FACTORY =
61              "http://www.dbunit.org/properties/datatypeFactory";
62      public static final String PROPERTY_ESCAPE_PATTERN =
63              "http://www.dbunit.org/properties/escapePattern";
64      public static final String PROPERTY_TABLE_TYPE =
65              "http://www.dbunit.org/properties/tableType";
66      public static final String PROPERTY_PRIMARY_KEY_FILTER =
67              "http://www.dbunit.org/properties/primaryKeyFilter";
68      public static final String PROPERTY_BATCH_SIZE =
69      		"http://www.dbunit.org/properties/batchSize";
70  	public static final String PROPERTY_FETCH_SIZE = 
71  			"http://www.dbunit.org/properties/fetchSize";
72  	public static final String PROPERTY_METADATA_HANDLER =
73  	        "http://www.dbunit.org/properties/metadataHandler";
74  	public static final String PROPERTY_ALLOW_VERIFYTABLEDEFINITION_EXPECTEDTABLE_COUNT_MISMATCH =
75  	        "http://www.dbunit.org/properties/allowVerifytabledefinitionExpectedtableCountMismatch";
76      public static final String PROPERTY_IDENTITY_COLUMN_FILTER =
77              "http://www.dbunit.org/properties/mssql/identityColumnFilter";
78  
79      public static final String FEATURE_CASE_SENSITIVE_TABLE_NAMES =
80          "http://www.dbunit.org/features/caseSensitiveTableNames";
81      public static final String FEATURE_QUALIFIED_TABLE_NAMES =
82          "http://www.dbunit.org/features/qualifiedTableNames";
83      public static final String FEATURE_BATCHED_STATEMENTS =
84          "http://www.dbunit.org/features/batchedStatements";
85      public static final String FEATURE_DATATYPE_WARNING =
86          "http://www.dbunit.org/features/datatypeWarning";
87      public static final String FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES =
88          "http://www.dbunit.org/features/skipOracleRecycleBinTables";
89      public static final String FEATURE_ALLOW_EMPTY_FIELDS =
90              "http://www.dbunit.org/features/allowEmptyFields";
91  
92      /**
93       * A list of all properties as {@link ConfigProperty} objects. 
94       * The objects contain the allowed java type and whether or not a property is nullable.
95       */
96      public static final ConfigProperty[] ALL_PROPERTIES = new ConfigProperty[] {
97          new ConfigProperty(PROPERTY_STATEMENT_FACTORY, IStatementFactory.class, false),
98          new ConfigProperty(PROPERTY_RESULTSET_TABLE_FACTORY, IResultSetTableFactory.class, false),
99          new ConfigProperty(PROPERTY_DATATYPE_FACTORY, IDataTypeFactory.class, false),
100         new ConfigProperty(PROPERTY_ESCAPE_PATTERN, String.class, true),
101         new ConfigProperty(PROPERTY_TABLE_TYPE, String[].class, false),
102         new ConfigProperty(PROPERTY_PRIMARY_KEY_FILTER, IColumnFilter.class, true),
103         new ConfigProperty(PROPERTY_BATCH_SIZE, Integer.class, false),
104         new ConfigProperty(PROPERTY_FETCH_SIZE, Integer.class, false),
105         new ConfigProperty(PROPERTY_METADATA_HANDLER, IMetadataHandler.class, false),
106         new ConfigProperty(PROPERTY_IDENTITY_COLUMN_FILTER, IColumnFilter.class, true),
107         new ConfigProperty(FEATURE_CASE_SENSITIVE_TABLE_NAMES, Boolean.class, false),
108         new ConfigProperty(FEATURE_QUALIFIED_TABLE_NAMES, Boolean.class, false),
109         new ConfigProperty(FEATURE_BATCHED_STATEMENTS, Boolean.class, false),
110         new ConfigProperty(FEATURE_DATATYPE_WARNING, Boolean.class, false),
111         new ConfigProperty(FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES, Boolean.class, false),
112         new ConfigProperty(FEATURE_ALLOW_EMPTY_FIELDS, Boolean.class, false),
113         new ConfigProperty(PROPERTY_ALLOW_VERIFYTABLEDEFINITION_EXPECTEDTABLE_COUNT_MISMATCH, Boolean.class, false),
114     };
115 
116     /**
117      * A list of all features as strings
118      * @deprecated since 2.4.7 Use the {@link #ALL_PROPERTIES} where features are listed now as well
119      */
120     public static final String[] ALL_FEATURES = new String[] {
121         FEATURE_CASE_SENSITIVE_TABLE_NAMES,
122         FEATURE_QUALIFIED_TABLE_NAMES,
123         FEATURE_BATCHED_STATEMENTS,
124         FEATURE_DATATYPE_WARNING,
125         FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES,
126         FEATURE_ALLOW_EMPTY_FIELDS
127     };
128     
129     private static final DefaultDataTypeFactory DEFAULT_DATA_TYPE_FACTORY =
130             new DefaultDataTypeFactory();
131     private static final PreparedStatementFactory PREPARED_STATEMENT_FACTORY =
132             new PreparedStatementFactory();
133     private static final CachedResultSetTableFactory RESULT_SET_TABLE_FACTORY =
134             new CachedResultSetTableFactory();
135     private static final String DEFAULT_ESCAPE_PATTERN = null;
136     private static final String[] DEFAULT_TABLE_TYPE = {"TABLE"};
137     private static final Integer DEFAULT_BATCH_SIZE = 100;
138     private static final Integer DEFAULT_FETCH_SIZE = 100;
139 
140 
141 
142     private Map _propertyMap = new HashMap();
143     
144     private final Configurator configurator;
145 
146     public DatabaseConfig()
147     {
148         setFeature(FEATURE_BATCHED_STATEMENTS, false);
149         setFeature(FEATURE_QUALIFIED_TABLE_NAMES, false);
150         setFeature(FEATURE_CASE_SENSITIVE_TABLE_NAMES, false);
151         setFeature(FEATURE_DATATYPE_WARNING, true);
152         setFeature(FEATURE_ALLOW_EMPTY_FIELDS, false);
153 
154         setProperty(PROPERTY_STATEMENT_FACTORY, PREPARED_STATEMENT_FACTORY);
155         setProperty(PROPERTY_RESULTSET_TABLE_FACTORY, RESULT_SET_TABLE_FACTORY);
156         setProperty(PROPERTY_DATATYPE_FACTORY, DEFAULT_DATA_TYPE_FACTORY);
157         setProperty(PROPERTY_ESCAPE_PATTERN, DEFAULT_ESCAPE_PATTERN);
158         setProperty(PROPERTY_TABLE_TYPE, DEFAULT_TABLE_TYPE);
159         setProperty(PROPERTY_BATCH_SIZE, DEFAULT_BATCH_SIZE);
160         setProperty(PROPERTY_FETCH_SIZE, DEFAULT_FETCH_SIZE);
161         setProperty(PROPERTY_METADATA_HANDLER, new DefaultMetadataHandler());
162         setProperty(
163                 PROPERTY_ALLOW_VERIFYTABLEDEFINITION_EXPECTEDTABLE_COUNT_MISMATCH,
164                 Boolean.FALSE);
165 
166         this.configurator = new Configurator(this);
167     }
168 
169     /**
170      * @return The configurator of this database config
171      */
172     protected Configurator getConfigurator() 
173     {
174         return configurator;
175     }
176 
177     /**
178      * Set the value of a feature flag.
179      *
180      * @param name the feature id
181      * @param value the feature status
182      * @deprecated since 2.4.7 Use the {@link #setProperty(String, Object)} also for features
183      */
184     public void setFeature(String name, boolean value)
185     {
186         logger.trace("setFeature(name={}, value={}) - start", name, String.valueOf(value));
187 
188         setProperty(name, Boolean.valueOf(value));
189     }
190 
191     /**
192      * Look up the value of a feature flag.
193      *
194      * @param name the feature id
195      * @return the feature status
196      * @deprecated since 2.4.7 Use the {@link #getProperty(String)} where features are listed now as well
197      */
198     public boolean getFeature(String name)
199     {
200         logger.trace("getFeature(name={}) - start", name);
201         
202         Object property = getProperty(name);
203         if(property == null)
204         {
205             return false;
206         }
207         else if(property instanceof Boolean)
208         {
209             Boolean feature = (Boolean) property;
210             return feature.booleanValue();
211         }
212         else
213         {
214             String propString = String.valueOf(property);
215             Boolean feature = Boolean.valueOf(propString);
216             return feature.booleanValue();
217         }
218     }
219 
220     /**
221      * Set the value of a property.
222      *
223      * @param name the property id
224      * @param value the property value
225      */
226     public void setProperty(String name, Object value)
227     {
228         logger.trace("setProperty(name={}, value={}) - start", name, value);
229         
230         value = convertIfNeeded(name, value);
231         
232         // Validate if the type of the given object is correct
233         checkObjectAllowed(name, value);
234         
235         // If we get here the type is allowed (no exception was thrown)
236         _propertyMap.put(name, value);
237     }
238 
239     /**
240      * Look up the value of a property.
241      *
242      * @param name the property id
243      * @return the property value
244      */
245     public Object getProperty(String name)
246     {
247         logger.trace("getProperty(name={}) - start", name);
248 
249         return _propertyMap.get(name);
250     }
251 
252     private Object convertIfNeeded(String property, Object value) 
253     {
254         logger.trace("convertIfNeeded(property={}, value={}) - start", property, value);
255 
256         ConfigProperty prop = findByName(property);
257         if(prop==null) {
258             throw new IllegalArgumentException("Did not find property with name '" + property + "'");
259         }
260         Class allowedPropType = prop.getPropertyType();
261 
262         if(allowedPropType == Boolean.class || allowedPropType == boolean.class)
263         {
264             // String -> Boolean is a special mapping which is allowed
265             if(value instanceof String)
266             {
267                 return Boolean.valueOf((String)value);
268             }
269         }
270         
271         return value;
272     }
273 
274     /**
275      * Checks whether the given value has the correct java type for the given property.
276      * If the value is not allowed for the given property an {@link IllegalArgumentException} is thrown.
277      * @param property The property to be set
278      * @param value The value to which the property should be set
279      */
280     protected void checkObjectAllowed(String property, Object value)
281     {
282         logger.trace("checkObjectAllowed(property={}, value={}) - start", property, value);
283 
284         ConfigProperty prop = findByName(property);
285         
286         if(prop != null)
287         {
288             // First check for null
289             if(value == null)
290             {
291                 if(prop.isNullable())
292                 {
293                     // All right. No class check is needed
294                     return;
295                 }
296                 else
297                 {
298                     throw new IllegalArgumentException("The property '" + property + "' is not nullable.");
299                 }
300             }
301             else
302             {
303                 Class allowedPropType = prop.getPropertyType();
304                 if(!allowedPropType.isAssignableFrom(value.getClass()))
305                 {
306                     throw new IllegalArgumentException("Cannot cast object of type '" + value.getClass() + 
307                             "' to allowed type '" + allowedPropType + "'.");
308                 }
309             }
310         }
311         else
312         {
313             logger.info("Unknown property '" + property + "'. Cannot validate the type of the object to be set." +
314                     " Please notify a developer to update the list of properties.");
315         }
316     }
317     
318     /**
319      * Sets the given properties on the {@link DatabaseConfig} instance using the given String values.
320      * This is useful to set properties configured as strings by a build tool like ant or maven. 
321      * If the required property type is an object it uses reflection to create an instance of the class
322      * specified as string.
323      * @param stringProperties The properties as strings. The key of the properties can be either the long or
324      * the short name.
325      * @throws DatabaseUnitException 
326      */
327     public void setPropertiesByString(Properties stringProperties) throws DatabaseUnitException
328     {
329         for (Iterator iterator = stringProperties.entrySet().iterator(); iterator.hasNext();) {
330             Map.Entry entry = (Map.Entry) iterator.next();
331             
332             String propKey = (String)entry.getKey();
333             String propValue = (String)entry.getValue();
334 
335             ConfigProperty dbunitProp = DatabaseConfig.findByName(propKey);
336             if(dbunitProp == null)
337             {
338                 logger.debug("Did not find long name property {} - trying short name...", entry);
339                 dbunitProp = DatabaseConfig.findByShortName(propKey);
340             }
341 
342             if(dbunitProp == null)
343             {
344                 logger.info("Could not set property '{}' - not found in the list of known properties.", entry);
345             }
346             else
347             {
348                 String fullPropName = dbunitProp.getProperty();
349                 Object obj = createObjectFromString(dbunitProp, propValue);
350                 this.setProperty(fullPropName, obj);
351             }
352         }
353     }
354     
355     private Object createObjectFromString(ConfigProperty dbunitProp, String propValue) 
356     throws DatabaseUnitException 
357     {
358         if (dbunitProp == null) {
359             throw new NullPointerException(
360                     "The parameter 'dbunitProp' must not be null");
361         }
362         if (propValue == null) {
363             // Null must not be casted
364             return null;
365         }
366         
367         Class targetClass = dbunitProp.getPropertyType();
368         if(targetClass == String.class)
369         {
370             return propValue;
371         }
372         else if(targetClass == Boolean.class)
373         {
374             return Boolean.valueOf(propValue);
375         }
376         else if(targetClass == String[].class)
377         {
378             String[] result = propValue.split(",");
379             for (int i = 0; i < result.length; i++) {
380                 result[i] = result[i].trim();
381             }
382             return result;
383         }
384         else if(targetClass == Integer.class)
385         {
386             return new Integer(propValue);
387         }
388         else
389         {
390             // Try via reflection
391             return createInstance(propValue);
392         }
393     }
394 
395     private Object createInstance(String className) throws DatabaseUnitException 
396     {
397         // Setup data type factory for example.
398         try
399         {
400             Object o = Class.forName(className).newInstance();
401             return o;
402         }
403         catch (ClassNotFoundException e)
404         {
405             throw new DatabaseUnitException(
406                     "Class Not Found: '" + className + "' could not be loaded", e);
407         }
408         catch (IllegalAccessException e)
409         {
410             throw new DatabaseUnitException(
411                     "Illegal Access: '" + className + "' could not be loaded", e);
412         }
413         catch (InstantiationException e)
414         {
415             throw new DatabaseUnitException(
416                     "Instantiation Exception: '" + className + "' could not be loaded", e);
417         }
418     }
419 
420     /**
421      * Searches the {@link ConfigProperty} object for the property with the given name
422      * @param property The property for which the enumerated object should be resolved
423      * @return The property object or <code>null</code> if it was not found.
424      */
425     public static final ConfigProperty findByName(String property) 
426     {
427         for (int i = 0; i < ALL_PROPERTIES.length; i++) {
428             if(ALL_PROPERTIES[i].getProperty().equals(property))
429             {
430                 return ALL_PROPERTIES[i];
431             }
432         }
433         // property not found.
434         return null;
435     }
436     
437     /**
438      * Searches the {@link ConfigProperty} object for the property with the given name
439      * @param propShortName The property short name for which the enumerated object should be resolved.
440      * Example: the short name of {@value #PROPERTY_FETCH_SIZE} is <code>fetchSize</code> which is the
441      * last part of the fully qualified URL.
442      * @return The property object or <code>null</code> if it was not found.
443      */
444     public static final ConfigProperty findByShortName(String propShortName) 
445     {
446         for (int i = 0; i < DatabaseConfig.ALL_PROPERTIES.length; i++) {
447             String fullProperty = DatabaseConfig.ALL_PROPERTIES[i].getProperty();
448             if(fullProperty.endsWith(propShortName))
449             {
450                 return DatabaseConfig.ALL_PROPERTIES[i];
451             }
452         }
453         // Property not found
454         logger.info("The property ending with '" + propShortName + "' was not found. " +
455                 "Please notify a dbunit developer to add the property to the " + DatabaseConfig.class);
456         return null;
457     }
458 
459     public String toString()
460     {
461         final StringBuilder sb = new StringBuilder();
462         sb.append(getClass().getName()).append("[");
463         sb.append(", _propertyMap=").append(_propertyMap);
464         sb.append("]");
465         return sb.toString();
466     }
467     
468 
469     
470     
471     /**
472      * @author gommma (gommma AT users.sourceforge.net)
473      * @author Last changed by: $Author$
474      * @version $Revision$ $Date$
475      * @since 2.4.0
476      */
477     public static class ConfigProperty
478     {
479         private String property;
480         private Class propertyType;
481         private boolean nullable;
482         
483         public ConfigProperty(String property, Class propertyType, boolean nullable) {
484             super();
485             
486             if (property == null) {
487                 throw new NullPointerException(
488                         "The parameter 'property' must not be null");
489             }
490             if (propertyType == null) {
491                 throw new NullPointerException(
492                         "The parameter 'propertyType' must not be null");
493             }
494             
495             this.property = property;
496             this.propertyType = propertyType;
497             this.nullable = nullable;
498         }
499         
500         public String getProperty() {
501             return property;
502         }
503 
504         public Class getPropertyType() {
505             return propertyType;
506         }
507 
508         public boolean isNullable() {
509             return nullable;
510         }
511 
512         public int hashCode() {
513             final int prime = 31;
514             int result = 1;
515             result = prime * result
516                     + ((property == null) ? 0 : property.hashCode());
517             return result;
518         }
519 
520         public boolean equals(Object obj) {
521             if (this == obj)
522                 return true;
523             if (obj == null)
524                 return false;
525             if (getClass() != obj.getClass())
526                 return false;
527             ConfigProperty other = (ConfigProperty) obj;
528             if (property == null) {
529                 if (other.property != null)
530                     return false;
531             } else if (!property.equals(other.property))
532                 return false;
533             return true;
534         }
535 
536         public String toString()
537         {
538             final StringBuilder sb = new StringBuilder();
539             sb.append(getClass().getName()).append("[");
540             sb.append("property=").append(property);
541             sb.append(", propertyType=").append(propertyType);
542             sb.append(", nullable=").append(nullable);
543             sb.append("]");
544             return sb.toString();
545         }
546     }
547     
548     
549     
550     /**
551      * Sets parameters stored in the {@link DatabaseConfig} on specific java objects like {@link Statement}.
552      * Is mainly there to avoid code duplication where {@link DatabaseConfig} parameters are used.
553      * @author gommma (gommma AT users.sourceforge.net)
554      * @author Last changed by: $Author$
555      * @version $Revision$ $Date$
556      * @since 2.4.4
557      */
558     protected static class Configurator
559     {
560         /**
561          * Logger for this class
562          */
563         private static final Logger logger = LoggerFactory.getLogger(Configurator.class);
564 
565         private DatabaseConfig config;
566         
567         /**
568          * @param config The configuration to be used by this configurator
569          * @since 2.4.4
570          */
571         public Configurator(DatabaseConfig config)
572         {
573             if (config == null) {
574                 throw new NullPointerException(
575                         "The parameter 'config' must not be null");
576             }
577             this.config = config;
578         }
579         /**
580          * Configures the given statement so that it has the properties that are configured in this {@link DatabaseConfig}.
581          * @param stmt The statement to be configured.
582          * @throws SQLException
583          * @since 2.4.4
584          */
585         void configureStatement(Statement stmt) throws SQLException 
586         {
587             logger.trace("configureStatement(stmt={}) - start", stmt);
588             Integer fetchSize = (Integer) config.getProperty(DatabaseConfig.PROPERTY_FETCH_SIZE);
589             stmt.setFetchSize(fetchSize.intValue());
590             logger.debug("Statement fetch size set to {}",fetchSize);
591         }
592         
593     }
594 
595 }