Revision: 3241
Author: silva.josemanuel1
Date: Thu Jan 21 12:13:16 2010
Log: Ensured ETL lineage is preserved when copying/pasting in cases where it wasn't. Also created a prompt for the user in case ETL lineage could not be preserved, so that the user may be notified and continue or cancel the operation.
FIXED - bug 1997: Copy tables causes exception
http://trillian.sqlpower.ca/bugzilla/show_bug.cgi?id=1997
http://code.google.com/p/power-architect/source/detail?r=3241

Added:
 /trunk/src/ca/sqlpower/architect/swingui/ImportSafetyChecker.java
Modified:
 /trunk/src/ca/sqlpower/architect/swingui/ASUtils.java
 /trunk/src/ca/sqlpower/architect/swingui/PlayPen.java

=======================================
--- /dev/null
+++ /trunk/src/ca/sqlpower/architect/swingui/ImportSafetyChecker.java Thu Jan 21 12:13:16 2010
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2010, SQL Power Group Inc.
+ *
+ * This file is part of Power*Architect.
+ *
+ * Power*Architect is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Power*Architect is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ca.sqlpower.architect.swingui;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import ca.sqlpower.object.SPObject;
+import ca.sqlpower.object.SPObjectUtils;
+import ca.sqlpower.sqlobject.SQLColumn;
+import ca.sqlpower.sqlobject.SQLDatabase;
+import ca.sqlpower.sqlobject.SQLObject;
+import ca.sqlpower.util.UserPrompter;
+import ca.sqlpower.util.UserPrompter.UserPromptOptions;
+import ca.sqlpower.util.UserPrompter.UserPromptResponse;
+import ca.sqlpower.util.UserPrompterFactory.UserPromptType;
+
+/**
+ *
+ * Used to filter SQLObjects from an import operation. It was originally made to determine
+ * if a copy and paste operation between different sessions would result
+ * in losing ETL lineage, and notifies the user giving them the option
+ * to continue the operation or not. However, it is hopefully designed in such
+ * a way that other filters could be easily added or implemented.
+ *
+ */
+
+class ImportSafetyChecker {
+
+    /**
+     * The session having objects imported into it.
+     */
+    private final ArchitectSwingSession targetSession;
+
+    /**
+ * Set true by the user prompt if the whole import is to be cancelled (everything will be filtered).
+     */
+    private boolean isCancelled = false;
+
+    /**
+ * Used to prompt the user when ETL lineage will be lost as a result of copying across sessions.
+     */
+    UserPrompter loseLineage;
+
+    ImportSafetyChecker(ArchitectSwingSession session) {
+
+        this.targetSession = session;
+
+        loseLineage = targetSession.createUserPrompter(
+ "One or more columns in {0} have ETL lineage from their source session. " + + "\nThis lineage will not be able to be copied along with them. " + + "\nCopy {0} anyway, and lose ETL lineage of one or more columns?",
+                UserPromptType.BOOLEAN, UserPromptOptions.OK_NOTOK_CANCEL,
+ UserPromptResponse.NOT_OK, UserPromptResponse.NOT_OK, "Yes", "No", "Cancel");
+
+    }
+
+    List<SQLObject> filterImportedItems(Collection<SQLObject> items) {
+        List<SQLObject> acceptedItems = new ArrayList<SQLObject>();
+
+        for (SQLObject item : items) {
+
+            if (accept(item)) {
+                acceptedItems.add(item);
+            }
+
+            if (isCancelled) {
+                return Collections.emptyList();
+            }
+
+        }
+
+        return acceptedItems;
+    }
+
+    /**
+ * Checks if this item should not be filtered out from the import operation.
+     */
+    private boolean accept(SQLObject item) {
+
+        SPObject parent = item;
+
+        while (parent.getParent() != null) {
+            parent = parent.getParent();
+        }
+
+ // Accept the item if it is in the same project/session, since ETL lineage won't be a problem.
+        if (targetSession.getWorkspace() == parent) {
+            return true;
+        }
+        if (!visit(item)) {
+ UserPromptResponse response = loseLineage.promptUser(item.getPhysicalName());
+            if (response == UserPromptResponse.CANCEL) {
+                isCancelled = true;
+            }
+            return (response == UserPromptResponse.OK);
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Checks if this item and its entire subtree pass the filter.
+     */
+    public boolean visit(SQLObject item) {
+
+        boolean subtreeAccepted = true;
+
+        if (item instanceof SQLColumn) {
+ return sourceDatabaseAccessible((SQLColumn) item, targetSession);
+        }
+
+        for (SQLObject child : item.getChildren()) {
+            if (!(subtreeAccepted &= visit(child))) {
+                break;
+            }
+        }
+        return subtreeAccepted;
+    }
+
+    /**
+ * Checks if the given sourceColumn's source database is accessible from the targetSession.
+     * If not, ETL lineage is impossible to preserve.
+     */
+ private boolean sourceDatabaseAccessible(SQLColumn sourceColumn, ArchitectSwingSession targetSession) {
+
+        SQLDatabase sourceSourceDatabase;
+
+        // The source column could be its own source if is is being
+        // copied directly from a database, not a play pen.
+        if (sourceColumn.getSourceColumn() == null) {
+            sourceColumn.setSourceColumn(sourceColumn);
+        }
+ sourceSourceDatabase = SPObjectUtils.getAncestor(sourceColumn.getSourceColumn(), SQLDatabase.class);
+
+        if (targetSession.getSourceDatabases().getDuplicateDbcs(
+                sourceSourceDatabase.getDataSource()) == null) {
+            return false;
+        } else {
+            return true;
+        }
+
+    }
+}
=======================================
--- /trunk/src/ca/sqlpower/architect/swingui/ASUtils.java Fri Jan 15 15:02:04 2010 +++ /trunk/src/ca/sqlpower/architect/swingui/ASUtils.java Thu Jan 21 12:13:16 2010
@@ -526,12 +526,15 @@
                     );
         }

