DatabaseConfig.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.database;

  22. import java.sql.SQLException;
  23. import java.sql.Statement;
  24. import java.util.HashMap;
  25. import java.util.Iterator;
  26. import java.util.Map;
  27. import java.util.Properties;

  28. import org.dbunit.DatabaseUnitException;
  29. import org.dbunit.database.statement.IStatementFactory;
  30. import org.dbunit.database.statement.PreparedStatementFactory;
  31. import org.dbunit.dataset.datatype.DefaultDataTypeFactory;
  32. import org.dbunit.dataset.datatype.IDataTypeFactory;
  33. import org.dbunit.dataset.filter.IColumnFilter;
  34. import org.slf4j.Logger;
  35. import org.slf4j.LoggerFactory;

  36. /**
  37.  * Configuration used by the {@link DatabaseConnection}.
  38.  *
  39.  * @author manuel.laflamme
  40.  * @author gommma (gommma AT users.sourceforge.net)
  41.  * @author Last changed by: $Author$
  42.  * @version $Revision$ $Date$
  43.  * @since 2.0
  44.  */
  45. public class DatabaseConfig
  46. {

  47.     /**
  48.      * Logger for this class
  49.      */
  50.     private static final Logger logger = LoggerFactory.getLogger(DatabaseConfig.class);

  51.     public static final String PROPERTY_STATEMENT_FACTORY =
  52.             "http://www.dbunit.org/properties/statementFactory";
  53.     public static final String PROPERTY_RESULTSET_TABLE_FACTORY =
  54.             "http://www.dbunit.org/properties/resultSetTableFactory";
  55.     public static final String PROPERTY_DATATYPE_FACTORY =
  56.             "http://www.dbunit.org/properties/datatypeFactory";
  57.     public static final String PROPERTY_ESCAPE_PATTERN =
  58.             "http://www.dbunit.org/properties/escapePattern";
  59.     public static final String PROPERTY_TABLE_TYPE =
  60.             "http://www.dbunit.org/properties/tableType";
  61.     public static final String PROPERTY_PRIMARY_KEY_FILTER =
  62.             "http://www.dbunit.org/properties/primaryKeyFilter";
  63.     public static final String PROPERTY_BATCH_SIZE =
  64.             "http://www.dbunit.org/properties/batchSize";
  65.     public static final String PROPERTY_FETCH_SIZE =
  66.             "http://www.dbunit.org/properties/fetchSize";
  67.     public static final String PROPERTY_METADATA_HANDLER =
  68.             "http://www.dbunit.org/properties/metadataHandler";
  69.     public static final String PROPERTY_ALLOW_VERIFYTABLEDEFINITION_EXPECTEDTABLE_COUNT_MISMATCH =
  70.             "http://www.dbunit.org/properties/allowVerifytabledefinitionExpectedtableCountMismatch";
  71.     public static final String PROPERTY_IDENTITY_COLUMN_FILTER =
  72.             "http://www.dbunit.org/properties/mssql/identityColumnFilter";

  73.     public static final String FEATURE_CASE_SENSITIVE_TABLE_NAMES =
  74.         "http://www.dbunit.org/features/caseSensitiveTableNames";
  75.     public static final String FEATURE_QUALIFIED_TABLE_NAMES =
  76.         "http://www.dbunit.org/features/qualifiedTableNames";
  77.     public static final String FEATURE_BATCHED_STATEMENTS =
  78.         "http://www.dbunit.org/features/batchedStatements";
  79.     public static final String FEATURE_DATATYPE_WARNING =
  80.         "http://www.dbunit.org/features/datatypeWarning";
  81.     public static final String FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES =
  82.         "http://www.dbunit.org/features/skipOracleRecycleBinTables";
  83.     public static final String FEATURE_ALLOW_EMPTY_FIELDS =
  84.             "http://www.dbunit.org/features/allowEmptyFields";

  85.     /**
  86.      * A list of all properties as {@link ConfigProperty} objects.
  87.      * The objects contain the allowed java type and whether or not a property is nullable.
  88.      */
  89.     public static final ConfigProperty[] ALL_PROPERTIES = new ConfigProperty[] {
  90.         new ConfigProperty(PROPERTY_STATEMENT_FACTORY, IStatementFactory.class, false),
  91.         new ConfigProperty(PROPERTY_RESULTSET_TABLE_FACTORY, IResultSetTableFactory.class, false),
  92.         new ConfigProperty(PROPERTY_DATATYPE_FACTORY, IDataTypeFactory.class, false),
  93.         new ConfigProperty(PROPERTY_ESCAPE_PATTERN, String.class, true),
  94.         new ConfigProperty(PROPERTY_TABLE_TYPE, String[].class, false),
  95.         new ConfigProperty(PROPERTY_PRIMARY_KEY_FILTER, IColumnFilter.class, true),
  96.         new ConfigProperty(PROPERTY_BATCH_SIZE, Integer.class, false),
  97.         new ConfigProperty(PROPERTY_FETCH_SIZE, Integer.class, false),
  98.         new ConfigProperty(PROPERTY_METADATA_HANDLER, IMetadataHandler.class, false),
  99.         new ConfigProperty(PROPERTY_IDENTITY_COLUMN_FILTER, IColumnFilter.class, true),
  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.         new ConfigProperty(FEATURE_ALLOW_EMPTY_FIELDS, Boolean.class, false),
  106.         new ConfigProperty(PROPERTY_ALLOW_VERIFYTABLEDEFINITION_EXPECTEDTABLE_COUNT_MISMATCH, Boolean.class, false),
  107.     };

  108.     /**
  109.      * A list of all features as strings
  110.      * @deprecated since 2.4.7 Use the {@link #ALL_PROPERTIES} where features are listed now as well
  111.      */
  112.     public static final String[] ALL_FEATURES = new String[] {
  113.         FEATURE_CASE_SENSITIVE_TABLE_NAMES,
  114.         FEATURE_QUALIFIED_TABLE_NAMES,
  115.         FEATURE_BATCHED_STATEMENTS,
  116.         FEATURE_DATATYPE_WARNING,
  117.         FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES,
  118.         FEATURE_ALLOW_EMPTY_FIELDS
  119.     };
  120.    
  121.     private static final DefaultDataTypeFactory DEFAULT_DATA_TYPE_FACTORY =
  122.             new DefaultDataTypeFactory();
  123.     private static final PreparedStatementFactory PREPARED_STATEMENT_FACTORY =
  124.             new PreparedStatementFactory();
  125.     private static final CachedResultSetTableFactory RESULT_SET_TABLE_FACTORY =
  126.             new CachedResultSetTableFactory();
  127.     private static final String DEFAULT_ESCAPE_PATTERN = null;
  128.     private static final String[] DEFAULT_TABLE_TYPE = {"TABLE"};
  129.     private static final Integer DEFAULT_BATCH_SIZE = 100;
  130.     private static final Integer DEFAULT_FETCH_SIZE = 100;



  131.     private Map _propertyMap = new HashMap();
  132.    
  133.     private final Configurator configurator;

  134.     public DatabaseConfig()
  135.     {
  136.         setFeature(FEATURE_BATCHED_STATEMENTS, false);
  137.         setFeature(FEATURE_QUALIFIED_TABLE_NAMES, false);
  138.         setFeature(FEATURE_CASE_SENSITIVE_TABLE_NAMES, false);
  139.         setFeature(FEATURE_DATATYPE_WARNING, true);
  140.         setFeature(FEATURE_ALLOW_EMPTY_FIELDS, false);

  141.         setProperty(PROPERTY_STATEMENT_FACTORY, PREPARED_STATEMENT_FACTORY);
  142.         setProperty(PROPERTY_RESULTSET_TABLE_FACTORY, RESULT_SET_TABLE_FACTORY);
  143.         setProperty(PROPERTY_DATATYPE_FACTORY, DEFAULT_DATA_TYPE_FACTORY);
  144.         setProperty(PROPERTY_ESCAPE_PATTERN, DEFAULT_ESCAPE_PATTERN);
  145.         setProperty(PROPERTY_TABLE_TYPE, DEFAULT_TABLE_TYPE);
  146.         setProperty(PROPERTY_BATCH_SIZE, DEFAULT_BATCH_SIZE);
  147.         setProperty(PROPERTY_FETCH_SIZE, DEFAULT_FETCH_SIZE);
  148.         setProperty(PROPERTY_METADATA_HANDLER, new DefaultMetadataHandler());
  149.         setProperty(
  150.                 PROPERTY_ALLOW_VERIFYTABLEDEFINITION_EXPECTEDTABLE_COUNT_MISMATCH,
  151.                 Boolean.FALSE);

  152.         this.configurator = new Configurator(this);
  153.     }

  154.     /**
  155.      * @return The configurator of this database config
  156.      */
  157.     protected Configurator getConfigurator()
  158.     {
  159.         return configurator;
  160.     }

  161.     /**
  162.      * Set the value of a feature flag.
  163.      *
  164.      * @param name the feature id
  165.      * @param value the feature status
  166.      * @deprecated since 2.4.7 Use the {@link #setProperty(String, Object)} also for features
  167.      */
  168.     public void setFeature(String name, boolean value)
  169.     {
  170.         logger.trace("setFeature(name={}, value={}) - start", name, String.valueOf(value));

  171.         setProperty(name, Boolean.valueOf(value));
  172.     }

  173.     /**
  174.      * Look up the value of a feature flag.
  175.      *
  176.      * @param name the feature id
  177.      * @return the feature status
  178.      * @deprecated since 2.4.7 Use the {@link #getProperty(String)} where features are listed now as well
  179.      */
  180.     public boolean getFeature(String name)
  181.     {
  182.         logger.trace("getFeature(name={}) - start", name);
  183.        
  184.         Object property = getProperty(name);
  185.         if(property == null)
  186.         {
  187.             return false;
  188.         }
  189.         else if(property instanceof Boolean)
  190.         {
  191.             Boolean feature = (Boolean) property;
  192.             return feature.booleanValue();
  193.         }
  194.         else
  195.         {
  196.             String propString = String.valueOf(property);
  197.             Boolean feature = Boolean.valueOf(propString);
  198.             return feature.booleanValue();
  199.         }
  200.     }

  201.     /**
  202.      * Set the value of a property.
  203.      *
  204.      * @param name the property id
  205.      * @param value the property value
  206.      */
  207.     public void setProperty(String name, Object value)
  208.     {
  209.         logger.trace("setProperty(name={}, value={}) - start", name, value);
  210.        
  211.         value = convertIfNeeded(name, value);
  212.        
  213.         // Validate if the type of the given object is correct
  214.         checkObjectAllowed(name, value);
  215.        
  216.         // If we get here the type is allowed (no exception was thrown)
  217.         _propertyMap.put(name, value);
  218.     }

  219.     /**
  220.      * Look up the value of a property.
  221.      *
  222.      * @param name the property id
  223.      * @return the property value
  224.      */
  225.     public Object getProperty(String name)
  226.     {
  227.         logger.trace("getProperty(name={}) - start", name);

  228.         return _propertyMap.get(name);
  229.     }

  230.     private Object convertIfNeeded(String property, Object value)
  231.     {
  232.         logger.trace("convertIfNeeded(property={}, value={}) - start", property, value);

  233.         ConfigProperty prop = findByName(property);
  234.         if(prop==null) {
  235.             throw new IllegalArgumentException("Did not find property with name '" + property + "'");
  236.         }
  237.         Class allowedPropType = prop.getPropertyType();

  238.         if(allowedPropType == Boolean.class || allowedPropType == boolean.class)
  239.         {
  240.             // String -> Boolean is a special mapping which is allowed
  241.             if(value instanceof String)
  242.             {
  243.                 return Boolean.valueOf((String)value);
  244.             }
  245.         }
  246.        
  247.         return value;
  248.     }

  249.     /**
  250.      * Checks whether the given value has the correct java type for the given property.
  251.      * If the value is not allowed for the given property an {@link IllegalArgumentException} is thrown.
  252.      * @param property The property to be set
  253.      * @param value The value to which the property should be set
  254.      */
  255.     protected void checkObjectAllowed(String property, Object value)
  256.     {
  257.         logger.trace("checkObjectAllowed(property={}, value={}) - start", property, value);

  258.         ConfigProperty prop = findByName(property);
  259.        
  260.         if(prop != null)
  261.         {
  262.             // First check for null
  263.             if(value == null)
  264.             {
  265.                 if(prop.isNullable())
  266.                 {
  267.                     // All right. No class check is needed
  268.                     return;
  269.                 }
  270.                 else
  271.                 {
  272.                     throw new IllegalArgumentException("The property '" + property + "' is not nullable.");
  273.                 }
  274.             }
  275.             else
  276.             {
  277.                 Class allowedPropType = prop.getPropertyType();
  278.                 if(!allowedPropType.isAssignableFrom(value.getClass()))
  279.                 {
  280.                     throw new IllegalArgumentException("Cannot cast object of type '" + value.getClass() +
  281.                             "' to allowed type '" + allowedPropType + "'.");
  282.                 }
  283.             }
  284.         }
  285.         else
  286.         {
  287.             logger.info("Unknown property '" + property + "'. Cannot validate the type of the object to be set." +
  288.                     " Please notify a developer to update the list of properties.");
  289.         }
  290.     }
  291.    
  292.     /**
  293.      * Sets the given properties on the {@link DatabaseConfig} instance using the given String values.
  294.      * This is useful to set properties configured as strings by a build tool like ant or maven.
  295.      * If the required property type is an object it uses reflection to create an instance of the class
  296.      * specified as string.
  297.      * @param stringProperties The properties as strings. The key of the properties can be either the long or
  298.      * the short name.
  299.      * @throws DatabaseUnitException
  300.      */
  301.     public void setPropertiesByString(Properties stringProperties) throws DatabaseUnitException
  302.     {
  303.         for (Iterator iterator = stringProperties.entrySet().iterator(); iterator.hasNext();) {
  304.             Map.Entry entry = (Map.Entry) iterator.next();
  305.            
  306.             String propKey = (String)entry.getKey();
  307.             String propValue = (String)entry.getValue();

  308.             ConfigProperty dbunitProp = DatabaseConfig.findByName(propKey);
  309.             if(dbunitProp == null)
  310.             {
  311.                 logger.debug("Did not find long name property {} - trying short name...", entry);
  312.                 dbunitProp = DatabaseConfig.findByShortName(propKey);
  313.             }

  314.             if(dbunitProp == null)
  315.             {
  316.                 logger.info("Could not set property '{}' - not found in the list of known properties.", entry);
  317.             }
  318.             else
  319.             {
  320.                 String fullPropName = dbunitProp.getProperty();
  321.                 Object obj = createObjectFromString(dbunitProp, propValue);
  322.                 this.setProperty(fullPropName, obj);
  323.             }
  324.         }
  325.     }
  326.    
  327.     private Object createObjectFromString(ConfigProperty dbunitProp, String propValue)
  328.     throws DatabaseUnitException
  329.     {
  330.         if (dbunitProp == null) {
  331.             throw new NullPointerException(
  332.                     "The parameter 'dbunitProp' must not be null");
  333.         }
  334.         if (propValue == null) {
  335.             // Null must not be casted
  336.             return null;
  337.         }
  338.        
  339.         Class targetClass = dbunitProp.getPropertyType();
  340.         if(targetClass == String.class)
  341.         {
  342.             return propValue;
  343.         }
  344.         else if(targetClass == Boolean.class)
  345.         {
  346.             return Boolean.valueOf(propValue);
  347.         }
  348.         else if(targetClass == String[].class)
  349.         {
  350.             String[] result = propValue.split(",");
  351.             for (int i = 0; i < result.length; i++) {
  352.                 result[i] = result[i].trim();
  353.             }
  354.             return result;
  355.         }
  356.         else if(targetClass == Integer.class)
  357.         {
  358.             return new Integer(propValue);
  359.         }
  360.         else
  361.         {
  362.             // Try via reflection
  363.             return createInstance(propValue);
  364.         }
  365.     }

  366.     private Object createInstance(String className) throws DatabaseUnitException
  367.     {
  368.         // Setup data type factory for example.
  369.         try
  370.         {
  371.             Object o = Class.forName(className).newInstance();
  372.             return o;
  373.         }
  374.         catch (ClassNotFoundException e)
  375.         {
  376.             throw new DatabaseUnitException(
  377.                     "Class Not Found: '" + className + "' could not be loaded", e);
  378.         }
  379.         catch (IllegalAccessException e)
  380.         {
  381.             throw new DatabaseUnitException(
  382.                     "Illegal Access: '" + className + "' could not be loaded", e);
  383.         }
  384.         catch (InstantiationException e)
  385.         {
  386.             throw new DatabaseUnitException(
  387.                     "Instantiation Exception: '" + className + "' could not be loaded", e);
  388.         }
  389.     }

  390.     /**
  391.      * Searches the {@link ConfigProperty} object for the property with the given name
  392.      * @param property The property for which the enumerated object should be resolved
  393.      * @return The property object or <code>null</code> if it was not found.
  394.      */
  395.     public static final ConfigProperty findByName(String property)
  396.     {
  397.         for (int i = 0; i < ALL_PROPERTIES.length; i++) {
  398.             if(ALL_PROPERTIES[i].getProperty().equals(property))
  399.             {
  400.                 return ALL_PROPERTIES[i];
  401.             }
  402.         }
  403.         // property not found.
  404.         return null;
  405.     }
  406.    
  407.     /**
  408.      * Searches the {@link ConfigProperty} object for the property with the given name
  409.      * @param propShortName The property short name for which the enumerated object should be resolved.
  410.      * Example: the short name of {@value #PROPERTY_FETCH_SIZE} is <code>fetchSize</code> which is the
  411.      * last part of the fully qualified URL.
  412.      * @return The property object or <code>null</code> if it was not found.
  413.      */
  414.     public static final ConfigProperty findByShortName(String propShortName)
  415.     {
  416.         for (int i = 0; i < DatabaseConfig.ALL_PROPERTIES.length; i++) {
  417.             String fullProperty = DatabaseConfig.ALL_PROPERTIES[i].getProperty();
  418.             if(fullProperty.endsWith(propShortName))
  419.             {
  420.                 return DatabaseConfig.ALL_PROPERTIES[i];
  421.             }
  422.         }
  423.         // Property not found
  424.         logger.info("The property ending with '" + propShortName + "' was not found. " +
  425.                 "Please notify a dbunit developer to add the property to the " + DatabaseConfig.class);
  426.         return null;
  427.     }

  428.     public String toString()
  429.     {
  430.         final StringBuilder sb = new StringBuilder();
  431.         sb.append(getClass().getName()).append("[");
  432.         sb.append(", _propertyMap=").append(_propertyMap);
  433.         sb.append("]");
  434.         return sb.toString();
  435.     }
  436.    

  437.    
  438.    
  439.     /**
  440.      * @author gommma (gommma AT users.sourceforge.net)
  441.      * @author Last changed by: $Author$
  442.      * @version $Revision$ $Date$
  443.      * @since 2.4.0
  444.      */
  445.     public static class ConfigProperty
  446.     {
  447.         private String property;
  448.         private Class propertyType;
  449.         private boolean nullable;
  450.        
  451.         public ConfigProperty(String property, Class propertyType, boolean nullable) {
  452.             super();
  453.            
  454.             if (property == null) {
  455.                 throw new NullPointerException(
  456.                         "The parameter 'property' must not be null");
  457.             }
  458.             if (propertyType == null) {
  459.                 throw new NullPointerException(
  460.                         "The parameter 'propertyType' must not be null");
  461.             }
  462.            
  463.             this.property = property;
  464.             this.propertyType = propertyType;
  465.             this.nullable = nullable;
  466.         }
  467.        
  468.         public String getProperty() {
  469.             return property;
  470.         }

  471.         public Class getPropertyType() {
  472.             return propertyType;
  473.         }

  474.         public boolean isNullable() {
  475.             return nullable;
  476.         }

  477.         public int hashCode() {
  478.             final int prime = 31;
  479.             int result = 1;
  480.             result = prime * result
  481.                     + ((property == null) ? 0 : property.hashCode());
  482.             return result;
  483.         }

  484.         public boolean equals(Object obj) {
  485.             if (this == obj)
  486.                 return true;
  487.             if (obj == null)
  488.                 return false;
  489.             if (getClass() != obj.getClass())
  490.                 return false;
  491.             ConfigProperty other = (ConfigProperty) obj;
  492.             if (property == null) {
  493.                 if (other.property != null)
  494.                     return false;
  495.             } else if (!property.equals(other.property))
  496.                 return false;
  497.             return true;
  498.         }

  499.         public String toString()
  500.         {
  501.             final StringBuilder sb = new StringBuilder();
  502.             sb.append(getClass().getName()).append("[");
  503.             sb.append("property=").append(property);
  504.             sb.append(", propertyType=").append(propertyType);
  505.             sb.append(", nullable=").append(nullable);
  506.             sb.append("]");
  507.             return sb.toString();
  508.         }
  509.     }
  510.    
  511.    
  512.    
  513.     /**
  514.      * Sets parameters stored in the {@link DatabaseConfig} on specific java objects like {@link Statement}.
  515.      * Is mainly there to avoid code duplication where {@link DatabaseConfig} parameters are used.
  516.      * @author gommma (gommma AT users.sourceforge.net)
  517.      * @author Last changed by: $Author$
  518.      * @version $Revision$ $Date$
  519.      * @since 2.4.4
  520.      */
  521.     protected static class Configurator
  522.     {
  523.         /**
  524.          * Logger for this class
  525.          */
  526.         private static final Logger logger = LoggerFactory.getLogger(Configurator.class);

  527.         private DatabaseConfig config;
  528.        
  529.         /**
  530.          * @param config The configuration to be used by this configurator
  531.          * @since 2.4.4
  532.          */
  533.         public Configurator(DatabaseConfig config)
  534.         {
  535.             if (config == null) {
  536.                 throw new NullPointerException(
  537.                         "The parameter 'config' must not be null");
  538.             }
  539.             this.config = config;
  540.         }
  541.         /**
  542.          * Configures the given statement so that it has the properties that are configured in this {@link DatabaseConfig}.
  543.          * @param stmt The statement to be configured.
  544.          * @throws SQLException
  545.          * @since 2.4.4
  546.          */
  547.         void configureStatement(Statement stmt) throws SQLException
  548.         {
  549.             logger.trace("configureStatement(stmt={}) - start", stmt);
  550.             Integer fetchSize = (Integer) config.getProperty(DatabaseConfig.PROPERTY_FETCH_SIZE);
  551.             stmt.setFetchSize(fetchSize.intValue());
  552.             logger.debug("Statement fetch size set to {}",fetchSize);
  553.         }
  554.        
  555.     }

  556. }