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