- if (containingSession != currentSession) { //Duplicating a SQLObject across sessions in the same context. + if (containingSession != currentSession) { //Duplicating a SQLObject across sessions in the same context. + ImportSafetyChecker checker = new ImportSafetyChecker(currentSession); + boolean preserveColumnSource = checker.visit(source); // check if the columns can be preserved
+
             return new DuplicateProperties(
true, //Have to add the source db to the project if it's missing.
                     SQLTable.TransferStyles.COPY,
                     false,
-                    true,
+                    preserveColumnSource,
                     true
                     );
         }
@@ -571,6 +574,10 @@
if (duplicateProperties.getDefaultTransferStyle() == TransferStyles.REVERSE_ENGINEER) {
             sourceColumn = source;
} else if (duplicateProperties.getDefaultTransferStyle() == TransferStyles.COPY) { + // getSourceColumn may return null if source is its own source column.
+            if (source.getSourceColumn() == null) {
+                source.setSourceColumn(source);
+            }
             sourceColumn = source.getSourceColumn();
         } else {
             sourceColumn = null;
@@ -606,6 +613,22 @@
List<SQLObject> ancestors = SQLObjectUtils.ancestorList(sourceColumn);
             System.out.println(ancestors);
             SQLObject child = targetSourceDatabase;
+
+ // It seems sometimes the PokeDBTree takes too long to populate the database,
+            // so this makes sure the new database is ready.
+            int waits = 0;
+            while (child.getChildren().size() == 0) {
+                try {
+                    waits++;
+                    Thread.sleep(50);
+                    if (waits > 100) {
+ throw new RuntimeException("Waiting too long for database to be populated.");
+                    }
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
             for (int i = 2; i < ancestors.size(); i++) {
                 SQLObject ancestor = ancestors.get(i);
System.out.println("Child " + child + " ancestor " + ancestor);
=======================================
--- /trunk/src/ca/sqlpower/architect/swingui/PlayPen.java Mon Dec 21 08:27:43 2009 +++ /trunk/src/ca/sqlpower/architect/swingui/PlayPen.java Thu Jan 21 12:13:16 2010
@@ -1530,12 +1530,15 @@
                         errorMessage, getDoStuffException());
                                if (getNextProcess() != null) {
                                        setCancelled(true);
-
                                }
                        }

-                       session.getPlayPen().startCompoundEdit("Drag to 
Playpen"); //$NON-NLS-1$
-
+                       session.getPlayPen().startCompoundEdit("Drag to 
Playpen"); //$NON-NLS-1$
+
+ // Filter out objects that would lose ETL lineage against the user's will.
+                       ImportSafetyChecker checker = new 
ImportSafetyChecker(session);
+                       sqlObjects = checker.filterImportedItems(sqlObjects);
+
                        try {

                                // reset iterator

Reply via email to