package er.extensions.concurrency;
import java.util.concurrent.Callable;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.eocontrol.EOObjectStore;
import com.webobjects.eocontrol.EOObjectStoreCoordinator;
import er.extensions.appserver.ERXApplication;
import er.extensions.eof.ERXEC;
/**
* A convenience class that provides some common logic that is used in
* {@link Runnable} and/or {@link Callable} tasks. It provides support for
* cleaning up editing context locks at the end of your task's run() method
* just like the behavior at the end of a normal R-R loop.
*
* @author kieran
* @param <T> if you implement a {@link Callable} this is the result type of
* the callable, if you implement a {@link Runnable} you don't
* need to specify this.
*/
public abstract class ERXTask<T> {
private volatile EOObjectStore _parentObjectStore;
private Long _taskEditingContextTimestampLag;
/**
* If you have a {@link Runnable} do not override run directly. Instead,
* override _run. The run method in ERXTask makes your _run method appear
* to be in a request, and cleans up resources at the end of the request.
*/
public final void run() {
ERXApplication._startRequest();
try {
_run();
}
finally {
ERXApplication._endRequest();
}
}
/**
* Override _run to provide your task's implementation if you have a {@link Runnable}.
*/
public void _run() { }
/**
* If you have a {@link Callable} do not override call directly. Instead,
* override _call. The call method in ERXTask makes your _call method appear
* to be in a request, and cleans up resources at the end of the request.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
public final T call() throws Exception {
ERXApplication._startRequest();
try {
return _call();
}
finally {
ERXApplication._endRequest();
}
}
/**
* Override _call to provide your task's implementation if you have a {@link Callable}.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
public T _call() throws Exception {
return null;
}
/**
* 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;
}
/**
* <strong>You must manually lock and unlock the editing context returned by
* this method.</strong> 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();
}
}