View Javadoc
1   /*
2    *
3    *  The DbUnit Database Testing Framework
4    *  Copyright (C)2002-2008, 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.assertion;
22  
23  import java.util.Arrays;
24  
25  import org.dbunit.dataset.Column;
26  import org.dbunit.dataset.ColumnFilterTable;
27  import org.dbunit.dataset.Columns;
28  import org.dbunit.dataset.DataSetException;
29  import org.dbunit.dataset.ITable;
30  import org.dbunit.dataset.ITableMetaData;
31  import org.dbunit.dataset.NoSuchColumnException;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  /**
36   * Default implementation of the {@link FailureHandler}.
37   *
38   * @author gommma (gommma AT users.sourceforge.net)
39   * @since 2.4.0
40   */
41  public class DefaultFailureHandler implements FailureHandler
42  {
43      private static final Logger logger =
44              LoggerFactory.getLogger(DefaultFailureHandler.class);
45  
46      private String[] _additionalColumnInfo;
47  
48      private FailureFactory failureFactory = new DefaultFailureFactory();
49  
50      /**
51       * Default constructor which does not provide any additional column
52       * information.
53       */
54      public DefaultFailureHandler()
55      {
56      }
57  
58      /**
59       * Create a default failure handler
60       *
61       * @param additionalColumnInfo
62       *            the column names of the columns for which additional
63       *            information should be printed when an assertion failed.
64       */
65      public DefaultFailureHandler(final Column[] additionalColumnInfo)
66      {
67          // Null-safe access
68          if (additionalColumnInfo != null)
69          {
70              this._additionalColumnInfo =
71                      Columns.getColumnNames(additionalColumnInfo);
72          }
73      }
74  
75      /**
76       * Create a default failure handler
77       *
78       * @param additionalColumnInfo
79       *            the column names of the columns for which additional
80       *            information should be printed when an assertion failed.
81       */
82      public DefaultFailureHandler(final String[] additionalColumnInfo)
83      {
84          this._additionalColumnInfo = additionalColumnInfo;
85      }
86  
87      /**
88       * @param failureFactory
89       *            The {@link FailureFactory} to be used for creating assertion
90       *            errors.
91       */
92      public void setFailureFactory(final FailureFactory failureFactory)
93      {
94          if (failureFactory == null)
95          {
96              throw new NullPointerException(
97                      "The parameter 'failureFactory' must not be null");
98          }
99          this.failureFactory = failureFactory;
100     }
101 
102     public Error createFailure(final String message, final String expected,
103             final String actual)
104     {
105         return this.failureFactory.createFailure(message, expected, actual);
106     }
107 
108     public Error createFailure(final String message)
109     {
110         return this.failureFactory.createFailure(message);
111     }
112 
113     public String getAdditionalInfo(final ITable expectedTable,
114             final ITable actualTable, final int row, final String columnName)
115     {
116         // add custom column values information for better identification of
117         // mismatching rows
118         return buildAdditionalColumnInfo(expectedTable, actualTable, row);
119     }
120 
121     private String buildAdditionalColumnInfo(final ITable expectedTable,
122             final ITable actualTable, final int rowIndex)
123     {
124         if (logger.isDebugEnabled())
125         {
126             logger.debug(
127                     "buildAdditionalColumnInfo(expectedTable={}, actualTable={}, rowIndex={}, "
128                             + "additionalColumnInfo={}) - start",
129                     new Object[] {expectedTable, actualTable,
130                             new Integer(rowIndex), _additionalColumnInfo});
131         }
132 
133         // No columns specified
134         if (_additionalColumnInfo == null || _additionalColumnInfo.length <= 0)
135         {
136             return null;
137         }
138 
139         final StringBuilder sb = new StringBuilder();
140         sb.append("Additional row info:");
141         for (int j = 0; j < _additionalColumnInfo.length; j++)
142         {
143             final String columnName = _additionalColumnInfo[j];
144 
145             final Object expectedKeyValue =
146                     getColumnValue(expectedTable, rowIndex, columnName);
147             final Object actualKeyValue =
148                     getColumnValue(actualTable, rowIndex, columnName);
149 
150             sb.append(" ('");
151             sb.append(columnName);
152             sb.append("': expected=<");
153             sb.append(expectedKeyValue);
154             sb.append(">, actual=<");
155             sb.append(actualKeyValue);
156             sb.append(">)");
157         }
158 
159         return sb.toString();
160     }
161 
162     protected Object getColumnValue(final ITable table, final int rowIndex,
163             final String columnName)
164     {
165         Object value = null;
166         try
167         {
168             // Get the ITable object to be used for showing the column values
169             // (needed in case of Filtered tables)
170             final ITable tableForCol = getTableForColumn(table, columnName);
171             value = tableForCol.getValue(rowIndex, columnName);
172         } catch (final DataSetException e)
173         {
174             value = makeAdditionalColumnInfoErrorMessage(columnName, e);
175         }
176         return value;
177     }
178 
179     protected String makeAdditionalColumnInfoErrorMessage(
180             final String columnName, final DataSetException e)
181     {
182         final StringBuilder sb = new StringBuilder();
183         sb.append("Exception creating more info for column '");
184         sb.append(columnName);
185         sb.append("': ");
186         sb.append(e.getClass().getName());
187         sb.append(": ");
188         sb.append(e.getMessage());
189         final String msg = sb.toString();
190 
191         logger.warn(msg, e);
192 
193         return " (!!!!! " + msg + ")";
194     }
195 
196     /**
197      * @param table
198      *            The table which might be a decorated table
199      * @param columnName
200      *            The column name for which a table is searched
201      * @return The table that as a column with the given name
202      * @throws DataSetException
203      *             If no table could be found having a column with the given
204      *             name
205      */
206     private ITable getTableForColumn(final ITable table,
207             final String columnName) throws DataSetException
208     {
209         final ITableMetaData tableMetaData = table.getTableMetaData();
210         try
211         {
212             tableMetaData.getColumnIndex(columnName);
213             // if the column index was resolved the table contains the given
214             // column. So just use this table
215             return table;
216         } catch (final NoSuchColumnException e)
217         {
218             // If the column was not found check for filtered table
219             if (table instanceof ColumnFilterTable)
220             {
221                 final ITableMetaData originalMetaData =
222                         ((ColumnFilterTable) table).getOriginalMetaData();
223                 originalMetaData.getColumnIndex(columnName);
224                 // If we get here the column exists - return the table since it
225                 // is not filtered in the CompositeTable.
226                 return table;
227             } else
228             {
229                 // Column not available in the table - rethrow the exception
230                 throw e;
231             }
232         }
233     }
234 
235     public void handle(final Difference diff)
236     {
237         final String msg = buildMessage(diff);
238 
239         final Error err =
240                 this.createFailure(msg, String.valueOf(diff.getExpectedValue()),
241                         String.valueOf(diff.getActualValue()));
242         // Throw the assertion error
243         throw err;
244     }
245 
246     protected String buildMessage(final Difference diff)
247     {
248         final StringBuilder builder = new StringBuilder(200);
249 
250         final int rowNum = diff.getRowIndex();
251         final String columnName = diff.getColumnName();
252         final ITable expectedTable = diff.getExpectedTable();
253         final ITable actualTable = diff.getActualTable();
254 
255         addFailMessage(diff, builder);
256 
257         final String expectedTableName =
258                 expectedTable.getTableMetaData().getTableName();
259 
260         // example message:
261         // "value (table=MYTAB, row=232, column=MYCOL, Additional row info:
262         // (column=MyIdCol, expected=444, actual=555)): expected:<123> but
263         // was:<1234>"
264         builder.append("value (table=").append(expectedTableName);
265         builder.append(", row=").append(rowNum);
266         builder.append(", col=").append(columnName);
267 
268         final String additionalInfo = this.getAdditionalInfo(expectedTable,
269                 actualTable, rowNum, columnName);
270         if (additionalInfo != null && !additionalInfo.trim().equals(""))
271         {
272             builder.append(", ").append(additionalInfo);
273         }
274 
275         builder.append(")");
276 
277         return builder.toString();
278     }
279 
280     protected void addFailMessage(final Difference diff,
281             final StringBuilder builder)
282     {
283         final String failMessage = diff.getFailMessage();
284         final boolean isFailMessage = isFailMessage(failMessage);
285         if (isFailMessage)
286         {
287             builder.append(failMessage).append(": ");
288         }
289     }
290 
291     protected boolean isFailMessage(final String failMessage)
292     {
293         return failMessage != null && !failMessage.isEmpty();
294     }
295 
296     @Override
297     public String toString()
298     {
299         final StringBuilder sb = new StringBuilder();
300         sb.append(DefaultFailureHandler.class.getName()).append("[");
301         sb.append("_additionalColumnInfo=").append(_additionalColumnInfo == null
302                 ? "null" : Arrays.asList(_additionalColumnInfo).toString());
303         sb.append("]");
304         return sb.toString();
305     }
306 
307     /**
308      * Default failure factory which returns DBUnits own assertion error
309      * instances.
310      *
311      * @author gommma (gommma AT users.sourceforge.net)
312      * @author Last changed by: $Author: gommma $
313      * @version $Revision: 872 $ $Date: 2008-11-08 09:45:52 -0600 (Sat, 08 Nov
314      *          2008) $
315      * @since 2.4.0
316      */
317     public static class DefaultFailureFactory implements FailureFactory
318     {
319         public Error createFailure(final String message, final String expected,
320                 final String actual)
321         {
322             // Return dbunit's own comparison failure object
323             return new DbComparisonFailure(message, expected, actual);
324         }
325 
326         public Error createFailure(final String message)
327         {
328             // Return dbunit's own failure object
329             return new DbAssertionFailedError(message);
330         }
331     }
332 }