001: package net.sourceforge.squirrel_sql.plugins.refactoring.commands;
002:
003: /*
004: * Copyright (C) 2006 Rob Manning
005: * manningr@user.sourceforge.net
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this library; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020: */
021: import java.awt.event.ActionEvent;
022: import java.awt.event.ActionListener;
023: import java.sql.PreparedStatement;
024: import java.sql.ResultSet;
025: import java.sql.SQLException;
026: import java.util.ArrayList;
027: import java.util.HashSet;
028: import java.util.List;
029:
030: import net.sourceforge.squirrel_sql.client.gui.ProgessCallBackDialog;
031: import net.sourceforge.squirrel_sql.client.session.DefaultSQLExecuterHandler;
032: import net.sourceforge.squirrel_sql.client.session.ISession;
033: import net.sourceforge.squirrel_sql.client.session.SQLExecuterTask;
034: import net.sourceforge.squirrel_sql.fw.dialects.DialectFactory;
035: import net.sourceforge.squirrel_sql.fw.dialects.HibernateDialect;
036: import net.sourceforge.squirrel_sql.fw.dialects.UserCancelledOperationException;
037: import net.sourceforge.squirrel_sql.fw.gui.ErrorDialog;
038: import net.sourceforge.squirrel_sql.fw.gui.GUIUtils;
039: import net.sourceforge.squirrel_sql.fw.sql.ForeignKeyInfo;
040: import net.sourceforge.squirrel_sql.fw.sql.IDatabaseObjectInfo;
041: import net.sourceforge.squirrel_sql.fw.sql.ITableInfo;
042: import net.sourceforge.squirrel_sql.fw.sql.SQLDatabaseMetaData;
043: import net.sourceforge.squirrel_sql.fw.sql.SQLUtilities;
044: import net.sourceforge.squirrel_sql.fw.util.StringManager;
045: import net.sourceforge.squirrel_sql.fw.util.StringManagerFactory;
046: import net.sourceforge.squirrel_sql.fw.util.StringUtilities;
047: import net.sourceforge.squirrel_sql.fw.util.log.ILogger;
048: import net.sourceforge.squirrel_sql.fw.util.log.LoggerController;
049:
050: public class DropTablesCommand extends AbstractRefactoringCommand {
051: /** Logger for this class. */
052: private final ILogger s_log = LoggerController
053: .createLogger(DropTablesCommand.class);
054:
055: /** Internationalized strings for this class */
056: private static final StringManager s_stringMgr = StringManagerFactory
057: .getStringManager(DropTablesCommand.class);
058:
059: private List<ITableInfo> orderedTables = null;
060:
061: private DropTableCommandExecHandler handler = null;
062:
063: ProgessCallBackDialog getOrderedTablesCallBack = null;
064:
065: private static interface i18n {
066:
067: //i18n[DropTablesCommand.progressDialogAnalyzeTitle=Analyzing tables to drop]
068: String PROGRESS_DIALOG_ANALYZE_TITLE = s_stringMgr
069: .getString("DropTablesCommand.progressDialogAnalyzeTitle");
070:
071: //i18n[DropTablesCommand.progressDialogDropTitle=Dropping tables]
072: String PROGRESS_DIALOG_DROP_TITLE = s_stringMgr
073: .getString("DropTablesCommand.progressDialogDropTitle");
074:
075: //i18n[DropTablesCommand.loadingPrefix=Analyzing table:]
076: String LOADING_PREFIX = s_stringMgr
077: .getString("DropTablesCommand.loadingPrefix");
078:
079: //i18n[DropTablesCommand.droppingConstraintPrefix=Dropping Constraint:]
080: String DROPPING_CONSTRAINT_PREFIX = s_stringMgr
081: .getString("DropTablesCommand.droppingConstraintPrefix");
082:
083: //i18n[DropTablesCommand.droppingTablePrefix=Dropping table:]
084: String DROPPING_TABLE_PREFIX = s_stringMgr
085: .getString("DropTablesCommand.droppingTablePrefix");
086:
087: }
088:
089: /**
090: * A set of materialized view names in the same schema as the table(s)
091: * being dropped
092: */
093: private HashSet<String> matViewLookup = null;
094:
095: /**
096: *
097: * @param session Current session..
098: * @param tables Array of <TT>IDatabaseObjectInfo</TT> objects
099: * representing the tables to be deleted.
100: *
101: * @throws IllegalArgumentException
102: * Thrown if a <TT>null</TT> <TT>ISession</TT> passed.
103: */
104: public DropTablesCommand(ISession session,
105: IDatabaseObjectInfo[] tables) {
106: super (session, tables);
107: }
108:
109: /**
110: * Drop selected tables in the object tree.
111: */
112: public void execute() {
113: try {
114: super .showDropTableDialog(new DropTablesActionListener(),
115: new ShowSQLListener());
116: } catch (Exception e) {
117: s_log.error("Unexpected exception " + e.getMessage(), e);
118: }
119: }
120:
121: @Override
122: protected void getSQLFromDialog(SQLResultListener listener) {
123: HibernateDialect dialect = null;
124: List<ITableInfo> tables = dropTableDialog.getTableInfoList();
125: boolean cascadeConstraints = dropTableDialog
126: .getCascadeConstraints();
127:
128: ArrayList<String> result = new ArrayList<String>();
129: try {
130: orderedTables = getOrderedTables(tables);
131:
132: dialect = DialectFactory.getDialect(
133: DialectFactory.DEST_TYPE, _session.getApplication()
134: .getMainFrame(), _session.getMetaData());
135: String sep = _session.getQueryTokenizer()
136: .getSQLStatementSeparator();
137:
138: // Drop FK constraints before dropping any tables. Otherwise, we
139: // may drop the child table prior to dropping it's FKs, which would
140: // be an error.
141: if (cascadeConstraints) {
142: for (ITableInfo info : orderedTables) {
143: List<String> dropFKSQLs = getDropChildFKConstraints(
144: dialect, info);
145: for (String dropFKSQL : dropFKSQLs) {
146: StringBuilder dropSQL = new StringBuilder();
147: dropSQL.append(dropFKSQL);
148: dropSQL.append("\n");
149: dropSQL.append(sep);
150: result.add(dropSQL.toString());
151: }
152: }
153: }
154:
155: // Set cascadeConstraints to false, since we've already generated the
156: // SQL for dropping these constraints above.
157: for (ITableInfo info : orderedTables) {
158: boolean isMaterializedView = isMaterializedView(info,
159: _session);
160: List<String> sqls = dialect.getTableDropSQL(info,
161: false, // cascadeConstraints
162: isMaterializedView);
163: for (String sql : sqls) {
164: StringBuilder dropSQL = new StringBuilder();
165: dropSQL.append(sql);
166: dropSQL.append("\n");
167: dropSQL.append(sep);
168: result.add(dropSQL.toString());
169: }
170: }
171: } catch (UnsupportedOperationException e2) {
172: //i18n[DropTablesCommand.unsupportedOperationMsg=The {0}
173: //dialect doesn't support dropping tables]
174: String msg = s_stringMgr.getString(
175: "DropTablesCommand.unsupportedOperationMsg",
176: dialect.getDisplayName());
177: _session.showMessage(msg);
178: } catch (UserCancelledOperationException e) {
179: // user cancelled selecting a dialect. do nothing?
180: }
181: listener.finished(result.toArray(new String[result.size()]));
182: }
183:
184: private List<String> getDropChildFKConstraints(
185: HibernateDialect dialect, ITableInfo ti) {
186: ArrayList<String> result = new ArrayList<String>();
187: ForeignKeyInfo[] fks = ti.getExportedKeys();
188: for (int i = 0; i < fks.length; i++) {
189: ForeignKeyInfo info = fks[i];
190: String fkName = info.getForeignKeyName();
191: String fkTable = info.getForeignKeyTableName();
192: result.add(dialect.getDropForeignKeySQL(fkName, fkTable));
193: }
194: return result;
195: }
196:
197: private List<ITableInfo> getOrderedTables(
198: final List<ITableInfo> tables) {
199: List<ITableInfo> result = tables;
200: SQLDatabaseMetaData md = _session.getSQLConnection()
201: .getSQLMetaData();
202:
203: try {
204: // Create the analysis dialog using the EDT, and wait for it to finish.
205: GUIUtils.processOnSwingEventThread(new Runnable() {
206: public void run() {
207: getOrderedTablesCallBack = new ProgessCallBackDialog(
208: dropTableDialog,
209: i18n.PROGRESS_DIALOG_ANALYZE_TITLE, tables
210: .size());
211:
212: getOrderedTablesCallBack
213: .setLoadingPrefix(i18n.LOADING_PREFIX);
214: }
215: }, true);
216:
217: // Now, get the drop order (same as delete) and update the dialog
218: // status while doing so.
219: result = SQLUtilities.getDeletionOrder(tables, md,
220: getOrderedTablesCallBack);
221: } catch (SQLException e) {
222: s_log.error(
223: "Encountered exception while attempting to order tables "
224: + "according to constraints: "
225: + e.getMessage(), e);
226: }
227: return result;
228: }
229:
230: /**
231: * Returns a boolean value indicating whether or not the specified table
232: * info is not only a table, but also a materialized view.
233: *
234: * @param ti
235: * @param session
236: * @return
237: */
238: private boolean isMaterializedView(ITableInfo ti, ISession session) {
239: if (!DialectFactory.isOracle(session.getMetaData())) {
240: // Only Oracle supports materialized views directly.
241: return false;
242: }
243: if (matViewLookup == null) {
244: initMatViewLookup(session, ti.getSchemaName());
245: }
246: return matViewLookup.contains(ti.getSimpleName());
247: }
248:
249: private void initMatViewLookup(ISession session, String schema) {
250: matViewLookup = new HashSet<String>();
251: // There is no good way using JDBC metadata to tell if the table is a
252: // materialized view. So, we need to query the data dictionary to find
253: // that out. Get all table names whose comment indicates that they are
254: // a materialized view.
255: String sql = "SELECT TABLE_NAME FROM ALL_TAB_COMMENTS "
256: + "where COMMENTS like 'snapshot%' " + "and OWNER = ? ";
257:
258: PreparedStatement stmt = null;
259: ResultSet rs = null;
260: try {
261: stmt = session.getSQLConnection().prepareStatement(sql);
262: stmt.setString(1, schema);
263: rs = stmt.executeQuery();
264: if (rs.next()) {
265: String tableName = rs.getString(1);
266: matViewLookup.add(tableName);
267: }
268: } catch (SQLException e) {
269: s_log.error(
270: "Unexpected exception while attempting to find mat. views "
271: + "in schema: " + schema, e);
272: } finally {
273: SQLUtilities.closeResultSet(rs);
274: SQLUtilities.closeStatement(stmt);
275: }
276:
277: }
278:
279: private class ShowSQLListener implements ActionListener,
280: SQLResultListener {
281:
282: /* (non-Javadoc)
283: * @see net.sourceforge.squirrel_sql.plugins.refactoring.commands.DropTablesCommand.SQLResultListener#finished(java.lang.String[])
284: */
285: public void finished(String[] sql) {
286: if (sql.length == 0) {
287: // TODO: tell the user no changes
288: return;
289: }
290: StringBuffer script = new StringBuffer();
291: for (int i = 0; i < sql.length; i++) {
292: script.append(sql[i]);
293: script.append("\n\n");
294: }
295:
296: ErrorDialog sqldialog = new ErrorDialog(dropTableDialog,
297: script.toString());
298: //i18n[DropTablesCommand.sqlDialogTitle=Drop Table SQL]
299: String title = s_stringMgr
300: .getString("DropTablesCommand.sqlDialogTitle");
301: sqldialog.setTitle(title);
302: sqldialog.setVisible(true);
303: }
304:
305: public void actionPerformed(ActionEvent e) {
306: _session.getApplication().getThreadPool().addTask(
307: new GetSQLTask(this ));
308: }
309: }
310:
311: private class DropTablesActionListener implements ActionListener,
312: SQLResultListener {
313:
314: /* (non-Javadoc)
315: * @see net.sourceforge.squirrel_sql.plugins.refactoring.commands.DropTablesCommand.SQLResultListener#finished(java.lang.String[])
316: */
317: public void finished(String[] sqls) {
318: final StringBuilder script = new StringBuilder();
319: for (int i = 0; i < sqls.length; i++) {
320: String sql = sqls[i];
321: if (s_log.isDebugEnabled()) {
322: s_log.debug("DropTablesCommand: adding SQL - "
323: + sql);
324: }
325: script.append(sql);
326: script.append("\n");
327: }
328: // Shows the user a dialog to let them know what's happening
329: GUIUtils.processOnSwingEventThread(new Runnable() {
330: public void run() {
331: DropTablesCommand.this .handler = new DropTableCommandExecHandler(
332: _session);
333:
334: final SQLExecuterTask executer = new SQLExecuterTask(
335: _session, script.toString(),
336: DropTablesCommand.this .handler);
337: executer.setSchemaCheck(false);
338: _session.getApplication().getThreadPool().addTask(
339: new Runnable() {
340: public void run() {
341: executer.run();
342:
343: GUIUtils
344: .processOnSwingEventThread(new Runnable() {
345: public void run() {
346: dropTableDialog
347: .setVisible(false);
348: _session
349: .getSchemaInfo()
350: .reloadAll();
351: }
352: });
353: }
354: });
355:
356: }
357: });
358: }
359:
360: public void actionPerformed(ActionEvent e) {
361: if (dropTableDialog == null) {
362: return;
363: }
364: _session.getApplication().getThreadPool().addTask(
365: new GetSQLTask(this ));
366: }
367:
368: }
369:
370: public class GetSQLTask implements Runnable {
371:
372: private SQLResultListener _listener;
373:
374: public GetSQLTask(SQLResultListener listener) {
375: _listener = listener;
376: }
377:
378: /* (non-Javadoc)
379: * @see java.lang.Runnable#run()
380: */
381: public void run() {
382: getSQLFromDialog(_listener);
383: }
384: }
385:
386: private class DropTableCommandExecHandler extends
387: DefaultSQLExecuterHandler {
388:
389: ProgessCallBackDialog cb = null;
390:
391: /** This is used to track the number of tables seen so far, so that we
392: * can pick the right one from the ordered table list to display as the
393: * table name of the table currently being dropped - yes, a hack!
394: */
395: int tableCount = 0;
396:
397: public DropTableCommandExecHandler(ISession session) {
398: super (session);
399: cb = new ProgessCallBackDialog(dropTableDialog,
400: i18n.PROGRESS_DIALOG_DROP_TITLE,
401: DropTablesCommand.this .orderedTables.size());
402: }
403:
404: /* (non-Javadoc)
405: * @see net.sourceforge.squirrel_sql.client.session.DefaultSQLExecuterHandler#sqlStatementCount(int)
406: */
407: @Override
408: public void sqlStatementCount(int statementCount) {
409: cb.setTotalItems(statementCount);
410: }
411:
412: /* (non-Javadoc)
413: * @see net.sourceforge.squirrel_sql.client.session.DefaultSQLExecuterHandler#sqlToBeExecuted(java.lang.String)
414: */
415: @Override
416: public void sqlToBeExecuted(String sql) {
417: if (s_log.isDebugEnabled()) {
418: s_log.debug("Statement to be executed: " + sql);
419: }
420:
421: if (sql.startsWith("ALTER")) {
422: cb.setLoadingPrefix(i18n.DROPPING_CONSTRAINT_PREFIX);
423: // Hack!!! hopefully the FK name will always be the last token!
424: String[] parts = StringUtilities.split(sql, ' ');
425: cb.currentlyLoading(parts[parts.length - 1]);
426: } else {
427: cb.setLoadingPrefix(i18n.DROPPING_TABLE_PREFIX);
428: if (tableCount < DropTablesCommand.this .orderedTables
429: .size()) {
430: ITableInfo ti = DropTablesCommand.this.orderedTables
431: .get(tableCount);
432: cb.currentlyLoading(ti.getSimpleName());
433: }
434: tableCount++;
435: }
436: }
437:
438: }
439:
440: }
|