001: /*
002: *
003: * The DbUnit Database Testing Framework
004: * Copyright (C)2002-2004, DbUnit.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: */
021: package org.dbunit.database;
022:
023: import org.slf4j.Logger;
024: import org.slf4j.LoggerFactory;
025:
026: import org.dbunit.dataset.DataSetException;
027: import org.dbunit.dataset.filter.SequenceTableFilter;
028:
029: import java.sql.DatabaseMetaData;
030: import java.sql.ResultSet;
031: import java.sql.SQLException;
032: import java.util.Arrays;
033: import java.util.HashMap;
034: import java.util.HashSet;
035: import java.util.Iterator;
036: import java.util.LinkedList;
037: import java.util.List;
038: import java.util.Map;
039: import java.util.Set;
040:
041: /**
042: * This filter orders tables using dependency information provided by
043: * {@link java.sql.DatabaseMetaData#getExportedKeys}.
044: *
045: * @author Manuel Laflamme
046: * @author Erik Price
047: * @since Mar 23, 2003
048: * @version $Revision: 554 $
049: */
050: public class DatabaseSequenceFilter extends SequenceTableFilter {
051:
052: /**
053: * Logger for this class
054: */
055: private static final Logger logger = LoggerFactory
056: .getLogger(DatabaseSequenceFilter.class);
057:
058: /** Cache for tablename/foreign key mappings. */
059: private static Map _dependentMap;
060:
061: /**
062: * Create a DatabaseSequenceFilter that only exposes specified table names.
063: */
064: public DatabaseSequenceFilter(IDatabaseConnection connection,
065: String[] tableNames) throws DataSetException, SQLException {
066: super (sortTableNames(connection, tableNames));
067: }
068:
069: /**
070: * Create a DatabaseSequenceFilter that exposes all the database tables.
071: */
072: public DatabaseSequenceFilter(IDatabaseConnection connection)
073: throws DataSetException, SQLException {
074: this (connection, connection.createDataSet().getTableNames());
075: }
076:
077: /**
078: * Re-orders a string array of table names, placing dependent ("parent")
079: * tables after their dependencies ("children").
080: *
081: * @param tableNames A string array of table names to be ordered.
082: * @return The re-ordered array of table names.
083: * @throws DataSetException
084: * @throws SQLException If an exception is encountered in accessing the database.
085: */
086: static String[] sortTableNames(IDatabaseConnection connection,
087: String[] tableNames) throws DataSetException, SQLException
088: // not sure why this throws DataSetException ? - ENP
089: {
090: logger.debug("sortTableNames(connection=" + connection
091: + ", tableNames=" + tableNames + ") - start");
092:
093: boolean reprocess = true;
094: List tmpTableNames = Arrays.asList(tableNames);
095: List sortedTableNames = null;
096: DatabaseSequenceFilter._dependentMap = new HashMap();
097:
098: while (reprocess) {
099: sortedTableNames = new LinkedList();
100:
101: // re-order 'tmpTableNames' into 'sortedTableNames'
102: for (Iterator i = tmpTableNames.iterator(); i.hasNext();) {
103: boolean foundDependentInSortedTableNames = false;
104: String tmpTable = (String) i.next();
105: Set tmpTableDependents = getDependentTableNames(
106: connection, tmpTable);
107:
108: int sortedTableIndex = -1;
109: for (Iterator k = sortedTableNames.iterator(); k
110: .hasNext();) {
111: String sortedTable = (String) k.next();
112: if (tmpTableDependents.contains(sortedTable)) {
113: sortedTableIndex = sortedTableNames
114: .indexOf(sortedTable);
115: foundDependentInSortedTableNames = true;
116: break; // end for loop; we know the index
117: }
118: }
119:
120: // add 'tmpTable' to 'sortedTableNames'.
121: // Insert it before its first dependent if there are any,
122: // otherwise append it to the end of 'sortedTableNames'
123: if (foundDependentInSortedTableNames) {
124: if (sortedTableIndex < 0) {
125: throw new IllegalStateException(
126: "sortedTableIndex should be 0 or greater, but is "
127: + sortedTableIndex);
128: }
129: sortedTableNames.add(sortedTableIndex, tmpTable);
130: } else {
131: sortedTableNames.add(tmpTable);
132: }
133: }
134:
135: // don't stop processing until we have a perfect run (no re-ordering)
136: if (tmpTableNames.equals(sortedTableNames)) {
137: reprocess = false;
138: } else {
139:
140: tmpTableNames = null;
141: tmpTableNames = (List) ((LinkedList) sortedTableNames)
142: .clone();
143: }
144: }// end 'while (reprocess)'
145:
146: return (String[]) sortedTableNames.toArray(new String[0]);
147: }
148:
149: /**
150: * Returns a Set containing the names of all tables which are dependent upon
151: * <code>tableName</code>'s primary key as foreign keys.
152: *
153: * @param connection An IDatabaseConnection to a database that supports
154: * referential integrity.
155: * @param tableName The table whose primary key is to be used in determining
156: * dependent foreign key tables.
157: * @return The Set of dependent foreign key table names.
158: * @throws SQLException If an exception is encountered in accessing the database.
159: */
160: private static Set getDependentTableNames(
161: IDatabaseConnection connection, String tableName)
162: throws SQLException {
163: logger.debug("getDependentTableNames(connection=" + connection
164: + ", tableName=" + tableName + ") - start");
165:
166: if (_dependentMap.containsKey(tableName)) {
167: return (Set) _dependentMap.get(tableName);
168: }
169:
170: DatabaseMetaData metaData = connection.getConnection()
171: .getMetaData();
172: String schema = connection.getSchema();
173:
174: ResultSet resultSet = metaData.getExportedKeys(null, schema,
175: tableName);
176: try {
177: Set foreignTableSet = new HashSet();
178:
179: while (resultSet.next()) {
180: // TODO : add support for qualified table names
181: // String foreignSchemaName = resultSet.getString(6);
182: String foreignTableName = resultSet.getString(7);
183:
184: foreignTableSet.add(foreignTableName);
185: }
186:
187: _dependentMap.put(tableName, foreignTableSet);
188: return foreignTableSet;
189: } finally {
190: resultSet.close();
191: }
192: }
193:
194: }
|