DatabaseSequenceFilter.java

  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. package org.dbunit.database;

  22. import java.sql.SQLException;
  23. import java.util.Arrays;
  24. import java.util.HashMap;
  25. import java.util.HashSet;
  26. import java.util.Iterator;
  27. import java.util.LinkedList;
  28. import java.util.List;
  29. import java.util.Map;
  30. import java.util.Set;

  31. import org.dbunit.database.search.TablesDependencyHelper;
  32. import org.dbunit.dataset.DataSetException;
  33. import org.dbunit.dataset.filter.SequenceTableFilter;
  34. import org.dbunit.util.search.SearchException;
  35. import org.slf4j.Logger;
  36. import org.slf4j.LoggerFactory;

  37. /**
  38.  * This filter orders tables using dependency information provided by
  39.  * {@link java.sql.DatabaseMetaData#getExportedKeys}. Note that this class
  40.  * name is a bit misleading since it is not at all related to database
  41.  * sequences. It just brings database tables in a specific order.
  42.  *
  43.  * @author Manuel Laflamme
  44.  * @author Erik Price
  45.  * @author Last changed by: $Author$
  46.  * @version $Revision$ $Date$
  47.  * @since 1.5.1 (Mar 23, 2003)
  48.  */
  49. public class DatabaseSequenceFilter extends SequenceTableFilter
  50. {

  51.     /**
  52.      * Logger for this class
  53.      */
  54.     private static final Logger logger = LoggerFactory.getLogger(DatabaseSequenceFilter.class);
  55.  

  56.     /**
  57.      * Create a DatabaseSequenceFilter that only exposes specified table names.
  58.      */
  59.     public DatabaseSequenceFilter(IDatabaseConnection connection,
  60.             String[] tableNames) throws DataSetException, SQLException
  61.     {
  62.         super(sortTableNames(connection, tableNames));
  63.     }

  64.     /**
  65.      * Create a DatabaseSequenceFilter that exposes all the database tables.
  66.      */
  67.     public DatabaseSequenceFilter(IDatabaseConnection connection)
  68.             throws DataSetException, SQLException
  69.     {
  70.         this(connection, connection.createDataSet().getTableNames());
  71.     }

  72.     /**
  73.      * Re-orders a string array of table names, placing dependent ("parent")
  74.      * tables after their dependencies ("children").
  75.      *
  76.      * @param tableNames A string array of table names to be ordered.
  77.      * @return The re-ordered array of table names.
  78.      * @throws DataSetException
  79.      * @throws SQLException If an exception is encountered in accessing the database.
  80.      */
  81.     static String[] sortTableNames(
  82.         IDatabaseConnection connection,
  83.         String[] tableNames)
  84.         throws DataSetException, SQLException
  85.             // not sure why this throws DataSetException ? - ENP
  86.     {
  87.         logger.debug("sortTableNames(connection={}, tableNames={}) - start", connection, tableNames);

  88.         // Get dependencies for each table
  89.         Map dependencies = new HashMap();
  90.         try {
  91.             for (int i = 0; i < tableNames.length; i++) {
  92.                 String tableName = tableNames[i];
  93.                 DependencyInfo info = getDependencyInfo(connection, tableName);
  94.                 dependencies.put(tableName, info);
  95.             }
  96.         } catch (SearchException e) {
  97.             throw new DataSetException("Exception while searching the dependent tables.", e);
  98.         }

  99.        
  100.         // Check whether the table dependency info contains cycles
  101.         for (Iterator iterator = dependencies.values().iterator(); iterator.hasNext();) {
  102.             DependencyInfo info = (DependencyInfo) iterator.next();
  103.             info.checkCycles();
  104.         }
  105.        
  106.         return sort(tableNames, dependencies);
  107.     }
  108.    

  109.     private static String[] sort(String[] tableNames, Map dependencies)
  110.     {
  111.         logger.debug("sort(tableNames={}, dependencies={}) - start", tableNames, dependencies);
  112.        
  113.         boolean reprocess = true;
  114.         List tmpTableNames = Arrays.asList(tableNames);
  115.         List sortedTableNames = null;
  116.        
  117.         while (reprocess) {
  118.             sortedTableNames = new LinkedList();
  119.            
  120.             // re-order 'tmpTableNames' into 'sortedTableNames'
  121.             for (Iterator i = tmpTableNames.iterator(); i.hasNext();)
  122.             {
  123.                 boolean foundDependentInSortedTableNames = false;
  124.                 String tmpTable = (String)i.next();
  125.                 DependencyInfo tmpTableDependents = (DependencyInfo) dependencies.get(tmpTable);
  126.                

  127.                 int sortedTableIndex = -1;
  128.                 for (Iterator k = sortedTableNames.iterator(); k.hasNext();)
  129.                 {
  130.                     String sortedTable = (String)k.next();
  131.                     if (tmpTableDependents.containsDirectDependsOn(sortedTable))
  132.                     {
  133.                         sortedTableIndex = sortedTableNames.indexOf(sortedTable);
  134.                         foundDependentInSortedTableNames = true;
  135.                         break; // end for loop; we know the index
  136.                     }
  137.                 }

  138.                
  139.                 // add 'tmpTable' to 'sortedTableNames'.
  140.                 // Insert it before its first dependent if there are any,
  141.                 // otherwise append it to the end of 'sortedTableNames'
  142.                 if (foundDependentInSortedTableNames) {
  143.                     if (sortedTableIndex < 0) {
  144.                         throw new IllegalStateException(
  145.                             "sortedTableIndex should be 0 or greater, but is "
  146.                                 + sortedTableIndex);
  147.                     }
  148.                     sortedTableNames.add(sortedTableIndex, tmpTable);
  149.                 }
  150.                 else
  151.                 {
  152.                     sortedTableNames.add(tmpTable);
  153.                 }
  154.             }
  155.            
  156.            
  157.            
  158.             // don't stop processing until we have a perfect run (no re-ordering)
  159.             if (tmpTableNames.equals(sortedTableNames))
  160.             {
  161.                 reprocess = false;
  162.             }
  163.             else
  164.             {

  165.                 tmpTableNames = null;
  166.                 tmpTableNames = (List)((LinkedList)sortedTableNames).clone();
  167.             }
  168.         }// end 'while (reprocess)'
  169.        
  170.         return (String[])sortedTableNames.toArray(new String[0]);
  171.     }

  172.     /**
  173.      * Creates the dependency information for the given table
  174.      * @param connection
  175.      * @param tableName
  176.      * @return The dependency information for the given table
  177.      * @throws SearchException
  178.      */
  179.     private static DependencyInfo getDependencyInfo(
  180.             IDatabaseConnection connection, String tableName)
  181.     throws SearchException
  182.     {
  183.         logger.debug("getDependencyInfo(connection={}, tableName={}) - start", connection, tableName);
  184.        
  185.         // The tables dependency helpers makes a depth search for dependencies and returns the whole
  186.         // tree of dependent objects, not only the direct FK-PK related tables.
  187.         String[] allDependentTables = TablesDependencyHelper.getDependentTables(connection, tableName);
  188.         String[] allDependsOnTables = TablesDependencyHelper.getDependsOnTables(connection, tableName);
  189.         Set allDependentTablesSet = new HashSet(Arrays.asList(allDependentTables));
  190.         Set allDependsOnTablesSet = new HashSet(Arrays.asList(allDependsOnTables));
  191.         // Remove the table itself which is automatically included by the TablesDependencyHelper
  192.         allDependentTablesSet.remove(tableName);
  193.         allDependsOnTablesSet.remove(tableName);
  194.        
  195.         Set directDependsOnTablesSet = TablesDependencyHelper.getDirectDependsOnTables(connection, tableName);
  196.         Set directDependentTablesSet = TablesDependencyHelper.getDirectDependentTables(connection, tableName);
  197.         directDependsOnTablesSet.remove(tableName);
  198.         directDependentTablesSet.remove(tableName);
  199.        
  200.         DependencyInfo info = new DependencyInfo(tableName,
  201.                 directDependsOnTablesSet, directDependentTablesSet,
  202.                 allDependsOnTablesSet, allDependentTablesSet);
  203.         return info;
  204.     }


  205.    
  206.     /**
  207.      * Container of dependency information for one single table.
  208.      *
  209.      * @author gommma (gommma AT users.sourceforge.net)
  210.      * @author Last changed by: $Author$
  211.      * @version $Revision$ $Date$
  212.      * @since 2.4.0
  213.      */
  214.     static class DependencyInfo
  215.     {
  216.         /**
  217.          * Logger for this class
  218.          */
  219.         private static final Logger logger = LoggerFactory.getLogger(DatabaseSequenceFilter.class);

  220.         private String tableName;
  221.        
  222.         private Set allTableDependsOn;
  223.         private Set allTableDependent;
  224.        
  225.         private Set directDependsOnTablesSet;
  226.         private Set directDependentTablesSet;
  227.        
  228.         /**
  229.          * @param tableName
  230.          * @param allTableDependsOn Tables that are required as prerequisite so that this one can exist
  231.          * @param allTableDependent Tables that need this one in order to be able to exist
  232.          */
  233.         public DependencyInfo(String tableName,
  234.                 Set directDependsOnTablesSet, Set directDependentTablesSet,
  235.                 Set allTableDependsOn, Set allTableDependent)
  236.         {
  237.             super();
  238.             this.directDependsOnTablesSet = directDependsOnTablesSet;
  239.             this.directDependentTablesSet = directDependentTablesSet;
  240.             this.allTableDependsOn = allTableDependsOn;
  241.             this.allTableDependent = allTableDependent;
  242.             this.tableName = tableName;
  243.         }

  244.         public boolean containsDirectDependent(String tableName) {
  245.             return this.directDependentTablesSet.contains(tableName);
  246.         }
  247.         public boolean containsDirectDependsOn(String tableName) {
  248.             return this.directDependsOnTablesSet.contains(tableName);
  249.         }

  250.         public String getTableName() {
  251.             return tableName;
  252.         }

  253.         public Set getAllTableDependsOn() {
  254.             return allTableDependsOn;
  255.         }

  256.         public Set getAllTableDependent() {
  257.             return allTableDependent;
  258.         }
  259.        
  260.         public Set getDirectDependsOnTablesSet() {
  261.             return directDependsOnTablesSet;
  262.         }

  263.         public Set getDirectDependentTablesSet() {
  264.             return directDependentTablesSet;
  265.         }

  266.         /**
  267.          * Checks this table's information for cycles by intersecting the two sets.
  268.          * When the result set has at least one element we do have cycles.
  269.          * @throws CyclicTablesDependencyException
  270.          */
  271.         public void checkCycles() throws CyclicTablesDependencyException
  272.         {
  273.             logger.debug("checkCycles() - start");

  274.             // Intersect the "tableDependsOn" and "otherTablesDependOn" to check for cycles
  275.             Set intersect = new HashSet(this.allTableDependsOn);
  276.             intersect.retainAll(this.allTableDependent);
  277.             if(!intersect.isEmpty()){
  278.                 throw new CyclicTablesDependencyException(tableName, intersect);
  279.             }
  280.         }

  281.         public String toString()
  282.         {
  283.             final StringBuilder sb = new StringBuilder();
  284.             sb.append("DependencyInfo[");
  285.             sb.append("table=").append(tableName);
  286.             sb.append(", directDependsOn=").append(directDependsOnTablesSet);
  287.             sb.append(", directDependent=").append(directDependentTablesSet);
  288.             sb.append(", allDependsOn=").append(allTableDependsOn);
  289.             sb.append(", allDependent=").append(allTableDependent);
  290.             sb.append("]");
  291.             return sb.toString();
  292.         }
  293.        
  294.     }
  295. }