001: /*
002: *
003: * The DbUnit Database Testing Framework
004: * Copyright (C)2005, 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:
022: package org.dbunit.database.search;
023:
024: import org.slf4j.Logger;
025: import org.slf4j.LoggerFactory;
026:
027: import java.sql.Connection;
028: import java.sql.DatabaseMetaData;
029: import java.sql.ResultSet;
030: import java.sql.SQLException;
031: import java.util.SortedSet;
032: import java.util.TreeSet;
033:
034: import org.dbunit.database.IDatabaseConnection;
035:
036: import org.dbunit.util.search.AbstractNodesFilterSearchCallback;
037: import org.dbunit.util.search.IEdge;
038: import org.dbunit.util.search.SearchException;
039:
040: /**
041: * Super-class for the ISearchCallback that implements the
042: * <code>getEdges()</code> method using the database meta-data.
043: *
044: * @author Felipe Leme <dbunit@felipeal.net>
045: * @version $Revision: 554 $
046: * @since Aug 25, 2005
047: */
048: public abstract class AbstractMetaDataBasedSearchCallback extends
049: AbstractNodesFilterSearchCallback {
050:
051: /**
052: * Logger for this class
053: */
054: private static final Logger logger = LoggerFactory
055: .getLogger(AbstractMetaDataBasedSearchCallback.class);
056:
057: private final IDatabaseConnection connection;
058:
059: /**
060: * Defautl constructor.
061: * @param connection connection where the edges will be calculated from
062: */
063: public AbstractMetaDataBasedSearchCallback(
064: IDatabaseConnection connection) {
065: this .connection = connection;
066: }
067:
068: /**
069: * Get the connection where the edges will be calculated from.
070: * @return the connection where the edges will be calculated from
071: */
072: public IDatabaseConnection getConnection() {
073: logger.debug("getConnection() - start");
074:
075: return connection;
076: }
077:
078: protected static final int IMPORT = 0;
079: protected static final int EXPORT = 1;
080:
081: /**
082: * indexes of the column names on the MetaData result sets.
083: */
084: protected static final int[] TABLENAME_INDEXES = { 3, 7 };
085: protected static final int[] PK_INDEXES = { 4, 4 };
086: protected static final int[] FK_INDEXES = { 8, 8 };
087:
088: /**
089: * Get the nodes using the direct foreign key dependency, i.e, if table A has
090: * a FK for a table B, then getNodesFromImportedKeys(A) will return B.
091: * @param node table name
092: * @return tables with direct FK dependency from node
093: * @throws SearchException
094: */
095: protected SortedSet getNodesFromImportedKeys(Object node)
096: throws SearchException {
097: logger.debug("getNodesFromImportedKeys(node=" + node
098: + ") - start");
099:
100: return getNodes(IMPORT, node);
101: }
102:
103: /**
104: * Get the nodes using the reverse foreign key dependency, i.e, if table C has
105: * a FK for a table A, then getNodesFromExportedKeys(A) will return C.<br>
106: *
107: * <strong>NOTE:</strong> this method should be used only as an auxiliary
108: * method for sub-classes that also use <code>getNodesFromImportedKeys()</code>
109: * or something similiar, otherwise the generated sequence of tables might not
110: * work when inserted in the database (as some tables might be missing).
111: * <br>
112: * @param node table name
113: * @return tables with reverse FK dependency from node
114: * @throws SearchException
115: */
116: protected SortedSet getNodesFromExportedKeys(Object node)
117: throws SearchException {
118: logger.debug("getNodesFromExportedKeys(node=" + node
119: + ") - start");
120:
121: return getNodes(EXPORT, node);
122: }
123:
124: /**
125: * Get the nodes using the both direct and reverse foreign key dependency, i.e,
126: * if table C has a FK for a table A and table A has a FK for a table B, then
127: * getNodesFromImportAndExportedKeys(A) will return B and C.
128: * @param node table name
129: * @return tables with reverse and direct FK dependency from node
130: * @throws SearchException
131: */
132: protected SortedSet getNodesFromImportAndExportKeys(Object node)
133: throws SearchException {
134: logger.debug("getNodesFromImportAndExportKeys(node=" + node
135: + ") - start");
136:
137: SortedSet importedNodes = getNodesFromImportedKeys(node);
138: SortedSet exportedNodes = getNodesFromExportedKeys(node);
139: importedNodes.addAll(exportedNodes);
140: return importedNodes;
141: }
142:
143: private SortedSet getNodes(int type, Object node)
144: throws SearchException {
145: logger.debug("getNodes(type=" + type + ", node=" + node
146: + ") - start");
147:
148: try {
149: Connection conn = this .connection.getConnection();
150: String schema = this .connection.getSchema();
151: DatabaseMetaData metaData = conn.getMetaData();
152: SortedSet edges = new TreeSet();
153: getNodes(type, node, conn, schema, metaData, edges);
154: return edges;
155: } catch (SQLException e) {
156: logger.error("getNodes()", e);
157:
158: throw new SearchException(e);
159: }
160: }
161:
162: private void getNodes(int type, Object node, Connection conn,
163: String schema, DatabaseMetaData metaData, SortedSet edges)
164: throws SearchException {
165: logger.debug("getNodes(type=" + type + ", node=" + node
166: + ", conn=" + conn + ", schema=" + schema
167: + ", metaData=" + metaData + ", edges=" + edges
168: + ") - start");
169:
170: if (logger.isDebugEnabled()) {
171: logger.debug("Getting edges for node " + node);
172: }
173: try {
174: if (!(node instanceof String)) {
175: throw new IllegalArgumentException(
176: "node should be a String, not a "
177: + node.getClass().getName());
178: }
179: String tableName = (String) node;
180: ResultSet rs = null;
181: switch (type) {
182: case IMPORT:
183: rs = metaData.getImportedKeys(null, schema, tableName);
184: break;
185: case EXPORT:
186: rs = metaData.getExportedKeys(null, schema, tableName);
187: break;
188: }
189: while (rs.next()) {
190: int index = TABLENAME_INDEXES[type];
191: String dependentTableName = rs.getString(index);
192: String pkColumn = rs.getString(PK_INDEXES[type]);
193: String fkColumn = rs.getString(FK_INDEXES[type]);
194: IEdge edge = newEdge(rs, type, tableName,
195: dependentTableName, fkColumn, pkColumn);
196: if (logger.isDebugEnabled()) {
197: logger.debug("Adding edge " + edge);
198: }
199: edges.add(edge);
200: }
201: } catch (SQLException e) {
202: logger.error("getNodes()", e);
203:
204: throw new SearchException(e);
205: }
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(
222: ResultSet rs, int type, String from, String to,
223: String fkColumn, String pkColumn) throws SearchException {
224: logger.debug("createFKEdge(rs=" + rs + ", type=" + type
225: + ", from=" + from + ", to=" + to + ", fkColumn="
226: + fkColumn + ", pkColumn=" + pkColumn + ") - start");
227:
228: return type == IMPORT ? new ForeignKeyRelationshipEdge(from,
229: to, fkColumn, pkColumn)
230: : new ForeignKeyRelationshipEdge(to, from, fkColumn,
231: pkColumn);
232: }
233:
234: /**
235: * This method can be overwritten by the sub-classes if they need to decorate
236: * the edge (for instance, providing an Edge that contains the primary and
237: * foreign keys used).
238: * @param rs database meta-data result set
239: * @param type type of relationship (IMPORT or EXPORT)
240: * @param from name of the table representing the 'from' node
241: * @param to name of the table representing the 'to' node
242: * @param fkColumn name of the foreign key column
243: * @param pkColumn name of the primary key column
244: * @return edge representing the relationship between the 2 tables, according to
245: * the type
246: * @throws SearchException not thrown in this method (but might on sub-classes)
247: */
248: protected IEdge newEdge(ResultSet rs, int type, String from,
249: String to, String fkColumn, String pkColumn)
250: throws SearchException {
251: logger.debug("newEdge(rs=" + rs + ", type=" + type + ", from="
252: + from + ", to=" + to + ", fkColumn=" + fkColumn
253: + ", pkColumn=" + pkColumn + ") - start");
254:
255: return createFKEdge(rs, type, from, to, fkColumn, pkColumn);
256: }
257:
258: }
|