Revision: 3634
Author: [email protected]
Date: Wed Jun 23 11:55:57 2010
Log: Two fixes to profiling related to threading. The first problem is the profile results were being copied previously so changes would be done on the foreground thread. This copying was being done on the background thread but needed to be done on the foreground to prevent the getters from creating an inconsistent copy.

The second change was to also copy the table and columns of the profile being copied. This way the table would not be changed while doing the profiling. This was causing profiles to come out empty as the table would end up populating on a different thread after the profile was completed on the background thread.
http://code.google.com/p/power-architect/source/detail?r=3634

Modified:
/trunk/src/main/java/ca/sqlpower/architect/profile/AbstractProfileResult.java /trunk/src/main/java/ca/sqlpower/architect/profile/AbstractTableProfileCreator.java
 /trunk/src/main/java/ca/sqlpower/architect/profile/ColumnProfileResult.java
 /trunk/src/main/java/ca/sqlpower/architect/profile/ProfileManagerImpl.java
 /trunk/src/main/java/ca/sqlpower/architect/profile/TableProfileResult.java

=======================================
--- /trunk/src/main/java/ca/sqlpower/architect/profile/AbstractProfileResult.java Wed Apr 14 09:10:02 2010 +++ /trunk/src/main/java/ca/sqlpower/architect/profile/AbstractProfileResult.java Wed Jun 23 11:55:57 2010
@@ -77,8 +77,12 @@
         this.profiledObject = profiledObject;
     }

