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;
+ }
+ }
}
}