DefaultFailureHandler.java

  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. import java.util.Arrays;

  23. import org.dbunit.dataset.Column;
  24. import org.dbunit.dataset.ColumnFilterTable;
  25. import org.dbunit.dataset.Columns;
  26. import org.dbunit.dataset.DataSetException;
  27. import org.dbunit.dataset.ITable;
  28. import org.dbunit.dataset.ITableMetaData;
  29. import org.dbunit.dataset.NoSuchColumnException;
  30. import org.slf4j.Logger;
  31. import org.slf4j.LoggerFactory;

  32. /**
  33.  * Default implementation of the {@link FailureHandler}.
  34.  *
  35.  * @author gommma (gommma AT users.sourceforge.net)
  36.  * @since 2.4.0
  37.  */
  38. public class DefaultFailureHandler implements FailureHandler
  39. {
  40.     private static final Logger logger =
  41.             LoggerFactory.getLogger(DefaultFailureHandler.class);

  42.     private String[] _additionalColumnInfo;

  43.     private FailureFactory failureFactory = new DefaultFailureFactory();

  44.     /**
  45.      * Default constructor which does not provide any additional column
  46.      * information.
  47.      */
  48.     public DefaultFailureHandler()
  49.     {
  50.     }

  51.     /**
  52.      * Create a default failure handler
  53.      *
  54.      * @param additionalColumnInfo
  55.      *            the column names of the columns for which additional
  56.      *            information should be printed when an assertion failed.
  57.      */
  58.     public DefaultFailureHandler(final Column[] additionalColumnInfo)
  59.     {
  60.         // Null-safe access
  61.         if (additionalColumnInfo != null)
  62.         {
  63.             this._additionalColumnInfo =
  64.                     Columns.getColumnNames(additionalColumnInfo);
  65.         }
  66.     }

  67.     /**
  68.      * Create a default failure handler
  69.      *
  70.      * @param additionalColumnInfo
  71.      *            the column names of the columns for which additional
  72.      *            information should be printed when an assertion failed.
  73.      */
  74.     public DefaultFailureHandler(final String[] additionalColumnInfo)
  75.     {
  76.         this._additionalColumnInfo = additionalColumnInfo;
  77.     }

  78.     /**
  79.      * @param failureFactory
  80.      *            The {@link FailureFactory} to be used for creating assertion
  81.      *            errors.
  82.      */
  83.     public void setFailureFactory(final FailureFactory failureFactory)
  84.     {
  85.         if (failureFactory == null)
  86.         {
  87.             throw new NullPointerException(
  88.                     "The parameter 'failureFactory' must not be null");
  89.         }
  90.         this.failureFactory = failureFactory;
  91.     }

  92.     public Error createFailure(final String message, final String expected,
  93.             final String actual)
  94.     {
  95.         return this.failureFactory.createFailure(message, expected, actual);
  96.     }

  97.     public Error createFailure(final String message)
  98.     {
  99.         return this.failureFactory.createFailure(message);
  100.     }

  101.     public String getAdditionalInfo(final ITable expectedTable,
  102.             final ITable actualTable, final int row, final String columnName)
  103.     {
  104.         // add custom column values information for better identification of
  105.         // mismatching rows
  106.         return buildAdditionalColumnInfo(expectedTable, actualTable, row);
  107.     }

  108.     private String buildAdditionalColumnInfo(final ITable expectedTable,
  109.             final ITable actualTable, final int rowIndex)
  110.     {
  111.         if (logger.isDebugEnabled())
  112.         {
  113.             logger.debug(
  114.                     "buildAdditionalColumnInfo(expectedTable={}, actualTable={}, rowIndex={}, "
  115.                             + "additionalColumnInfo={}) - start",
  116.                     expectedTable, actualTable,
  117.                             rowIndex, _additionalColumnInfo);
  118.         }

  119.         // No columns specified
  120.         if (_additionalColumnInfo == null || _additionalColumnInfo.length <= 0)
  121.         {
  122.             return null;
  123.         }

  124.         final StringBuilder sb = new StringBuilder();
  125.         sb.append("Additional row info:");
  126.         for (int j = 0; j < _additionalColumnInfo.length; j++)
  127.         {
  128.             final String columnName = _additionalColumnInfo[j];

  129.             final Object expectedKeyValue =
  130.                     getColumnValue(expectedTable, rowIndex, columnName);
  131.             final Object actualKeyValue =
  132.                     getColumnValue(actualTable, rowIndex, columnName);

  133.             sb.append(" ('");
  134.             sb.append(columnName);
  135.             sb.append("': expected=<");
  136.             sb.append(expectedKeyValue);
  137.             sb.append(">, actual=<");
  138.             sb.append(actualKeyValue);
  139.             sb.append(">)");
  140.         }

  141.         return sb.toString();
  142.     }

  143.     protected Object getColumnValue(final ITable table, final int rowIndex,
  144.             final String columnName)
  145.     {
  146.         Object value = null;
  147.         try
  148.         {
  149.             // Get the ITable object to be used for showing the column values
  150.             // (needed in case of Filtered tables)
  151.             final ITable tableForCol = getTableForColumn(table, columnName);
  152.             value = tableForCol.getValue(rowIndex, columnName);
  153.         } catch (final DataSetException e)
  154.         {
  155.             value = makeAdditionalColumnInfoErrorMessage(columnName, e);
  156.         }
  157.         return value;
  158.     }

  159.     protected String makeAdditionalColumnInfoErrorMessage(
  160.             final String columnName, final DataSetException e)
  161.     {
  162.         final StringBuilder sb = new StringBuilder();
  163.         sb.append("Exception creating more info for column '");
  164.         sb.append(columnName);
  165.         sb.append("': ");
  166.         sb.append(e.getClass().getName());
  167.         sb.append(": ");
  168.         sb.append(e.getMessage());
  169.         final String msg = sb.toString();

  170.         logger.warn(msg, e);

  171.         return " (!!!!! " + msg + ")";
  172.     }

  173.     /**
  174.      * @param table
  175.      *            The table which might be a decorated table
  176.      * @param columnName
  177.      *            The column name for which a table is searched
  178.      * @return The table that as a column with the given name
  179.      * @throws DataSetException
  180.      *             If no table could be found having a column with the given
  181.      *             name
  182.      */
  183.     private ITable getTableForColumn(final ITable table,
  184.             final String columnName) throws DataSetException
  185.     {
  186.         final ITableMetaData tableMetaData = table.getTableMetaData();
  187.         try
  188.         {
  189.             tableMetaData.getColumnIndex(columnName);
  190.             // if the column index was resolved the table contains the given
  191.             // column. So just use this table
  192.             return table;
  193.         } catch (final NoSuchColumnException e)
  194.         {
  195.             // If the column was not found check for filtered table
  196.             if (table instanceof ColumnFilterTable)
  197.             {
  198.                 final ITableMetaData originalMetaData =
  199.                         ((ColumnFilterTable) table).getOriginalMetaData();
  200.                 originalMetaData.getColumnIndex(columnName);
  201.                 // If we get here the column exists - return the table since it
  202.                 // is not filtered in the CompositeTable.
  203.                 return table;
  204.             } else
  205.             {
  206.                 // Column not available in the table - rethrow the exception
  207.                 throw e;
  208.             }
  209.         }
  210.     }

  211.     public void handle(final Difference diff)
  212.     {
  213.         final String msg = buildMessage(diff);

  214.         final Error err =
  215.                 this.createFailure(msg, String.valueOf(diff.getExpectedValue()),
  216.                         String.valueOf(diff.getActualValue()));
  217.         // Throw the assertion error
  218.         throw err;
  219.     }

  220.     protected String buildMessage(final Difference diff)
  221.     {
  222.         final StringBuilder builder = new StringBuilder(200);

  223.         final int rowNum = diff.getRowIndex();
  224.         final String columnName = diff.getColumnName();
  225.         final ITable expectedTable = diff.getExpectedTable();
  226.         final ITable actualTable = diff.getActualTable();

  227.         addFailMessage(diff, builder);

  228.         final String expectedTableName =
  229.                 expectedTable.getTableMetaData().getTableName();

  230.         // example message:
  231.         // "value (table=MYTAB, row=232, column=MYCOL, Additional row info:
  232.         // (column=MyIdCol, expected=444, actual=555)): expected:<123> but
  233.         // was:<1234>"
  234.         builder.append("value (table=").append(expectedTableName);
  235.         builder.append(", row=").append(rowNum);
  236.         builder.append(", col=").append(columnName);

  237.         final String additionalInfo = this.getAdditionalInfo(expectedTable,
  238.                 actualTable, rowNum, columnName);
  239.         if (additionalInfo != null && !additionalInfo.trim().equals(""))
  240.         {
  241.             builder.append(", ").append(additionalInfo);
  242.         }

  243.         builder.append(")");

  244.         return builder.toString();
  245.     }

  246.     protected void addFailMessage(final Difference diff,
  247.             final StringBuilder builder)
  248.     {
  249.         final String failMessage = diff.getFailMessage();
  250.         final boolean isFailMessage = isFailMessage(failMessage);
  251.         if (isFailMessage)
  252.         {
  253.             builder.append(failMessage).append(": ");
  254.         }
  255.     }

  256.     protected boolean isFailMessage(final String failMessage)
  257.     {
  258.         return failMessage != null && !failMessage.isEmpty();
  259.     }

  260.     @Override
  261.     public String toString()
  262.     {
  263.         final StringBuilder sb = new StringBuilder();
  264.         sb.append(DefaultFailureHandler.class.getName()).append("[");
  265.         sb.append("_additionalColumnInfo=").append(_additionalColumnInfo == null
  266.                 ? "null" : Arrays.asList(_additionalColumnInfo).toString());
  267.         sb.append("]");
  268.         return sb.toString();
  269.     }

  270.     /**
  271.      * Default failure factory which returns DBUnits own assertion error
  272.      * instances.
  273.      *
  274.      * @author gommma (gommma AT users.sourceforge.net)
  275.      * @author Last changed by: $Author: gommma $
  276.      * @version $Revision: 872 $ $Date: 2008-11-08 09:45:52 -0600 (Sat, 08 Nov
  277.      *          2008) $
  278.      * @since 2.4.0
  279.      */
  280.     public static class DefaultFailureFactory implements FailureFactory
  281.     {
  282.         public Error createFailure(final String message, final String expected,
  283.                 final String actual)
  284.         {
  285.             // Return dbunit's own comparison failure object
  286.             return new DbComparisonFailure(message, expected, actual);
  287.         }

  288.         public Error createFailure(final String message)
  289.         {
  290.             // Return dbunit's own failure object
  291.             return new DbAssertionFailedError(message);
  292.         }
  293.     }
  294. }