-    public AbstractProfileResult(AbstractProfileResult<T> profileToCopy) {
-        this(profileToCopy.getProfiledObject());
+    /**
+ * Copies the properties from the profile to copy. The profiled object can be a different
+     * object than the one in the profile being copied if desired.
+     */
+ public AbstractProfileResult(AbstractProfileResult<T> profileToCopy, T profiledObject) {
+        this(profiledObject);
         this.createStartTime = profileToCopy.createStartTime;
         this.createEndTime = profileToCopy.createEndTime;
         this.ex = profileToCopy.ex;
=======================================
--- /trunk/src/main/java/ca/sqlpower/architect/profile/AbstractTableProfileCreator.java Mon May 31 08:31:06 2010 +++ /trunk/src/main/java/ca/sqlpower/architect/profile/AbstractTableProfileCreator.java Wed Jun 23 11:55:57 2010
@@ -21,15 +21,7 @@

 import org.apache.log4j.Logger;

-import ca.sqlpower.architect.enterprise.ArchitectPersisterSuperConverter;
-import ca.sqlpower.architect.enterprise.ArchitectSessionPersister;
-import ca.sqlpower.dao.SPPersisterListener;
-import ca.sqlpower.object.SPObject;
-import ca.sqlpower.sql.DataSourceCollection;
-import ca.sqlpower.sql.PlDotIni;
-import ca.sqlpower.sql.SPDataSource;
 import ca.sqlpower.util.MonitorableImpl;
-import ca.sqlpower.util.SessionNotFoundException;

 /**
  * Provides a template method for doProfile which alleviates the
@@ -48,73 +40,35 @@
* or without success) unless this profile population has been cancelled,
      * in which case it fires a profileCancelled event.
      */
-    public final boolean doProfile(final TableProfileResult actualTPR) {
-        final TableProfileResult tpr = new TableProfileResult(actualTPR);
-
-        //Setting the profile result UUID to match which allows the
-        //persister to update the actual tpr at the end.
-        tpr.setUUID(actualTPR.getUUID());
-        //Need to do the same for the parent pointer but do not want
-        //to attach it to the actual profile manager.
-        ProfileManager backgroundPM = new ProfileManagerImpl();
-        backgroundPM.setUUID(actualTPR.getParent().getUUID());
-        tpr.setParent(backgroundPM);
+    public final boolean doProfile(final TableProfileResult tpr) {

         MonitorableImpl pm = (MonitorableImpl) tpr.getProgressMonitor();
+        tpr.begin("Profiling");
         try {
-            tpr.begin("Profiling");
-            try {
-                tpr.fireProfileStarted();
-                pm.setMessage(tpr.getProfiledObject().getName());
-                if (!pm.isCancelled()) {
-                    pm.setStarted(true);
-                    pm.setFinished(false);
-                    tpr.setCreateStartTime(System.currentTimeMillis());
-                    doProfileImpl(tpr);
-                }
-            } catch (Exception ex) {
-                tpr.setException(ex);
-                logger.error("Profile failed. Saving exception:", ex);
-            } finally {
-                tpr.setCreateEndTime(System.currentTimeMillis());
-                pm.setProgress(pm.getProgress() + 1);
-                pm.setFinished(true);
-                // this somehow fixes a progress bar visibility issue
-                pm.setStarted(false);
-                if (pm.isCancelled()) {
-                    tpr.fireProfileCancelled();
-                } else {
-                    tpr.fireProfileFinished();
-                }
-            }
-            tpr.commit();
+            tpr.fireProfileStarted();
+            pm.setMessage(tpr.getProfiledObject().getName());
+            if (!pm.isCancelled()) {
+                pm.setStarted(true);
+                pm.setFinished(false);
+                tpr.setCreateStartTime(System.currentTimeMillis());
+                doProfileImpl(tpr);
+            }
+        } catch (Exception ex) {
+            tpr.setException(ex);
+            logger.error("Profile failed. Saving exception:", ex);
         } finally {
-            Runnable runner = new Runnable() {
-                public void run() {
- //None of the profiling creates or saves any data source information so an - //empty data source is used for the converter. If the profiling stores - //data source information in the future we may need the data source collection
-                    //in the project.
- DataSourceCollection<SPDataSource> dsCollection = new PlDotIni();
-
- SPObject root = actualTPR.getWorkspaceContainer().getWorkspace();
-                    ArchitectPersisterSuperConverter converter =
- new ArchitectPersisterSuperConverter(dsCollection, root);
-                    ArchitectSessionPersister persister =
- new ArchitectSessionPersister("Profiling persister", root, converter); - persister.setWorkspaceContainer(actualTPR.getWorkspaceContainer()); - SPPersisterListener eventCreator = new SPPersisterListener(persister, converter);
-                    eventCreator.persistObject(tpr,
- actualTPR.getParent().getChildren(TableProfileResult.class).indexOf(actualTPR),
-                            false);
-                }
-            };
-            try {
-                actualTPR.getRunnableDispatcher().runInForeground(runner);
-            } catch (SessionNotFoundException e) {
-                runner.run();
+            tpr.setCreateEndTime(System.currentTimeMillis());
+            pm.setProgress(pm.getProgress() + 1);
+            pm.setFinished(true);
+            // this somehow fixes a progress bar visibility issue
+            pm.setStarted(false);
+            if (pm.isCancelled()) {
+                tpr.fireProfileCancelled();
+            } else {
+                tpr.fireProfileFinished();
             }
         }
+        tpr.commit();
         return !pm.isCancelled();
     }

=======================================
--- /trunk/src/main/java/ca/sqlpower/architect/profile/ColumnProfileResult.java Mon Apr 19 14:18:19 2010 +++ /trunk/src/main/java/ca/sqlpower/architect/profile/ColumnProfileResult.java Wed Jun 23 11:55:57 2010
@@ -71,8 +71,8 @@
     /**
      * Deep-copy copy constructor.
      */
-    public ColumnProfileResult(ColumnProfileResult cprToCopy) {
-        super(cprToCopy);
+ public ColumnProfileResult(ColumnProfileResult cprToCopy, SQLColumn col) {
+        super(cprToCopy, col);
         setName("New Column Profile");
         this.avgLength = cprToCopy.avgLength;
         this.avgValue = cprToCopy.avgValue;
=======================================
--- /trunk/src/main/java/ca/sqlpower/architect/profile/ProfileManagerImpl.java Thu May 27 15:03:55 2010 +++ /trunk/src/main/java/ca/sqlpower/architect/profile/ProfileManagerImpl.java Wed Jun 23 11:55:57 2010
@@ -32,8 +32,11 @@
 import org.apache.log4j.Logger;

 import ca.sqlpower.architect.ArchitectProject;
+import ca.sqlpower.architect.enterprise.ArchitectPersisterSuperConverter;
+import ca.sqlpower.architect.enterprise.ArchitectSessionPersister;
 import ca.sqlpower.architect.profile.event.ProfileChangeEvent;
 import ca.sqlpower.architect.profile.event.ProfileChangeListener;
+import ca.sqlpower.dao.SPPersisterListener;
 import ca.sqlpower.object.AbstractSPObject;
 import ca.sqlpower.object.SPObject;
 import ca.sqlpower.object.annotation.Accessor;
@@ -42,12 +45,17 @@
 import ca.sqlpower.object.annotation.NonBound;
 import ca.sqlpower.object.annotation.NonProperty;
 import ca.sqlpower.object.annotation.Transient;
+import ca.sqlpower.sql.DataSourceCollection;
+import ca.sqlpower.sql.PlDotIni;
+import ca.sqlpower.sql.SPDataSource;
+import ca.sqlpower.sqlobject.SQLColumn;
 import ca.sqlpower.sqlobject.SQLDatabase;
 import ca.sqlpower.sqlobject.SQLObject;
 import ca.sqlpower.sqlobject.SQLObjectException;
 import ca.sqlpower.sqlobject.SQLObjectPreEvent;
 import ca.sqlpower.sqlobject.SQLObjectPreEventListener;
 import ca.sqlpower.sqlobject.SQLTable;
+import ca.sqlpower.util.SessionNotFoundException;
 import ca.sqlpower.util.UserPrompter;
 import ca.sqlpower.util.UserPrompter.UserPromptOptions;
 import ca.sqlpower.util.UserPrompter.UserPromptResponse;
@@ -156,37 +164,112 @@
     private List<TableProfileCreator> profileCreators = Arrays.asList(
(TableProfileCreator)new RemoteDatabaseProfileCreator(getDefaultProfileSettings()),
             new LocalReservoirProfileCreator(getDefaultProfileSettings()));
-
+
     /**
- * A Callable interface which populates a single profile result then returns it. - * Profile results don't throw the exceptions their populate() methods encounter, - * but this callable wrapper knows about that, and digs up and rethrows the - * exceptions encountered by populate(). This is more normal, and is also - * compatible with the ExecutorService implementation provided in the standard - * Java library (it handles exceptions and restarts the work queue properly). + * A Callable interface which populates a single profile result then returns + * it. Profile results don't throw the exceptions their populate() methods + * encounter, but this callable wrapper knows about that, and digs up and + * rethrows the exceptions encountered by populate(). This is more normal, + * and is also compatible with the ExecutorService implementation provided + * in the standard Java library (it handles exceptions and restarts the work
+     * queue properly).
+     * <p>
+ * To protect the profiling from threading problems all of the objects that + * exist in the {...@link SPObject} tree are duplicated before being passed to + * the profiler. This way the profiler can do as it desires with the objects
+     * and these objects cannot be modified by outside classes. When the
+     * profiling is one the profiles themselves will be updated on the
+     * foreground thread so the object model stays single threaded.
      */
private class ProfileResultCallable implements Callable<TableProfileResult> {

         /**
          * The table profile result this Callable populates.
          */
-        private TableProfileResult tpr;
-
-        ProfileResultCallable(TableProfileResult tpr) {
- if (tpr == null) throw new NullPointerException("Can't populate a null profile result!");
-            this.tpr = tpr;
+        private TableProfileResult actualTPR;
+
+        /**
+         * The table profile that is a copy of the real one that will be
+ * populated on a background thread. This prevents any problems with + * threading as the real profile result will not be modified until we
+         * are back on the foreground thread.
+         */
+        private final TableProfileResult tpr;
+
+        ProfileResultCallable(TableProfileResult actualTPR) {
+ if (actualTPR == null) throw new NullPointerException("Can't populate a null profile result!");
+            this.actualTPR = actualTPR;
+            SQLTable table;
+            TableProfileResult tempTPR;
+            try {
+                SQLTable profileTable = actualTPR.getProfiledObject();
+ table = new SQLTable(profileTable.getParentDatabase(), true);
+                table.setUUID(profileTable.getUUID());
+                table.updateToMatch(profileTable);
+                for (SQLColumn col : profileTable.getColumns()) {
+                    SQLColumn newCol = new SQLColumn();
+                    newCol.updateToMatch(col);
+                    newCol.setUUID(col.getUUID());
+                    table.addColumn(newCol);
+                }
+                tempTPR = new TableProfileResult(actualTPR, table);
+
+                //Setting the profile result UUID to match which allows the
+                //persister to update the actual tpr at the end.
+                tempTPR.setUUID(actualTPR.getUUID());
+ //Need to do the same for the parent pointer but do not want
+                //to attach it to the actual profile manager.
+                ProfileManager backgroundPM = new ProfileManagerImpl();
+                backgroundPM.setUUID(actualTPR.getParent().getUUID());
+                tempTPR.setParent(backgroundPM);
+            } catch (Exception e) {
+ //If an exception is thrown during setup define the profile to have an exception on
+                //it and handle appropriately when doing the profile.
+                tempTPR = null;
+                actualTPR.setException(e);
+            }
+            tpr = tempTPR;
         }

         /**
* Populates the profile result, throwing any exceptions encountered.
          */
         public TableProfileResult call() throws Exception {
+            //Exception occurred during setup.
+            if (actualTPR.getException() != null) {
+                throw actualTPR.getException();
+            }
             creator.doProfile(tpr);
// XXX the creator should stash a reference to the exception in tpr, then throw it anyway
             if (tpr.getException() != null) {
                 throw tpr.getException();
             }
-            return tpr;
+            Runnable runner = new Runnable() {
+                public void run() {
+ //None of the profiling creates or saves any data source information so an + //empty data source is used for the converter. If the profiling stores + //data source information in the future we may need the data source collection
+                    //in the project.
+ DataSourceCollection<SPDataSource> dsCollection = new PlDotIni();
+
+ SPObject root = actualTPR.getWorkspaceContainer().getWorkspace();
+                    ArchitectPersisterSuperConverter converter =
+ new ArchitectPersisterSuperConverter(dsCollection, root);
+                    ArchitectSessionPersister persister =
+ new ArchitectSessionPersister("Profiling persister", root, converter); + persister.setWorkspaceContainer(actualTPR.getWorkspaceContainer()); + SPPersisterListener eventCreator = new SPPersisterListener(persister, converter);
+                    eventCreator.persistObject(tpr,
+ actualTPR.getParent().getChildren(TableProfileResult.class).indexOf(actualTPR),
+                            false);
+                }
+            };
+            try {
+                actualTPR.getRunnableDispatcher().runInForeground(runner);
+            } catch (SessionNotFoundException e) {
+                runner.run();
+            }
+            return actualTPR;
         }
     }

=======================================
--- /trunk/src/main/java/ca/sqlpower/architect/profile/TableProfileResult.java Mon Apr 19 14:18:19 2010 +++ /trunk/src/main/java/ca/sqlpower/architect/profile/TableProfileResult.java Wed Jun 23 11:55:57 2010
@@ -97,14 +97,28 @@
* This is a deep-copy copy constructor. (ie: all of the descendants will be * copied as well). However, the progress monitor will be shared between the
      * results.
+     * <p>
+ * The table given to this constructor will be the table that will be the
+     * profiled object. Since this is a deep copy of the table the column
+ * profiles created will be made to match those in the SQLTable given. The + * profile results will be matched by the UUID of the columns being profiled + * to the table columns. If there are column profile results that do not
+     * match the table's columns they will be removed. The existing column
+ * profile results may also be rearranged as they will match the order of
+     * the columns in the table.
      */
-    public TableProfileResult(TableProfileResult tprToCopy) {
-        super(tprToCopy);
+ public TableProfileResult(TableProfileResult tprToCopy, SQLTable table) throws SQLObjectException {
+        super(tprToCopy, table);
         setName("New Table Profile");
         this.rowCount = tprToCopy.rowCount;
         this.progressMonitor = tprToCopy.progressMonitor;
- for (ColumnProfileResult cpr : tprToCopy.getColumnProfileResults()) {
-            addColumnProfileResult(new ColumnProfileResult(cpr));
+        for (SQLColumn col : table.getColumns()) {
+ for (ColumnProfileResult cpr : tprToCopy.getColumnProfileResults()) { + if (cpr.getProfiledObject().getUUID().equals(col.getUUID())) { + addColumnProfileResult(new ColumnProfileResult(cpr, col));
+                    break;
+                }
+            }
         }
     }

Reply via email to