diff --git a/Frameworks/Core/ERExtensions/Sources/er/extensions/concurrency/ERXLongResponseTask.java b/Frameworks/Core/ERExtensions/Sources/er/extensions/concurrency/ERXLongResponseTask.java index 87401cf55d4..de82b81b297 100644 --- a/Frameworks/Core/ERExtensions/Sources/er/extensions/concurrency/ERXLongResponseTask.java +++ b/Frameworks/Core/ERExtensions/Sources/er/extensions/concurrency/ERXLongResponseTask.java @@ -7,6 +7,9 @@ import com.webobjects.appserver.WOApplication; import com.webobjects.appserver.WOComponent; +import com.webobjects.eocontrol.EOEditingContext; +import com.webobjects.eocontrol.EOObjectStore; +import com.webobjects.eocontrol.EOObjectStoreCoordinator; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSForwardException; import com.webobjects.foundation.NSMutableArray; @@ -86,7 +89,9 @@ public static NSArray tasks() { } public abstract class DefaultImplementation implements Runnable, ERXLongResponseTask { - + private volatile EOObjectStore _parentObjectStore; + private Long _taskEditingContextTimestampLag; + /** logging support */ public Logger log = Logger.getLogger(ERXUtilities.class); @@ -344,5 +349,92 @@ public WOComponent nextPage() { */ public abstract Object performAction(); + //---------------------- Copied from ERXTask ------------------------------------- + + /** + * See Effective Java item #71 for explanation of this threadsafe lazy + * initialization technique + * + * @return the parent, usually an {@link EOObjectStoreCoordinator} to + * partition the task's EOF intensive work form the rest of the app. + */ + protected final EOObjectStore parentObjectStore() { + EOObjectStore osc = _parentObjectStore; + if (osc == null) { + synchronized (this) { + osc = _parentObjectStore; + if (osc == null) { + _parentObjectStore = osc = ERXTaskObjectStoreCoordinatorPool.objectStoreCoordinator(); + } + } + } + return osc; + } + + /** + * @param parentObjectStore + * the parent, usually an {@link EOObjectStoreCoordinator} to + * partition the task's EOF intensive work from the rest of the + * app. If you are going to manually set this, you should do it + * before starting the task. + */ + public final synchronized void setParentObjectStore(EOObjectStore parentObjectStore) { + _parentObjectStore = parentObjectStore; + } + + /** + * You must manually lock and unlock the editing context returned by + * this method. It is not recommended that you depend on auto + * locking in background threads. + * + * Even though this method currently returns an auto-locking EC if + * auto-locking is turned on for the app, a future update is planned that + * will return a manually locking EC from this method even if auto-locking + * is turned on for regular ECs used in normal request-response execution. + * + * @return a new EOEditingContext. + */ + protected EOEditingContext newEditingContext() { + EOEditingContext ec = ERXEC.newEditingContext(parentObjectStore()); + // if this is not a nested EC, we can set the fetch time stamp + if (!(parentObjectStore() instanceof EOEditingContext)) { + ec.setFetchTimestamp(taskEditingContextTimestampLag()); + } + return ec; + } + + /** + * By design EOEditingContext's have a fetch timestamp (default is 1 hour) + * that effectively creates an in-memory caching system for EOs. This works + * great for users browsing through pages in the app. However, experience + * has shown that background EOF tasks are performing updates based on the + * state of other EOs, and thus we want to This is a long-running task. The + * last thing I want to do is perform a long running task with stale EOs, so + * we lazily create a fetch timestamp of the current time when we create the + * first EC and thus ensure fresh data. Secondly, we continue, by default to + * use this timestamp for the duration of the task since experience has + * shown that by doing so we can prevent unnecessary database fetching + * especially when our task is adding lots of items to a single relationship + * in batches. + * + * However if you want fresh data each time you create an EC in your task, + * feel free to set the fetch time stamp to the current time in your task + * each time you create a new EC. + * + * For R-R ec's we prefer fresh data on new pages. However for long running + * tasks, it is often best pick a single point in time, usually when the + * first ec is created as the timestamp lag. This works well when we are + * iterating and making new ec's especially if we are adding 100's of items + * to a relationship and cycling ec's + * + * @return the timestamp lag to use for new ec's created in the task thread. + */ + protected long taskEditingContextTimestampLag() { + if (_taskEditingContextTimestampLag == null) { + _taskEditingContextTimestampLag = Long.valueOf(System.currentTimeMillis()); + } + return _taskEditingContextTimestampLag.longValue(); + } + } } diff --git a/Frameworks/Core/ERExtensions/Sources/er/extensions/concurrency/ERXTask.java b/Frameworks/Core/ERExtensions/Sources/er/extensions/concurrency/ERXTask.java index c4dd1ec983a..1d9a81e8a9c 100644 --- a/Frameworks/Core/ERExtensions/Sources/er/extensions/concurrency/ERXTask.java +++ b/Frameworks/Core/ERExtensions/Sources/er/extensions/concurrency/ERXTask.java @@ -150,7 +150,7 @@ protected EOEditingContext newEditingContext() { * * @return the timestamp lag to use for new ec's created in the task thread. */ - private long taskEditingContextTimestampLag() { + protected long taskEditingContextTimestampLag() { if (_taskEditingContextTimestampLag == null) { _taskEditingContextTimestampLag = Long.valueOf(System.currentTimeMillis()); } diff --git a/Frameworks/Core/ERExtensions/Sources/er/extensions/concurrency/ERXWOLongResponsePage.java b/Frameworks/Core/ERExtensions/Sources/er/extensions/concurrency/ERXWOLongResponsePage.java index f61159424eb..1b4722857d3 100644 --- a/Frameworks/Core/ERExtensions/Sources/er/extensions/concurrency/ERXWOLongResponsePage.java +++ b/Frameworks/Core/ERExtensions/Sources/er/extensions/concurrency/ERXWOLongResponsePage.java @@ -3,9 +3,13 @@ import com.webobjects.appserver.WOApplication; import com.webobjects.appserver.WOComponent; import com.webobjects.appserver.WOContext; +import com.webobjects.eocontrol.EOEditingContext; +import com.webobjects.eocontrol.EOObjectStore; +import com.webobjects.eocontrol.EOObjectStoreCoordinator; import com.webobjects.woextensions.WOLongResponsePage; import er.extensions.appserver.ERXApplication; +import er.extensions.eof.ERXEC; /** * ERXWOLongResponsePage is just like WOLongResponsePage except that it @@ -21,6 +25,9 @@ public abstract class ERXWOLongResponsePage extends WOLongResponsePage { * Java Object Serialization Spec */ private static final long serialVersionUID = 1L; + private volatile EOObjectStore _parentObjectStore; + private Long _taskEditingContextTimestampLag; + public ERXWOLongResponsePage(WOContext context) { super(context); @@ -44,4 +51,92 @@ public void run() { ERXApplication._endRequest(); } } + + //---------------------- Copied from ERXTask ------------------------------------- + + /** + * See Effective Java item #71 for explanation of this threadsafe lazy + * initialization technique + * + * @return the parent, usually an {@link EOObjectStoreCoordinator} to + * partition the task's EOF intensive work form the rest of the app. + */ + protected final EOObjectStore parentObjectStore() { + EOObjectStore osc = _parentObjectStore; + if (osc == null) { + synchronized (this) { + osc = _parentObjectStore; + if (osc == null) { + _parentObjectStore = osc = ERXTaskObjectStoreCoordinatorPool.objectStoreCoordinator(); + } + } + } + return osc; + } + + /** + * @param parentObjectStore + * the parent, usually an {@link EOObjectStoreCoordinator} to + * partition the task's EOF intensive work from the rest of the + * app. If you are going to manually set this, you should do it + * before starting the task. + */ + public final synchronized void setParentObjectStore(EOObjectStore parentObjectStore) { + _parentObjectStore = parentObjectStore; + } + + /** + * You must manually lock and unlock the editing context returned by + * this method. It is not recommended that you depend on auto + * locking in background threads. + * + * Even though this method currently returns an auto-locking EC if + * auto-locking is turned on for the app, a future update is planned that + * will return a manually locking EC from this method even if auto-locking + * is turned on for regular ECs used in normal request-response execution. + * + * @return a new EOEditingContext. + */ + protected EOEditingContext newEditingContext() { + EOEditingContext ec = ERXEC.newEditingContext(parentObjectStore()); + // if this is not a nested EC, we can set the fetch time stamp + if (!(parentObjectStore() instanceof EOEditingContext)) { + ec.setFetchTimestamp(taskEditingContextTimestampLag()); + } + return ec; + } + + /** + * By design EOEditingContext's have a fetch timestamp (default is 1 hour) + * that effectively creates an in-memory caching system for EOs. This works + * great for users browsing through pages in the app. However, experience + * has shown that background EOF tasks are performing updates based on the + * state of other EOs, and thus we want to This is a long-running task. The + * last thing I want to do is perform a long running task with stale EOs, so + * we lazily create a fetch timestamp of the current time when we create the + * first EC and thus ensure fresh data. Secondly, we continue, by default to + * use this timestamp for the duration of the task since experience has + * shown that by doing so we can prevent unnecessary database fetching + * especially when our task is adding lots of items to a single relationship + * in batches. + * + * However if you want fresh data each time you create an EC in your task, + * feel free to set the fetch time stamp to the current time in your task + * each time you create a new EC. + * + * For R-R ec's we prefer fresh data on new pages. However for long running + * tasks, it is often best pick a single point in time, usually when the + * first ec is created as the timestamp lag. This works well when we are + * iterating and making new ec's especially if we are adding 100's of items + * to a relationship and cycling ec's + * + * @return the timestamp lag to use for new ec's created in the task thread. + */ + protected long taskEditingContextTimestampLag() { + if (_taskEditingContextTimestampLag == null) { + _taskEditingContextTimestampLag = Long.valueOf(System.currentTimeMillis()); + } + return _taskEditingContextTimestampLag.longValue(); + } + }