AbstractMetaDataBasedSearchCallback.java

  1. /*
  2.  *
  3.  * The DbUnit Database Testing Framework
  4.  * Copyright (C)2005, 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.search;

  22. import java.sql.Connection;
  23. import java.sql.DatabaseMetaData;
  24. import java.sql.ResultSet;
  25. import java.sql.SQLException;
  26. import java.util.SortedSet;
  27. import java.util.TreeSet;

  28. import org.dbunit.database.DatabaseConfig;
  29. import org.dbunit.database.IDatabaseConnection;
  30. import org.dbunit.database.IMetadataHandler;
  31. import org.dbunit.dataset.NoSuchTableException;
  32. import org.dbunit.util.QualifiedTableName;
  33. import org.dbunit.util.SQLHelper;
  34. import org.dbunit.util.search.AbstractNodesFilterSearchCallback;
  35. import org.dbunit.util.search.IEdge;
  36. import org.dbunit.util.search.SearchException;
  37. import org.slf4j.Logger;
  38. import org.slf4j.LoggerFactory;

  39. /**
  40.  * Super-class for the ISearchCallback that implements the
  41.  * <code>getEdges()</code> method using the database meta-data.
  42.  *
  43.  * @author Felipe Leme (dbunit@felipeal.net)
  44.  * @version $Revision$
  45.  * @since Aug 25, 2005
  46.  */
  47. public abstract class AbstractMetaDataBasedSearchCallback extends AbstractNodesFilterSearchCallback {

  48.     /**
  49.      * Logger for this class
  50.      */
  51.     private static final Logger logger = LoggerFactory.getLogger(AbstractMetaDataBasedSearchCallback.class);

  52.     private final IDatabaseConnection connection;

  53.     /**
  54.      * Default constructor.
  55.      * @param connection connection where the edges will be calculated from
  56.      */
  57.     public AbstractMetaDataBasedSearchCallback(IDatabaseConnection connection) {
  58.         this.connection = connection;
  59.     }

  60.     /**
  61.      * Get the connection where the edges will be calculated from.
  62.      * @return the connection where the edges will be calculated from
  63.      */
  64.     public IDatabaseConnection getConnection() {
  65.         return connection;
  66.     }

  67.     protected static final int IMPORT = 0;
  68.     protected static final int EXPORT = 1;

  69.     /**
  70.      * indexes of the column names on the MetaData result sets.
  71.      */
  72.     protected static final int[] TABLENAME_INDEXES = { 3, 7 };  
  73.     protected static final int[] SCHEMANAME_INDEXES = { 2, 6 };  
  74.     protected static final int[] PK_INDEXES = { 4, 4 };
  75.     protected static final int[] FK_INDEXES = { 8, 8 };


  76.     /**
  77.      * Get the nodes using the direct foreign key dependency, i.e, if table A has
  78.      * a FK for a table B, then getNodesFromImportedKeys(A) will return B.
  79.      * @param node table name
  80.      * @return tables with direct FK dependency from node
  81.      * @throws SearchException
  82.      */
  83.     protected SortedSet getNodesFromImportedKeys(Object node)
  84.     throws SearchException {
  85.         logger.debug("getNodesFromImportedKeys(node={}) - start", node);

  86.         return getNodes(IMPORT, node);
  87.     }

  88.     /**
  89.      * Get the nodes using the reverse foreign key dependency, i.e, if table C has
  90.      * a FK for a table A, then getNodesFromExportedKeys(A) will return C.<br>
  91.      *
  92.      * <strong>NOTE:</strong> this method should be used only as an auxiliary
  93.      * method for sub-classes that also use <code>getNodesFromImportedKeys()</code>
  94.      * or something similar, otherwise the generated sequence of tables might not
  95.      * work when inserted in the database (as some tables might be missing).
  96.      * <br>
  97.      * @param node table name
  98.      * @return tables with reverse FK dependency from node
  99.      * @throws SearchException
  100.      */
  101.     protected SortedSet getNodesFromExportedKeys(Object node)
  102.     throws SearchException {
  103.         logger.debug("getNodesFromExportedKeys(node={}) - start", node);

  104.         return getNodes(EXPORT, node);
  105.     }

  106.     /**
  107.      * Get the nodes using the both direct and reverse foreign key dependency, i.e,
  108.      * if table C has a FK for a table A and table A has a FK for a table B, then
  109.      * getNodesFromImportAndExportedKeys(A) will return B and C.
  110.      * @param node table name
  111.      * @return tables with reverse and direct FK dependency from node
  112.      * @throws SearchException
  113.      */
  114.     protected SortedSet getNodesFromImportAndExportKeys(Object node)
  115.     throws SearchException {
  116.         logger.debug("getNodesFromImportAndExportKeys(node={}) - start", node);

  117.         SortedSet importedNodes = getNodesFromImportedKeys( node );
  118.         SortedSet exportedNodes = getNodesFromExportedKeys( node );
  119.         importedNodes.addAll( exportedNodes );
  120.         return importedNodes;
  121.     }

  122.     private SortedSet getNodes(int type, Object node) throws SearchException {
  123.         if(logger.isDebugEnabled())
  124.             logger.debug("getNodes(type={}, node={}) - start", Integer.toString(type), node);

  125.         try {
  126.             Connection conn = this.connection.getConnection();
  127.             String schema = this.connection.getSchema();
  128.             DatabaseMetaData metaData = conn.getMetaData();
  129.             SortedSet edges = new TreeSet();
  130.             getNodes(type, node, conn, schema, metaData, edges);
  131.             return edges;
  132.         } catch (SQLException e) {
  133.             throw new SearchException(e);
  134.         } catch (NoSuchTableException e) {
  135.             throw new SearchException(e);
  136.         }
  137.     }

  138.     private void getNodes(int type, Object node, Connection conn,
  139.             String schema, DatabaseMetaData metaData, SortedSet edges)
  140.     throws SearchException, NoSuchTableException
  141.     {
  142.         if (logger.isDebugEnabled())
  143.         {
  144.             logger.debug("getNodes(type={}, node={}, conn={}, schema={}, metaData={}, edges={}) - start",
  145.                     new Object[] {String.valueOf(type), node, conn, schema, metaData, edges});
  146.             logger.debug("Getting edges for node " + node);
  147.         }
  148.        
  149.         if (!(node instanceof String)) {
  150.             throw new IllegalArgumentException("node '" + node + "' should be a String, not a "
  151.                     + node.getClass().getName());
  152.         }
  153.         String tableName = (String) node;

  154.         QualifiedTableName qualifiedTableName = new QualifiedTableName(tableName, schema);
  155.         schema = qualifiedTableName.getSchema();
  156.         tableName = qualifiedTableName.getTable();
  157.        
  158.         ResultSet rs = null;
  159.         try {
  160.             IMetadataHandler metadataHandler = (IMetadataHandler)
  161.                     this.connection.getConfig().getProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER);
  162.             // Validate if the table exists
  163.             if(!metadataHandler.tableExists(metaData, schema, tableName))
  164.             {
  165.                 throw new NoSuchTableException("The table '"+tableName+"' does not exist in schema '"+schema+"'");
  166.             }

  167.             switch (type) {
  168.             case IMPORT:
  169.                 rs = metaData.getImportedKeys(null, schema, tableName);
  170.                 break;
  171.             case EXPORT:
  172.                 rs = metaData.getExportedKeys(null, schema, tableName);
  173.                 break;
  174.             }
  175.            
  176.            
  177.            
  178.             DatabaseConfig dbConfig = this.connection.getConfig();
  179.             while (rs.next()) {
  180.                 int index = TABLENAME_INDEXES[type];
  181.                 int schemaindex = SCHEMANAME_INDEXES[type];
  182.                 String dependentTableName = rs.getString(index);
  183.                 String dependentSchemaName = rs.getString(schemaindex);
  184.                 String pkColumn = rs.getString( PK_INDEXES[type] );
  185.                 String fkColumn = rs.getString( FK_INDEXES[type] );

  186.                 // set the schema in front if there is none ("SCHEMA.TABLE") - depending on the "qualified table names" feature
  187.                 tableName = new QualifiedTableName(tableName, schema).getQualifiedNameIfEnabled(dbConfig);
  188.                 dependentTableName = new QualifiedTableName(dependentTableName, dependentSchemaName).getQualifiedNameIfEnabled(dbConfig);
  189.                
  190.                 IEdge edge = newEdge(rs, type, tableName, dependentTableName, fkColumn, pkColumn );
  191.                 if ( logger.isDebugEnabled() ) {
  192.                     logger.debug("Adding edge " + edge);
  193.                 }
  194.                 edges.add(edge);
  195.             }
  196.         }
  197.         catch (SQLException e) {
  198.             throw new SearchException(e);
  199.         }
  200.         finally
  201.         {
  202.             try {
  203.                 SQLHelper.close(rs);
  204.             } catch (SQLException e) {
  205.                 throw new SearchException(e);
  206.             }              
  207.         }
  208.     }


  209.     /**
  210.      * Creates an edge representing a foreign key relationship between 2 tables.<br>
  211.      * @param rs database meta-data result set
  212.      * @param type type of relationship (IMPORT or EXPORT)
  213.      * @param from name of the table representing the 'from' node
  214.      * @param to name of the table representing the 'to' node
  215.      * @param fkColumn name of the foreign key column
  216.      * @param pkColumn name of the primary key column
  217.      * @return edge representing the relationship between the 2 tables, according to
  218.      * the type
  219.      * @throws SearchException not thrown in this method (but might on sub-classes)
  220.      */
  221.     protected static ForeignKeyRelationshipEdge createFKEdge(ResultSet rs, int type,
  222.             String from, String to, String fkColumn, String pkColumn)
  223.     throws SearchException {
  224.         if (logger.isDebugEnabled()) {
  225.             logger.debug("createFKEdge(rs={}, type={}, from={}, to={}, fkColumn={}, pkColumn={}) - start",
  226.                     new Object[] {rs, String.valueOf(type), from, to, fkColumn, pkColumn});
  227.         }

  228.         return type == IMPORT ?
  229.                 new ForeignKeyRelationshipEdge( from, to, fkColumn, pkColumn ) :
  230.                     new ForeignKeyRelationshipEdge( to, from, fkColumn, pkColumn );
  231.     }


  232.     /**
  233.      * This method can be overwritten by the sub-classes if they need to decorate
  234.      * the edge (for instance, providing an Edge that contains the primary and
  235.      * foreign keys used).
  236.      * @param rs database meta-data result set
  237.      * @param type type of relationship (IMPORT or EXPORT)
  238.      * @param from name of the table representing the 'from' node
  239.      * @param to name of the table representing the 'to' node
  240.      * @param fkColumn name of the foreign key column
  241.      * @param pkColumn name of the primary key column
  242.      * @return edge representing the relationship between the 2 tables, according to
  243.      * the type
  244.      * @throws SearchException not thrown in this method (but might on sub-classes)
  245.      */
  246.     protected IEdge newEdge(ResultSet rs, int type, String from, String to, String fkColumn, String pkColumn)
  247.     throws SearchException {
  248.         if (logger.isDebugEnabled()) {
  249.             logger.debug("newEdge(rs={}, type={}, from={}, to={}, fkColumn={}, pkColumn={}) - start",
  250.                     new Object[] {rs, String.valueOf(type), from, to, fkColumn, pkColumn});
  251.         }

  252.         return createFKEdge( rs, type, from, to, fkColumn, pkColumn );
  253.     }
  254. }