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