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