/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.repeat.support;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.RepeatException;
import org.springframework.batch.repeat.RepeatOperations;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.util.Assert;
/**
* Provides {@link RepeatOperations} support including interceptors that can be
* used to modify or monitor the behaviour at run time.<br>
*
* This implementation is sufficient to be used to configure transactional
* behaviour for each item by making the {@link RepeatCallback} transactional,
* or for the whole batch by making the execute method transactional (but only
* then if the task executor is synchronous).<br>
*
* This class is thread-safe if its collaborators are thread-safe (interceptors,
* terminationPolicy, callback). Normally this will be the case, but clients
* need to be aware that if the task executor is asynchronous, then the other
* collaborators should be also. In particular the {@link RepeatCallback} that
* is wrapped in the execute method must be thread-safe - often it is based on
* some form of data source, which itself should be both thread-safe and
* transactional (multiple threads could be accessing it at any given time, and
* each thread would have its own transaction).<br>
*
* @author Dave Syer
*
*/
public class TaskExecutorRepeatTemplate extends RepeatTemplate {
/**
* Default limit for maximum number of concurrent unfinished results allowed
* by the template.
* {@link #getNextResult(RepeatContext, RepeatCallback, RepeatInternalState)}
* .
*/
public static final int DEFAULT_THROTTLE_LIMIT = 4;
private int throttleLimit = DEFAULT_THROTTLE_LIMIT;
private TaskExecutor taskExecutor = new SyncTaskExecutor();
/**
* Public setter for the throttle limit. The throttle limit is the largest
* number of concurrent tasks that can be executing at one time - if a new
* task arrives and the throttle limit is breached we wait for one of the
* executing tasks to finish before submitting the new one to the
* {@link TaskExecutor}. Default value is {@link #DEFAULT_THROTTLE_LIMIT}.
* N.B. when used with a thread pooled {@link TaskExecutor} the thread pool
* might prevent the throttle limit actually being reached (so make the core
* pool size larger than the throttle limit if possible).
*
* @param throttleLimit the throttleLimit to set.
*/
public void setThrottleLimit(int throttleLimit) {
this.throttleLimit = throttleLimit;
}
/**
* Setter for task executor to be used to run the individual item callbacks.
*
* @param taskExecutor a TaskExecutor
* @throws IllegalArgumentException if the argument is null
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
Assert.notNull(taskExecutor, "A TaskExecutor is required");
this.taskExecutor = taskExecutor;
}
/**
* Use the {@link #setTaskExecutor(TaskExecutor)} to generate a result. The
* internal state in this case is a queue of unfinished result holders of
* type {@link ResultHolder}. The holder with the return value should not be
* on the queue when this method exits. The queue is scoped in the calling
* method so there is no need to synchronize access.
*
*/
@Override
protected RepeatStatus getNextResult(RepeatContext context, RepeatCallback callback, RepeatInternalState state)
throws Throwable {
ExecutingRunnable runnable;
ResultQueue<ResultHolder> queue = ((ResultQueueInternalState) state).getResultQueue();
do {
/*
* Wrap the callback in a runnable that will add its result to the
* queue when it is ready.
*/
runnable = new ExecutingRunnable(callback, context, queue);
/**
* Tell the runnable that it can expect a result. This could have
* been in-lined with the constructor, but it might block, so it's
* better to do it here, since we have the option (it's a private
* class).
*/
runnable.expect();
/*
* Start the task possibly concurrently / in the future.
*/
taskExecutor.execute(runnable);
/*
* Allow termination policy to update its state. This must happen
* immediately before or after the call to the task executor.
*/
update(context);
/*
* Keep going until we get a result that is finished, or early
* termination...
*/
} while (queue.isEmpty() && !isComplete(context));
/*
* N.B. If the queue is empty then take() blocks until a result appears,
* and there must be at least one because we just submitted one to the
* task executor.
*/
ResultHolder result = queue.take();
if (result.getError() != null) {
throw result.getError();
}
return result.getResult();
}
/**
* Wait for all the results to appear on the queue and execute the after
* interceptors for each one.
*
* @see org.springframework.batch.repeat.support.RepeatTemplate#waitForResults(org.springframework.batch.repeat.support.RepeatInternalState)
*/
@Override
protected boolean waitForResults(RepeatInternalState state) {
ResultQueue<ResultHolder> queue = ((ResultQueueInternalState) state).getResultQueue();
boolean result = true;
while (queue.isExpecting()) {
/*
* Careful that no runnables that are not going to finish ever get
* onto the queue, else this may block forever.
*/
ResultHolder future;
try {
future = queue.take();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RepeatException("InterruptedException while waiting for result.");
}
if (future.getError() != null) {
state.getThrowables().add(future.getError());
result = false;
}
else {
RepeatStatus status = future.getResult();
result = result && canContinue(status);
executeAfterInterceptors(future.getContext(), status);
}
}
Assert.state(queue.isEmpty(), "Future results queue should be empty at end of batch.");
return result;
}
@Override
protected RepeatInternalState createInternalState(RepeatContext context) {
// Queue of pending results:
return new ResultQueueInternalState(throttleLimit);
}
/**
* A runnable that puts its result on a queue when it is done.
*
* @author Dave Syer
*
*/
private class ExecutingRunnable implements Runnable, ResultHolder {
private final RepeatCallback callback;
private final RepeatContext context;
private final ResultQueue<ResultHolder> queue;
private volatile RepeatStatus result;
private volatile Throwable error;
public ExecutingRunnable(RepeatCallback callback, RepeatContext context, ResultQueue<ResultHolder> queue) {
super();
this.callback = callback;
this.context = context;
this.queue = queue;
}
/**
* Tell the queue to expect a result.
*/
public void expect() {
try {
queue.expect();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RepeatException("InterruptedException waiting for to acquire lock on input.");
}
}
/**
* Execute the batch callback, and store the result, or any exception
* that is thrown for retrieval later by caller.
*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
boolean clearContext = false;
try {
if (RepeatSynchronizationManager.getContext() == null) {
clearContext = true;
RepeatSynchronizationManager.register(context);
}
if (logger.isDebugEnabled()) {
logger.debug("Repeat operation about to start at count=" + context.getStartedCount());
}
result = callback.doInIteration(context);
}
catch (Throwable e) {
error = e;
}
finally {
if (clearContext) {
RepeatSynchronizationManager.clear();
}
queue.put(this);
}
}
/**
* Get the result - never blocks because the queue manages waiting for
* the task to finish.
*/
@Override
public RepeatStatus getResult() {
return result;
}
/**
* Get the error - never blocks because the queue manages waiting for
* the task to finish.
*/
@Override
public Throwable getError() {
return error;
}
/**
* Getter for the context.
*/
@Override
public RepeatContext getContext() {
return this.context;
}
}
/**
* @author Dave Syer
*
*/
private static class ResultQueueInternalState extends RepeatInternalStateSupport {
private final ResultQueue<ResultHolder> results;
/**
* @param throttleLimit the throttle limit for the result queue
*/
public ResultQueueInternalState(int throttleLimit) {
super();
this.results = new ResultHolderResultQueue(throttleLimit);
}
/**
* @return the result queue
*/
public ResultQueue<ResultHolder> getResultQueue() {
return results;
}
}
}