AbstractBatchOperation.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.operation;

  22. import java.sql.SQLException;
  23. import java.util.BitSet;

  24. import org.dbunit.DatabaseUnitException;
  25. import org.dbunit.database.DatabaseConfig;
  26. import org.dbunit.database.IDatabaseConnection;
  27. import org.dbunit.database.statement.IPreparedBatchStatement;
  28. import org.dbunit.database.statement.IStatementFactory;
  29. import org.dbunit.dataset.Column;
  30. import org.dbunit.dataset.DataSetException;
  31. import org.dbunit.dataset.IDataSet;
  32. import org.dbunit.dataset.ITable;
  33. import org.dbunit.dataset.ITableIterator;
  34. import org.dbunit.dataset.ITableMetaData;
  35. import org.dbunit.dataset.RowOutOfBoundsException;
  36. import org.dbunit.dataset.datatype.DataType;
  37. import org.dbunit.dataset.datatype.TypeCastException;
  38. import org.slf4j.Logger;
  39. import org.slf4j.LoggerFactory;

  40. /**
  41.  * Base implementation for database operation that are executed in batch.
  42.  *
  43.  * @author Manuel Laflamme
  44.  * @version $Revision$
  45.  * @since Feb 19, 2002
  46.  */
  47. public abstract class AbstractBatchOperation extends AbstractOperation
  48. {
  49.     /**
  50.      * Logger for this class
  51.      */
  52.     private static final Logger logger =
  53.             LoggerFactory.getLogger(AbstractBatchOperation.class);

  54.     private static final BitSet EMPTY_BITSET = new BitSet();
  55.     protected boolean _reverseRowOrder = false;

  56.     static boolean isEmpty(ITable table) throws DataSetException
  57.     {
  58.         logger.debug("isEmpty(table={}) - start", table);

  59.         Column[] columns = table.getTableMetaData().getColumns();

  60.         // No columns = empty
  61.         if (columns.length == 0)
  62.         {
  63.             return true;
  64.         }

  65.         // Try to fetch first table value
  66.         try
  67.         {
  68.             table.getValue(0, columns[0].getColumnName());
  69.             return false;
  70.         } catch (RowOutOfBoundsException e)
  71.         {
  72.             // Not able to access first row thus empty
  73.             return true;
  74.         }
  75.     }

  76.     /**
  77.      * Returns list of tables this operation is applied to. This method allow
  78.      * subclass to do filtering.
  79.      */
  80.     protected ITableIterator iterator(IDataSet dataSet)
  81.             throws DatabaseUnitException
  82.     {
  83.         return dataSet.iterator();
  84.     }

  85.     /**
  86.      * Returns mapping of columns to ignore by this operation. Each bit set
  87.      * represent a column to ignore.
  88.      */
  89.     BitSet getIgnoreMapping(ITable table, int row) throws DataSetException
  90.     {
  91.         return EMPTY_BITSET;
  92.     }

  93.     /**
  94.      * Returns false if the specified table row have a different ignore mapping
  95.      * than the specified mapping.
  96.      */
  97.     boolean equalsIgnoreMapping(BitSet ignoreMapping, ITable table, int row)
  98.             throws DataSetException
  99.     {
  100.         return true;
  101.     }

  102.     abstract OperationData getOperationData(ITableMetaData metaData,
  103.             BitSet ignoreMapping, IDatabaseConnection connection)
  104.             throws DataSetException;

  105.     ////////////////////////////////////////////////////////////////////////////
  106.     // DatabaseOperation class

  107.     @Override
  108.     public void execute(IDatabaseConnection connection, IDataSet dataSet)
  109.             throws DatabaseUnitException, SQLException
  110.     {
  111.         logger.debug("execute(connection={}, dataSet={}) - start", connection,
  112.                 dataSet);

  113.         DatabaseConfig databaseConfig = connection.getConfig();
  114.         IStatementFactory factory = (IStatementFactory) databaseConfig
  115.                 .getProperty(DatabaseConfig.PROPERTY_STATEMENT_FACTORY);
  116.         boolean allowEmptyFields = connection.getConfig()
  117.                 .getFeature(DatabaseConfig.FEATURE_ALLOW_EMPTY_FIELDS);

  118.         // for each table
  119.         ITableIterator iterator = iterator(dataSet);
  120.         while (iterator.next())
  121.         {
  122.             ITable table = iterator.getTable();

  123.             String tableName = table.getTableMetaData().getTableName();
  124.             logger.trace("execute: processing table='{}'", tableName);

  125.             // Do not process empty table
  126.             if (isEmpty(table))
  127.             {
  128.                 continue;
  129.             }

  130.             ITableMetaData metaData =
  131.                     getOperationMetaData(connection, table.getTableMetaData());
  132.             BitSet ignoreMapping = null;
  133.             OperationData operationData = null;
  134.             IPreparedBatchStatement statement = null;

  135.             try
  136.             {
  137.                 // For each row
  138.                 int start = _reverseRowOrder ? table.getRowCount() - 1 : 0;
  139.                 int increment = _reverseRowOrder ? -1 : 1;

  140.                 try
  141.                 {
  142.                     for (int i = start;; i = i + increment)
  143.                     {
  144.                         int row = i;

  145.                         // If current row have a different ignore value mapping
  146.                         // than
  147.                         // previous one, we generate a new statement
  148.                         if (ignoreMapping == null
  149.                                 || !equalsIgnoreMapping(ignoreMapping, table,
  150.                                         row))
  151.                         {
  152.                             // Execute and close previous statement
  153.                             if (statement != null)
  154.                             {
  155.                                 statement.executeBatch();
  156.                                 statement.clearBatch();
  157.                                 statement.close();
  158.                             }

  159.                             ignoreMapping = getIgnoreMapping(table, row);
  160.                             operationData = getOperationData(metaData,
  161.                                     ignoreMapping, connection);
  162.                             statement = factory.createPreparedBatchStatement(
  163.                                     operationData.getSql(), connection);
  164.                         }

  165.                         // for each column
  166.                         Column[] columns = operationData.getColumns();
  167.                         for (int j = 0; j < columns.length; j++)
  168.                         {
  169.                             // Bind value only if not in ignore mapping
  170.                             if (!ignoreMapping.get(j))
  171.                             {
  172.                                 Column column = columns[j];
  173.                                 String columnName = column.getColumnName();
  174.                                 try
  175.                                 {
  176.                                     DataType dataType = column.getDataType();
  177.                                     Object value =
  178.                                             table.getValue(row, columnName);

  179.                                     if ("".equals(value) && !allowEmptyFields)
  180.                                     {
  181.                                         handleColumnHasNoValue(tableName,
  182.                                                 columnName);
  183.                                     }

  184.                                     statement.addValue(value, dataType);
  185.                                 } catch (TypeCastException e)
  186.                                 {
  187.                                     final String msg =
  188.                                             "Error casting value for table '"
  189.                                                     + tableName
  190.                                                     + "' and column '"
  191.                                                     + columnName + "'";
  192.                                     logger.error("execute: {}", msg);
  193.                                     throw new TypeCastException(msg, e);
  194.                                 }
  195.                             }
  196.                         }
  197.                         statement.addBatch();
  198.                     }
  199.                 } catch (RowOutOfBoundsException e)
  200.                 {
  201.                     // This exception occurs when records are exhausted
  202.                     // and we reach the end of the table. Ignore this error

  203.                     // end of table
  204.                 }

  205.                 statement.executeBatch();
  206.                 statement.clearBatch();
  207.             } catch (SQLException e)
  208.             {
  209.                 final String msg =
  210.                         "Exception processing table name='" + tableName + "'";
  211.                 throw new DatabaseUnitException(msg, e);
  212.             } finally
  213.             {
  214.                 if (statement != null)
  215.                 {
  216.                     statement.close();
  217.                 }
  218.             }
  219.         }
  220.     }

  221.     protected void handleColumnHasNoValue(String tableName, String columnName)
  222.     {
  223.         final String tableColumnName = tableName + "." + columnName;
  224.         final String msg = "table.column=" + tableColumnName
  225.                 + " value is empty but must contain a value"
  226.                 + " (to disable this feature check,"
  227.                 + " set DatabaseConfig.FEATURE_ALLOW_EMPTY_FIELDS to true)";
  228.         logger.error("execute: {}", msg);

  229.         throw new IllegalArgumentException(msg);
  230.     }

  231.     @Override
  232.     public String toString()
  233.     {
  234.         final StringBuilder sb = new StringBuilder();
  235.         sb.append(getClass().getName()).append("[");
  236.         sb.append("_reverseRowOrder=").append(this._reverseRowOrder);
  237.         sb.append(", super=").append(super.toString());
  238.         sb.append("]");
  239.         return sb.toString();
  240.     }
  241. }