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