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  
22  package org.dbunit.operation;
23  
24  import java.sql.SQLException;
25  import java.util.BitSet;
26  
27  import org.dbunit.DatabaseUnitException;
28  import org.dbunit.database.DatabaseConfig;
29  import org.dbunit.database.IDatabaseConnection;
30  import org.dbunit.database.statement.IPreparedBatchStatement;
31  import org.dbunit.database.statement.IStatementFactory;
32  import org.dbunit.dataset.Column;
33  import org.dbunit.dataset.DataSetException;
34  import org.dbunit.dataset.IDataSet;
35  import org.dbunit.dataset.ITable;
36  import org.dbunit.dataset.ITableIterator;
37  import org.dbunit.dataset.ITableMetaData;
38  import org.dbunit.dataset.RowOutOfBoundsException;
39  import org.dbunit.dataset.datatype.DataType;
40  import org.dbunit.dataset.datatype.TypeCastException;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  /**
45   * Base implementation for database operation that are executed in batch.
46   *
47   * @author Manuel Laflamme
48   * @version $Revision$
49   * @since Feb 19, 2002
50   */
51  public abstract class AbstractBatchOperation extends AbstractOperation
52  {
53      /**
54       * Logger for this class
55       */
56      private static final Logger logger =
57              LoggerFactory.getLogger(AbstractBatchOperation.class);
58  
59      private static final BitSet EMPTY_BITSET = new BitSet();
60      protected boolean _reverseRowOrder = false;
61  
62      static boolean isEmpty(ITable table) throws DataSetException
63      {
64          logger.debug("isEmpty(table={}) - start", table);
65  
66          Column[] columns = table.getTableMetaData().getColumns();
67  
68          // No columns = empty
69          if (columns.length == 0)
70          {
71              return true;
72          }
73  
74          // Try to fetch first table value
75          try
76          {
77              table.getValue(0, columns[0].getColumnName());
78              return false;
79          } catch (RowOutOfBoundsException e)
80          {
81              // Not able to access first row thus empty
82              return true;
83          }
84      }
85  
86      /**
87       * Returns list of tables this operation is applied to. This method allow
88       * subclass to do filtering.
89       */
90      protected ITableIterator iterator(IDataSet dataSet)
91              throws DatabaseUnitException
92      {
93          return dataSet.iterator();
94      }
95  
96      /**
97       * Returns mapping of columns to ignore by this operation. Each bit set
98       * represent a column to ignore.
99       */
100     BitSet getIgnoreMapping(ITable table, int row) throws DataSetException
101     {
102         return EMPTY_BITSET;
103     }
104 
105     /**
106      * Returns false if the specified table row have a different ignore mapping
107      * than the specified mapping.
108      */
109     boolean equalsIgnoreMapping(BitSet ignoreMapping, ITable table, int row)
110             throws DataSetException
111     {
112         return true;
113     }
114 
115     abstract OperationData getOperationData(ITableMetaData metaData,
116             BitSet ignoreMapping, IDatabaseConnection connection)
117             throws DataSetException;
118 
119     ////////////////////////////////////////////////////////////////////////////
120     // DatabaseOperation class
121 
122     @Override
123     public void execute(IDatabaseConnection connection, IDataSet dataSet)
124             throws DatabaseUnitException, SQLException
125     {
126         logger.debug("execute(connection={}, dataSet={}) - start", connection,
127                 dataSet);
128 
129         DatabaseConfig databaseConfig = connection.getConfig();
130         IStatementFactory factory = (IStatementFactory) databaseConfig
131                 .getProperty(DatabaseConfig.PROPERTY_STATEMENT_FACTORY);
132         boolean allowEmptyFields = connection.getConfig()
133                 .getFeature(DatabaseConfig.FEATURE_ALLOW_EMPTY_FIELDS);
134 
135         // for each table
136         ITableIterator iterator = iterator(dataSet);
137         while (iterator.next())
138         {
139             ITable table = iterator.getTable();
140 
141             String tableName = table.getTableMetaData().getTableName();
142             logger.trace("execute: processing table='{}'", tableName);
143 
144             // Do not process empty table
145             if (isEmpty(table))
146             {
147                 continue;
148             }
149 
150             ITableMetaData metaData =
151                     getOperationMetaData(connection, table.getTableMetaData());
152             BitSet ignoreMapping = null;
153             OperationData operationData = null;
154             IPreparedBatchStatement statement = null;
155 
156             try
157             {
158                 // For each row
159                 int start = _reverseRowOrder ? table.getRowCount() - 1 : 0;
160                 int increment = _reverseRowOrder ? -1 : 1;
161 
162                 try
163                 {
164                     for (int i = start;; i = i + increment)
165                     {
166                         int row = i;
167 
168                         // If current row have a different ignore value mapping
169                         // than
170                         // previous one, we generate a new statement
171                         if (ignoreMapping == null
172                                 || !equalsIgnoreMapping(ignoreMapping, table,
173                                         row))
174                         {
175                             // Execute and close previous statement
176                             if (statement != null)
177                             {
178                                 statement.executeBatch();
179                                 statement.clearBatch();
180                                 statement.close();
181                             }
182 
183                             ignoreMapping = getIgnoreMapping(table, row);
184                             operationData = getOperationData(metaData,
185                                     ignoreMapping, connection);
186                             statement = factory.createPreparedBatchStatement(
187                                     operationData.getSql(), connection);
188                         }
189 
190                         // for each column
191                         Column[] columns = operationData.getColumns();
192                         for (int j = 0; j < columns.length; j++)
193                         {
194                             // Bind value only if not in ignore mapping
195                             if (!ignoreMapping.get(j))
196                             {
197                                 Column column = columns[j];
198                                 String columnName = column.getColumnName();
199                                 try
200                                 {
201                                     DataType dataType = column.getDataType();
202                                     Object value =
203                                             table.getValue(row, columnName);
204 
205                                     if ("".equals(value) && !allowEmptyFields)
206                                     {
207                                         handleColumnHasNoValue(tableName,
208                                                 columnName);
209                                     }
210 
211                                     statement.addValue(value, dataType);
212                                 } catch (TypeCastException e)
213                                 {
214                                     final String msg =
215                                             "Error casting value for table '"
216                                                     + tableName
217                                                     + "' and column '"
218                                                     + columnName + "'";
219                                     logger.error("execute: {}", msg);
220                                     throw new TypeCastException(msg, e);
221                                 }
222                             }
223                         }
224                         statement.addBatch();
225                     }
226                 } catch (RowOutOfBoundsException e)
227                 {
228                     // This exception occurs when records are exhausted
229                     // and we reach the end of the table. Ignore this error
230 
231                     // end of table
232                 }
233 
234                 statement.executeBatch();
235                 statement.clearBatch();
236             } catch (SQLException e)
237             {
238                 final String msg =
239                         "Exception processing table name='" + tableName + "'";
240                 throw new DatabaseUnitException(msg, e);
241             } finally
242             {
243                 if (statement != null)
244                 {
245                     statement.close();
246                 }
247             }
248         }
249     }
250 
251     protected void handleColumnHasNoValue(String tableName, String columnName)
252     {
253         final String tableColumnName = tableName + "." + columnName;
254         final String msg = "table.column=" + tableColumnName
255                 + " value is empty but must contain a value"
256                 + " (to disable this feature check,"
257                 + " set DatabaseConfig.FEATURE_ALLOW_EMPTY_FIELDS to true)";
258         logger.error("execute: {}", msg);
259 
260         throw new IllegalArgumentException(msg);
261     }
262 
263     @Override
264     public String toString()
265     {
266         StringBuffer sb = new StringBuffer();
267         sb.append(getClass().getName()).append("[");
268         sb.append("_reverseRowOrder=").append(this._reverseRowOrder);
269         sb.append(", super=").append(super.toString());
270         sb.append("]");
271         return sb.toString();
272     }
273 }