BATCH-1466: removed integration module
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
Manifest-Version: 1.0
|
||||
Class-Path:
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package org.springframework.batch.integration.async;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link ItemProcessor} that delegates to a nested processor and in the
|
||||
* background. To allow for background processing the return value from the
|
||||
* processor is a {@link Future} which needs to be unpacked before the item can
|
||||
* be used by a client.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @param <I> the input object type
|
||||
* @param <O> the output object type (will be wrapped in a Future)
|
||||
*/
|
||||
public class AsyncItemProcessor<I, O> implements ItemProcessor<I, Future<O>>, InitializingBean {
|
||||
|
||||
private ItemProcessor<I, O> delegate;
|
||||
|
||||
private TaskExecutor taskExecutor = new SyncTaskExecutor();
|
||||
|
||||
/**
|
||||
* Check mandatory properties (the {@link #setDelegate(ItemProcessor)}).
|
||||
*
|
||||
* @see InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(delegate, "The delegate must be set.");
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link ItemProcessor} to use to delegate processing to in a
|
||||
* background thread.
|
||||
*
|
||||
* @param delegate the {@link ItemProcessor} to use as a delegate
|
||||
*/
|
||||
public void setDelegate(ItemProcessor<I, O> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link TaskExecutor} to use to allow the item processing to proceed
|
||||
* in the background. Defaults to a {@link SyncTaskExecutor} so no threads
|
||||
* are created unless this is overridden.
|
||||
*
|
||||
* @param taskExecutor a {@link TaskExecutor}
|
||||
*/
|
||||
public void setTaskExecutor(TaskExecutor taskExecutor) {
|
||||
this.taskExecutor = taskExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the input by delegating to the provided item processor. The
|
||||
* return value is wrapped in a {@link Future} so that clients can unpack it
|
||||
* later.
|
||||
*
|
||||
* @see ItemProcessor#process(Object)
|
||||
*/
|
||||
public Future<O> process(final I item) throws Exception {
|
||||
FutureTask<O> task = new FutureTask<O>(new Callable<O>() {
|
||||
public O call() throws Exception {
|
||||
return delegate.process(item);
|
||||
}
|
||||
});
|
||||
taskExecutor.execute(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.springframework.batch.integration.async;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class AsyncItemWriter<T> implements ItemWriter<Future<T>>, InitializingBean {
|
||||
|
||||
private ItemWriter<T> delegate;
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(delegate, "A delegate ItemWriter must be provided.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delegate
|
||||
*/
|
||||
public void setDelegate(ItemWriter<T> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public void write(List<? extends Future<T>> items) throws Exception {
|
||||
List<T> list = new ArrayList<T>();
|
||||
for (Future<T> future : items) {
|
||||
list.add(future.get());
|
||||
}
|
||||
delegate.write(list);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 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.integration.chunk;
|
||||
|
||||
import org.springframework.batch.item.ItemWriterException;
|
||||
|
||||
/**
|
||||
* Exception indicating that a failure or early completion condition was
|
||||
* detected in a remote worker.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class AsynchronousFailureException extends ItemWriterException {
|
||||
|
||||
/**
|
||||
* Create a new {@link AsynchronousFailureException} based on a message and
|
||||
* another exception.
|
||||
*
|
||||
* @param message
|
||||
* the message for this exception
|
||||
* @param cause
|
||||
* the other exception
|
||||
*/
|
||||
public AsynchronousFailureException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link AsynchronousFailureException} based on a message.
|
||||
*
|
||||
* @param message
|
||||
* the message for this exception
|
||||
*/
|
||||
public AsynchronousFailureException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* 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.integration.chunk;
|
||||
|
||||
public interface ChunkHandler<T> {
|
||||
|
||||
ChunkResponse handleChunk(ChunkRequest<T> chunk);
|
||||
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* 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.integration.chunk;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.ExitStatus;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.ItemStreamException;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.integration.gateway.MessagingGateway;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class ChunkMessageChannelItemWriter<T> extends StepExecutionListenerSupport implements ItemWriter<T>, ItemStream {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ChunkMessageChannelItemWriter.class);
|
||||
|
||||
static final String ACTUAL = ChunkMessageChannelItemWriter.class.getName() + ".ACTUAL";
|
||||
|
||||
static final String EXPECTED = ChunkMessageChannelItemWriter.class.getName() + ".EXPECTED";
|
||||
|
||||
private static final long DEFAULT_THROTTLE_LIMIT = 6;
|
||||
|
||||
private MessagingGateway messagingGateway;
|
||||
|
||||
private LocalState localState = new LocalState();
|
||||
|
||||
private long throttleLimit = DEFAULT_THROTTLE_LIMIT;
|
||||
|
||||
/**
|
||||
* Public setter for the throttle limit. This limits the number of pending
|
||||
* requests for chunk processing to avoid overwhelming the receivers.
|
||||
* @param throttleLimit the throttle limit to set
|
||||
*/
|
||||
public void setThrottleLimit(long throttleLimit) {
|
||||
this.throttleLimit = throttleLimit;
|
||||
}
|
||||
|
||||
public void setMessagingGateway(MessagingGateway messagingGateway) {
|
||||
this.messagingGateway = messagingGateway;
|
||||
}
|
||||
|
||||
public void write(List<? extends T> items) throws Exception {
|
||||
|
||||
// Block until expecting <= throttle limit
|
||||
while (localState.getExpecting() > throttleLimit) {
|
||||
getNextResult();
|
||||
}
|
||||
|
||||
if (!items.isEmpty()) {
|
||||
|
||||
logger.debug("Dispatching chunk: " + items);
|
||||
ChunkRequest<T> request = new ChunkRequest<T>(items, localState.getJobId(), localState
|
||||
.createStepContribution());
|
||||
messagingGateway.send(request);
|
||||
localState.expected++;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeStep(StepExecution stepExecution) {
|
||||
localState.setStepExecution(stepExecution);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus afterStep(StepExecution stepExecution) {
|
||||
if (!(stepExecution.getStatus() == BatchStatus.COMPLETED)) {
|
||||
return ExitStatus.EXECUTING;
|
||||
}
|
||||
long expecting = localState.getExpecting();
|
||||
boolean timedOut;
|
||||
try {
|
||||
logger.debug("Waiting for results in step listener...");
|
||||
timedOut = !waitForResults();
|
||||
logger.debug("Finished waiting for results in step listener.");
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
logger.debug("Detected failure waiting for results in step listener.", e);
|
||||
stepExecution.setStatus(BatchStatus.FAILED);
|
||||
return ExitStatus.FAILED.addExitDescription(e.getClass().getName() + ": " + e.getMessage());
|
||||
}
|
||||
if (timedOut) {
|
||||
stepExecution.setStatus(BatchStatus.FAILED);
|
||||
throw new ItemStreamException("Timed out waiting for back log at end of step");
|
||||
}
|
||||
return ExitStatus.COMPLETED.addExitDescription("Waited for " + expecting + " results.");
|
||||
}
|
||||
|
||||
public void close() throws ItemStreamException {
|
||||
localState.reset();
|
||||
}
|
||||
|
||||
public void open(ExecutionContext executionContext) throws ItemStreamException {
|
||||
if (executionContext.containsKey(EXPECTED)) {
|
||||
localState.expected = executionContext.getLong(EXPECTED);
|
||||
localState.actual = executionContext.getLong(ACTUAL);
|
||||
if (!waitForResults()) {
|
||||
throw new ItemStreamException("Timed out waiting for back log on open");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void update(ExecutionContext executionContext) throws ItemStreamException {
|
||||
executionContext.putLong(EXPECTED, localState.expected);
|
||||
executionContext.putLong(ACTUAL, localState.actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until all the results that are in the pipeline come back to the
|
||||
* reply channel.
|
||||
*
|
||||
* @return true if successfully received a result, false if timed out
|
||||
*/
|
||||
private boolean waitForResults() {
|
||||
// TODO: cumulative timeout, or throw an exception?
|
||||
int count = 0;
|
||||
int maxCount = 40;
|
||||
while (localState.getExpecting() > 0 && count++ < maxCount) {
|
||||
getNextResult();
|
||||
}
|
||||
return count < maxCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next result if it is available (within the timeout specified in
|
||||
* the gateway), otherwise do nothing.
|
||||
*
|
||||
* @throws AsynchronousFailureException If there is a response and it
|
||||
* contains a failed chunk response.
|
||||
*
|
||||
* @throws IllegalStateException if the result contains the wrong job
|
||||
* instance id (maybe we are sharing a channel and we shouldn't be)
|
||||
*/
|
||||
private void getNextResult() {
|
||||
// TODO: make sure this is transactional (should be if single threaded)
|
||||
ChunkResponse payload = (ChunkResponse) messagingGateway.receive();
|
||||
if (payload != null) {
|
||||
Long jobInstanceId = payload.getJobId();
|
||||
Assert.state(jobInstanceId != null, "Message did not contain job instance id.");
|
||||
Assert.state(jobInstanceId.equals(localState.getJobId()), "Message contained wrong job instance id ["
|
||||
+ jobInstanceId + "] should have been [" + localState.getJobId() + "].");
|
||||
localState.actual++;
|
||||
// TODO: apply the skip count
|
||||
if (!payload.isSuccessful()) {
|
||||
throw new AsynchronousFailureException("Failure or interrupt detected in handler: "
|
||||
+ payload.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class LocalState {
|
||||
private long actual;
|
||||
|
||||
private long expected;
|
||||
|
||||
private StepExecution stepExecution;
|
||||
|
||||
public long getExpecting() {
|
||||
return expected - actual;
|
||||
}
|
||||
|
||||
public StepContribution createStepContribution() {
|
||||
return stepExecution.createStepContribution();
|
||||
}
|
||||
|
||||
public Long getJobId() {
|
||||
return stepExecution.getJobExecution().getJobId();
|
||||
}
|
||||
|
||||
public void setStepExecution(StepExecution stepExecution) {
|
||||
this.stepExecution = stepExecution;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
expected = actual = 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* 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.integration.chunk;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.step.item.Chunk;
|
||||
import org.springframework.batch.core.step.item.ChunkProcessor;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@MessageEndpoint
|
||||
public class ChunkProcessorChunkHandler<S> implements ChunkHandler<S>, InitializingBean {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ChunkProcessorChunkHandler.class);
|
||||
|
||||
private ChunkProcessor<S> chunkProcessor;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(chunkProcessor, "A ChunkProcessor must be provided");
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link ChunkProcessor}.
|
||||
*
|
||||
* @param chunkProcessor the chunkProcessor to set
|
||||
*/
|
||||
public void setChunkProcessor(ChunkProcessor<S> chunkProcessor) {
|
||||
this.chunkProcessor = chunkProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @see ChunkHandler#handleChunk(ChunkRequest)
|
||||
*/
|
||||
@ServiceActivator
|
||||
public ChunkResponse handleChunk(ChunkRequest<S> chunkRequest) {
|
||||
|
||||
logger.debug("Handling chunk: " + chunkRequest);
|
||||
|
||||
StepContribution stepContribution = chunkRequest.getStepContribution();
|
||||
try {
|
||||
chunkProcessor.process(stepContribution, new Chunk<S>(chunkRequest.getItems()));
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.debug("Failed chunk", e);
|
||||
return new ChunkResponse(false, chunkRequest.getJobId(), stepContribution, e.getClass().getName() + ": "
|
||||
+ e.getMessage());
|
||||
}
|
||||
|
||||
logger.debug("Completed chunk handling with " + stepContribution);
|
||||
return new ChunkResponse(true, chunkRequest.getJobId(), stepContribution);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* 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.integration.chunk;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
|
||||
public class ChunkRequest<T> implements Serializable {
|
||||
|
||||
private final Long jobId;
|
||||
private final Collection<? extends T> items;
|
||||
private final StepContribution stepContribution;
|
||||
|
||||
public ChunkRequest(Collection<? extends T> items, Long jobId, StepContribution stepContribution) {
|
||||
this.items = items;
|
||||
this.jobId = jobId;
|
||||
this.stepContribution = stepContribution;
|
||||
}
|
||||
|
||||
public Long getJobId() {
|
||||
return jobId;
|
||||
}
|
||||
|
||||
public Collection<? extends T> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link StepContribution} for this chunk
|
||||
*/
|
||||
public StepContribution getStepContribution() {
|
||||
return stepContribution;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName()+": jobId="+jobId+", contribution="+stepContribution+", item count="+items.size();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* 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.integration.chunk;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
|
||||
public class ChunkResponse implements Serializable {
|
||||
|
||||
private final StepContribution stepContribution;
|
||||
private final Long jobId;
|
||||
private final boolean status;
|
||||
private final String message;
|
||||
|
||||
public ChunkResponse(Long jobId, StepContribution stepContribution) {
|
||||
this(true, jobId, stepContribution, null);
|
||||
}
|
||||
|
||||
public ChunkResponse(boolean status, Long jobId, StepContribution stepContribution) {
|
||||
this(status, jobId, stepContribution, null);
|
||||
}
|
||||
|
||||
public ChunkResponse(boolean status, Long jobId, StepContribution stepContribution, String message) {
|
||||
this.status = status;
|
||||
this.jobId = jobId;
|
||||
this.stepContribution = stepContribution;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public StepContribution getStepContribution() {
|
||||
return stepContribution;
|
||||
}
|
||||
|
||||
public Long getJobId() {
|
||||
return jobId;
|
||||
}
|
||||
|
||||
public boolean isSuccessful() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName()+": jobId="+jobId+", stepContribution="+stepContribution+", successful="+status;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
/*
|
||||
* 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.integration.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.StepExecutionListener;
|
||||
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
|
||||
import org.springframework.batch.core.converter.JobParametersConverter;
|
||||
import org.springframework.batch.core.job.SimpleJob;
|
||||
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.step.item.SimpleStepFactoryBean;
|
||||
import org.springframework.batch.integration.launch.JobLaunchingMessageHandler;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.batch.item.file.FlatFileItemReader;
|
||||
import org.springframework.batch.item.xml.StaxEventItemReader;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.annotation.Required;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.io.FileSystemResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.integration.channel.DirectChannel;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A FactoryBean for a {@link Job} with a single step which just pumps messages
|
||||
* from a file into a channel. The channel has to be a {@link DirectChannel} to
|
||||
* ensure that failures propagate up to the step and fail the job execution.
|
||||
* Normally this job will be used in conjunction with a
|
||||
* {@link JobLaunchingMessageHandler} and a
|
||||
* {@link ResourcePayloadAsJobParameterStrategy}, so that the user can just send
|
||||
* a message to a request channel listing the files to be processed, and
|
||||
* everything else just happens by magic. After a failure the job will be
|
||||
* restarted just by sending it the same message.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class FileToMessagesJobFactoryBean<T> implements FactoryBean, BeanNameAware {
|
||||
|
||||
private String name = "fileToMessageJob";
|
||||
|
||||
private ItemReader<? extends T> itemReader;
|
||||
|
||||
private MessageChannel channel;
|
||||
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
private JobRepository jobRepository;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang
|
||||
* .String)
|
||||
*/
|
||||
public void setBeanName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link ItemReader}. Must be either a
|
||||
* {@link FlatFileItemReader} or a {@link StaxEventItemReader}. In either
|
||||
* case there is no need to set the resource property as it will be set by
|
||||
* this factory.
|
||||
*
|
||||
* @param itemReader the itemReader to set
|
||||
*/
|
||||
@Required
|
||||
public void setItemReader(ItemReader<? extends T> itemReader) {
|
||||
this.itemReader = itemReader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the channel. Each item from the item reader will be
|
||||
* sent to this channel.
|
||||
*
|
||||
* @param channel the channel to set
|
||||
*/
|
||||
@Required
|
||||
public void setChannel(MessageChannel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link JobRepository}.
|
||||
*
|
||||
* @param jobRepository the job repository to set
|
||||
*/
|
||||
@Required
|
||||
public void setJobRepository(JobRepository jobRepository) {
|
||||
this.jobRepository = jobRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link PlatformTransactionManager}.
|
||||
*
|
||||
* @param transactionManager the transaction manager to set
|
||||
*/
|
||||
@Required
|
||||
public void setTransactionManager(PlatformTransactionManager transactionManager) {
|
||||
this.transactionManager = transactionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Job} that can process a flat file or XML file into
|
||||
* messages. To launch the job will require only a {@link JobParameters}
|
||||
* instance with a resource location as a URL.
|
||||
*
|
||||
* @see org.springframework.beans.factory.FactoryBean#getObject()
|
||||
*/
|
||||
public Object getObject() throws Exception {
|
||||
|
||||
SimpleJob job = new SimpleJob();
|
||||
job.setName(name);
|
||||
job.setJobRepository(jobRepository);
|
||||
|
||||
SimpleStepFactoryBean<T, T> stepFactory = new SimpleStepFactoryBean<T, T>();
|
||||
stepFactory.setBeanName("step");
|
||||
|
||||
Assert.state((itemReader instanceof FlatFileItemReader) || (itemReader instanceof StaxEventItemReader),
|
||||
"ItemReader must be either a FlatFileItemReader or a StaxEventItemReader");
|
||||
JobParameterResourceProxy resourceProxy = new JobParameterResourceProxy();
|
||||
resourceProxy.setKey(ResourcePayloadAsJobParameterStrategy.FILE_INPUT_PATH);
|
||||
stepFactory.setListeners(new StepExecutionListener[] { resourceProxy });
|
||||
setResource(itemReader, resourceProxy);
|
||||
stepFactory.setItemReader(itemReader);
|
||||
|
||||
Assert.notNull(channel, "A channel must be provided");
|
||||
Assert.state(channel instanceof DirectChannel,
|
||||
"The channel must be a DirectChannel (otherwise failures can not be recovered from)");
|
||||
MessageChannelItemWriter<? super T> itemWriter = new MessageChannelItemWriter<T>(channel);
|
||||
stepFactory.setItemWriter(itemWriter);
|
||||
|
||||
Assert.notNull(transactionManager, "A transaction manager must be provided");
|
||||
stepFactory.setTransactionManager(transactionManager);
|
||||
|
||||
Assert.notNull(jobRepository, "A job repository must be provided");
|
||||
stepFactory.setJobRepository(jobRepository);
|
||||
|
||||
job.addStep((Step) stepFactory.getObject());
|
||||
return job;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param itemReader
|
||||
* @param resource
|
||||
*/
|
||||
private void setResource(ItemReader<? extends T> itemReader, Resource resource) {
|
||||
if (itemReader instanceof FlatFileItemReader) {
|
||||
((FlatFileItemReader<? extends T>) itemReader).setResource(resource);
|
||||
}
|
||||
else {
|
||||
((StaxEventItemReader<? extends T>) itemReader).setResource(resource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns {@link Job}.
|
||||
*
|
||||
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
|
||||
*/
|
||||
public Class<Job> getObjectType() {
|
||||
return Job.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always true. TODO: should it be false?
|
||||
*
|
||||
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
|
||||
*/
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strategy for resolving a filename just prior to step execution. The proxy
|
||||
* is given a key that will correspond to a key in the job parameters. Just
|
||||
* before the step is executed, the resource will be created with its
|
||||
* filename as the value found in the job parameters.
|
||||
*
|
||||
* To use this resource it must be initialised with a {@link StepExecution}.
|
||||
* The best way to do that is to register it as a listener in the step that
|
||||
* is going to need it. For this reason the resource implements
|
||||
* {@link StepExecutionListener}.
|
||||
*
|
||||
* @see Resource
|
||||
*/
|
||||
private class JobParameterResourceProxy extends StepExecutionListenerSupport implements Resource,
|
||||
ResourceLoaderAware, StepExecutionListener {
|
||||
|
||||
private JobParametersConverter jobParametersConverter = new DefaultJobParametersConverter();
|
||||
|
||||
private ResourceLoader resourceLoader = new FileSystemResourceLoader();
|
||||
|
||||
private Resource delegate;
|
||||
|
||||
private String key = null;
|
||||
|
||||
private static final String NOT_INITIALISED = "The delegate resource has not been initialised. "
|
||||
+ "Remember to register this object as a StepListener.";
|
||||
|
||||
/**
|
||||
* @param relativePath
|
||||
* @throws IOException
|
||||
* @see org.springframework.core.io.Resource#createRelative(java.lang.String)
|
||||
*/
|
||||
public Resource createRelative(String relativePath) throws IOException {
|
||||
Assert.state(delegate != null, NOT_INITIALISED);
|
||||
return delegate.createRelative(relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.core.io.Resource#exists()
|
||||
*/
|
||||
public boolean exists() {
|
||||
Assert.state(delegate != null, NOT_INITIALISED);
|
||||
return delegate.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.core.io.Resource#getDescription()
|
||||
*/
|
||||
public String getDescription() {
|
||||
Assert.state(delegate != null, NOT_INITIALISED);
|
||||
return delegate.getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
* @see org.springframework.core.io.Resource#getFile()
|
||||
*/
|
||||
public File getFile() throws IOException {
|
||||
Assert.state(delegate != null, NOT_INITIALISED);
|
||||
return delegate.getFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.core.io.Resource#getFilename()
|
||||
*/
|
||||
public String getFilename() {
|
||||
Assert.state(delegate != null, NOT_INITIALISED);
|
||||
return delegate.getFilename();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
* @see org.springframework.core.io.InputStreamSource#getInputStream()
|
||||
*/
|
||||
public InputStream getInputStream() throws IOException {
|
||||
Assert.state(delegate != null, NOT_INITIALISED);
|
||||
return delegate.getInputStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
* @see org.springframework.core.io.Resource#getURI()
|
||||
*/
|
||||
public URI getURI() throws IOException {
|
||||
Assert.state(delegate != null, NOT_INITIALISED);
|
||||
return delegate.getURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
* @see org.springframework.core.io.Resource#getURL()
|
||||
*/
|
||||
public URL getURL() throws IOException {
|
||||
Assert.state(delegate != null, NOT_INITIALISED);
|
||||
return delegate.getURL();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.core.io.Resource#isOpen()
|
||||
*/
|
||||
public boolean isOpen() {
|
||||
Assert.state(delegate != null, NOT_INITIALISED);
|
||||
return delegate.isOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.core.io.Resource#isReadable()
|
||||
*/
|
||||
public boolean isReadable() {
|
||||
Assert.state(delegate != null, NOT_INITIALISED);
|
||||
return delegate.isReadable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.core.io.Resource#lastModified()
|
||||
*/
|
||||
public long lastModified() throws IOException {
|
||||
Assert.state(delegate != null, NOT_INITIALISED);
|
||||
return delegate.lastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link JobParametersConverter} used to
|
||||
* translate {@link JobParameters} into {@link Properties}. Defaults to
|
||||
* a {@link DefaultJobParametersConverter}.
|
||||
*
|
||||
* @param jobParametersConverter the {@link JobParametersConverter} to
|
||||
* set
|
||||
*/
|
||||
public void setJobParametersFactory(JobParametersConverter jobParametersConverter) {
|
||||
this.jobParametersConverter = jobParametersConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always false because we are expecting to be step scoped.
|
||||
*
|
||||
* @see org.springframework.beans.factory.config.AbstractFactoryBean#isSingleton()
|
||||
*/
|
||||
public boolean isSingleton() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.springframework.context.ResourceLoaderAware#setResourceLoader(org.springframework.core.io.ResourceLoader)
|
||||
*/
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the properties of the enclosing {@link StepExecution} that
|
||||
* will be needed to create a file name.
|
||||
*
|
||||
* @see org.springframework.batch.core.StepExecutionListener#beforeStep(org.springframework.batch.core.StepExecution)
|
||||
*/
|
||||
public void beforeStep(StepExecution execution) {
|
||||
Properties properties = jobParametersConverter.getProperties(execution.getJobExecution().getJobInstance()
|
||||
.getJobParameters());
|
||||
delegate = resourceLoader.getResource(properties.getProperty(this.key));
|
||||
}
|
||||
}
|
||||
|
||||
private static class MessageChannelItemWriter<T> implements ItemWriter<T> {
|
||||
|
||||
private MessageChannel channel;
|
||||
|
||||
public MessageChannelItemWriter(MessageChannel channel) {
|
||||
super();
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public void write(List<? extends T> items) throws Exception {
|
||||
for (T item : items) {
|
||||
channel.send(new GenericMessage<T>(item));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* 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.integration.file;
|
||||
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.integration.core.Message;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Jonas Partner
|
||||
*
|
||||
*/
|
||||
public interface MessageToJobParametersStrategy {
|
||||
|
||||
public JobParameters getJobParameters(Message<?> message);
|
||||
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* 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.integration.file;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.item.ItemStreamException;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.core.Message;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ResourcePayloadAsJobParameterStrategy implements MessageToJobParametersStrategy {
|
||||
|
||||
/**
|
||||
* The key name for the job parameter that will be a URL for the input file
|
||||
*/
|
||||
public static final String FILE_INPUT_PATH = "input.file.path";
|
||||
|
||||
/**
|
||||
* Convert a message payload which is a {@link Resource} to its URL
|
||||
* representation and load that into a job parameter.
|
||||
*
|
||||
* @see MessageToJobParametersStrategy#getJobParameters(Message)
|
||||
*/
|
||||
public JobParameters getJobParameters(Message<?> message) {
|
||||
JobParametersBuilder builder = new JobParametersBuilder();
|
||||
Resource resource = (Resource) message.getPayload();
|
||||
try {
|
||||
builder.addString(FILE_INPUT_PATH, resource.getURL().toExternalForm());
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new ItemStreamException("Could not create URL for resource: [" + resource + "]", e);
|
||||
}
|
||||
return builder.toJobParameters();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* 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.integration.job;
|
||||
|
||||
/**
|
||||
* @author dsyer
|
||||
*
|
||||
*/
|
||||
public class JobExecutionReply {
|
||||
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* 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.integration.job;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
|
||||
/**
|
||||
* Encapsulation of a request to execute a job execution through a message flow
|
||||
* consisting of step handlers. A handler should pass the message on as it is,
|
||||
* modifying the request properties as necessary. Generally a handler will
|
||||
* execute a step as part of the {@link JobExecution} passed in, and should
|
||||
* change the status to {@link BatchStatus#COMPLETED} if the step is successful
|
||||
* (generally a handler cannot determine if the whole job execution is complete,
|
||||
* so this is just information about the step).<br/>
|
||||
*
|
||||
* If the incoming status is {@link BatchStatus#FAILED},
|
||||
* {@link BatchStatus#ABANDONED} or {@link BatchStatus#STOPPING} the request
|
||||
* should be ignored by handlers (passed on without modification).
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class JobExecutionRequest {
|
||||
|
||||
private JobExecution jobExecution;
|
||||
|
||||
private BatchStatus status;
|
||||
|
||||
private Throwable throwable;
|
||||
|
||||
/**
|
||||
* @param jobExecution
|
||||
*/
|
||||
public JobExecutionRequest(JobExecution jobExecution) {
|
||||
this.jobExecution = jobExecution;
|
||||
status = jobExecution.getStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current job execution id
|
||||
*/
|
||||
public Long getJobId() {
|
||||
return this.jobExecution.getJobId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current {@link BatchStatus}
|
||||
*/
|
||||
public BatchStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the status.
|
||||
* @param status the status to set
|
||||
*/
|
||||
public void setStatus(BatchStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if there are errors
|
||||
*/
|
||||
public boolean hasErrors() {
|
||||
return throwable != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public getter for the throwable.
|
||||
* @return the throwable
|
||||
*/
|
||||
public Throwable getLastThrowable() {
|
||||
return throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the throwable.
|
||||
* @param throwable the throwable to set
|
||||
*/
|
||||
public void registerThrowable(Throwable throwable) {
|
||||
this.throwable = throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public getter for the jobExecution.
|
||||
* @return the jobExecution
|
||||
*/
|
||||
public JobExecution getJobExecution() {
|
||||
return jobExecution;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName()+": "+jobExecution;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
/*
|
||||
* 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.integration.job;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.ExitStatus;
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.UnexpectedJobExecutionException;
|
||||
import org.springframework.batch.core.step.AbstractStep;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
import org.springframework.beans.factory.annotation.Required;
|
||||
import org.springframework.integration.channel.PollableChannel;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.message.GenericMessage;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class MessageOrientedStep extends AbstractStep {
|
||||
|
||||
/**
|
||||
* Key in execution context for flag to say we are waiting.
|
||||
*/
|
||||
public static final String WAITING = MessageOrientedStep.class.getName() + ".WAITING";
|
||||
|
||||
private MessageChannel outputChannel;
|
||||
|
||||
private PollableChannel source;
|
||||
|
||||
private static long MINUTE = 1000 * 60;
|
||||
|
||||
private long executionTimeout = 30*MINUTE ;
|
||||
|
||||
private long pollingInterval = 5;
|
||||
|
||||
/**
|
||||
* Public setter for the execution timeout in minutes. Defaults to 30.
|
||||
* @param executionTimeoutMinutes the timeout to set
|
||||
*/
|
||||
public void setExecutionTimeoutMinutes(int executionTimeoutMinutes) {
|
||||
this.executionTimeout = executionTimeoutMinutes * MINUTE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the execution timeout in milliseconds. Defaults to 30 minutes.
|
||||
* @param executionTimeout
|
||||
*/
|
||||
public void setExecutionTimeout(long executionTimeout) {
|
||||
this.executionTimeout = executionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the polling interval in milliseconds while waiting for
|
||||
* replies signalling the end of the step. Defaults to 5.
|
||||
* @param pollingInterval the polling interval to set
|
||||
*/
|
||||
public void setPollingInterval(long pollingInterval) {
|
||||
this.pollingInterval = pollingInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the target.
|
||||
* @param outputChannel the target to set
|
||||
*/
|
||||
@Required
|
||||
public void setOutputChannel(MessageChannel outputChannel) {
|
||||
this.outputChannel = outputChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the source.
|
||||
* @param source the source to set
|
||||
*/
|
||||
@Required
|
||||
public void setInputChannel(PollableChannel source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AbstractStep#execute(StepExecution)
|
||||
*/
|
||||
@Override
|
||||
protected void doExecute(StepExecution stepExecution) throws JobInterruptedException,
|
||||
UnexpectedJobExecutionException {
|
||||
|
||||
JobExecutionRequest request = new JobExecutionRequest(stepExecution.getJobExecution());
|
||||
|
||||
ExecutionContext executionContext = stepExecution.getExecutionContext();
|
||||
|
||||
if (executionContext.containsKey(WAITING)) {
|
||||
// restart scenario: we are still waiting for a response
|
||||
waitForReply(request.getJobId());
|
||||
}
|
||||
else {
|
||||
executionContext.putString(WAITING, "true");
|
||||
// TODO: need these two lines to be atomic
|
||||
getJobRepository().update(stepExecution);
|
||||
outputChannel.send(new GenericMessage<JobExecutionRequest>(request));
|
||||
waitForReply(request.getJobId());
|
||||
}
|
||||
|
||||
stepExecution.setExitStatus(ExitStatus.COMPLETED);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expectedJobId
|
||||
*/
|
||||
private void waitForReply(Long expectedJobId) {
|
||||
long timeout = pollingInterval;
|
||||
long maxCount = executionTimeout / timeout;
|
||||
long count = 0;
|
||||
|
||||
while (count++ < maxCount) {
|
||||
|
||||
// TODO: timeout?
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<JobExecutionRequest> message = (Message<JobExecutionRequest>) source.receive(timeout);
|
||||
|
||||
if (message != null) {
|
||||
|
||||
JobExecutionRequest payload = message.getPayload();
|
||||
Long jobInstanceId = payload.getJobId();
|
||||
Assert.state(jobInstanceId != null, "Message did not contain job instance id.");
|
||||
Assert.state(jobInstanceId.equals(expectedJobId), "Message contained wrong job instance id ["
|
||||
+ jobInstanceId + "] should have been [" + expectedJobId + "].");
|
||||
|
||||
if (payload.getStatus() == BatchStatus.COMPLETED) {
|
||||
// One of the steps decided we were finished
|
||||
// TODO: wait for all the other steps that might be
|
||||
// executing concurrently?
|
||||
// TODO: maybe *any* reply on this channel should
|
||||
// mean the end of the step?
|
||||
break;
|
||||
}
|
||||
|
||||
if (payload.hasErrors()) {
|
||||
rethrow(payload.getLastThrowable());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (count >= maxCount) {
|
||||
throw new StepExecutionTimeoutException("Timed out waiting for steps to execute.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lastThrowable
|
||||
*/
|
||||
private static void rethrow(Throwable t) throws RuntimeException {
|
||||
if (t instanceof RuntimeException) {
|
||||
throw (RuntimeException) t;
|
||||
}
|
||||
if (t instanceof Exception) {
|
||||
throw new UnexpectedJobExecutionException("Unexpected checked exception thrown by step.", t);
|
||||
}
|
||||
throw (Error) t;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* 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.integration.job;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobExecutionException;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.StartLimitExceededException;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
import org.springframework.beans.factory.annotation.Required;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@MessageEndpoint
|
||||
public class StepExecutionMessageHandler {
|
||||
|
||||
private Step step;
|
||||
|
||||
private JobRepository jobRepository;
|
||||
|
||||
/**
|
||||
* Public setter for the {@link Step}.
|
||||
* @param step the step to set
|
||||
*/
|
||||
@Required
|
||||
public void setStep(Step step) {
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link JobRepository} that is needed to manage the
|
||||
* state of the batch meta domain (jobs, steps, executions) during the life
|
||||
* of a job.
|
||||
*
|
||||
* @param jobRepository
|
||||
*/
|
||||
@Required
|
||||
public void setJobRepository(JobRepository jobRepository) {
|
||||
this.jobRepository = jobRepository;
|
||||
}
|
||||
|
||||
@ServiceActivator
|
||||
public JobExecutionRequest handle(JobExecutionRequest request) {
|
||||
|
||||
// Hand off immediately if the job has already failed
|
||||
if (isComplete(request)) {
|
||||
return request;
|
||||
}
|
||||
|
||||
JobExecution jobExecution = request.getJobExecution();
|
||||
JobInstance jobInstance = jobExecution.getJobInstance();
|
||||
|
||||
StepExecution stepExecution = jobExecution.createStepExecution(step.getName());
|
||||
try {
|
||||
|
||||
StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, step.getName());
|
||||
|
||||
// Even if it completed successfully we want to pass on the output
|
||||
// attributes, so set up the execution context here if it is
|
||||
// available.
|
||||
if (lastStepExecution != null) {
|
||||
stepExecution.setExecutionContext(lastStepExecution.getExecutionContext());
|
||||
}
|
||||
|
||||
// If it is already complete and not restartable it will simply be
|
||||
// skipped
|
||||
if (shouldStart(lastStepExecution, step)) {
|
||||
|
||||
if (!isRestart(jobInstance, lastStepExecution)) {
|
||||
stepExecution.setExecutionContext(new ExecutionContext());
|
||||
}
|
||||
jobRepository.add(stepExecution);
|
||||
step.execute(stepExecution);
|
||||
|
||||
}
|
||||
else if (lastStepExecution != null) {
|
||||
|
||||
/*
|
||||
* We only set these if the step is not going to execute. They
|
||||
* might be needed by the next step to receive the request, but
|
||||
* they won't be persisted because the step is not executed.
|
||||
*/
|
||||
stepExecution.setStatus(lastStepExecution.getStatus());
|
||||
stepExecution.setExitStatus(lastStepExecution.getExitStatus());
|
||||
|
||||
}
|
||||
|
||||
// (the job might actually not be complete, but the stage is).
|
||||
request.setStatus(BatchStatus.COMPLETED);
|
||||
|
||||
}
|
||||
catch (Exception e) {
|
||||
handleFailure(request, e);
|
||||
}
|
||||
catch (Error e) {
|
||||
handleFailure(request, e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return request;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param jobInstance
|
||||
* @param lastStepExecution
|
||||
* @return
|
||||
*/
|
||||
private boolean isRestart(JobInstance jobInstance, StepExecution lastStepExecution) {
|
||||
return (lastStepExecution != null && !lastStepExecution.getStatus().equals(BatchStatus.COMPLETED));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param request
|
||||
* @return
|
||||
*/
|
||||
private boolean isComplete(JobExecutionRequest request) {
|
||||
return request.getStatus() == BatchStatus.FAILED || request.getStatus() == BatchStatus.ABANDONED
|
||||
|| request.getStatus() == BatchStatus.STOPPING;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param request
|
||||
* @param e
|
||||
*/
|
||||
private void handleFailure(JobExecutionRequest request, Throwable e) {
|
||||
request.registerThrowable(e);
|
||||
request.setStatus(BatchStatus.FAILED);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: merge this with SimpleJob implementation.
|
||||
*
|
||||
* Given a step and configuration, return true if the step should start,
|
||||
* false if it should not, and throw an exception if the job should finish.
|
||||
*/
|
||||
private boolean shouldStart(StepExecution lastStepExecution, Step step) throws JobExecutionException {
|
||||
|
||||
BatchStatus stepStatus;
|
||||
// if the last execution is null, the step has never been executed.
|
||||
if (lastStepExecution == null) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
stepStatus = lastStepExecution.getStatus();
|
||||
}
|
||||
|
||||
if (stepStatus == BatchStatus.UNKNOWN) {
|
||||
throw new JobExecutionException("Cannot restart step from UNKNOWN status. "
|
||||
+ "The last execution may have ended with a failure that could not be rolled back, "
|
||||
+ "so it may be dangerous to proceed. " + "Manual intervention is probably necessary.");
|
||||
}
|
||||
|
||||
if (stepStatus == BatchStatus.COMPLETED && step.isAllowStartIfComplete() == false) {
|
||||
// step is complete, false should be returned, indicating that the
|
||||
// step should not be started
|
||||
return false;
|
||||
}
|
||||
|
||||
if (jobRepository.getStepExecutionCount(lastStepExecution.getJobExecution().getJobInstance(), step.getName()) < step
|
||||
.getStartLimit()) {
|
||||
// step start count is less than start max, return true
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// start max has been exceeded, throw an exception.
|
||||
throw new StartLimitExceededException("Maximum start limit exceeded for step: " + step.getName()
|
||||
+ "StartMax: " + step.getStartLimit());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* 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.integration.job;
|
||||
|
||||
import org.springframework.batch.core.UnexpectedJobExecutionException;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class StepExecutionTimeoutException extends UnexpectedJobExecutionException {
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param msg the exception message.
|
||||
*
|
||||
*/
|
||||
public StepExecutionTimeoutException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* 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.integration.launch;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
|
||||
/**
|
||||
* Encapsulation of a {@link Job} and its {@link JobParameters} forming a
|
||||
* request for a job to be launched.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class JobLaunchRequest {
|
||||
|
||||
private final Job job;
|
||||
private final JobParameters jobParameters;
|
||||
|
||||
/**
|
||||
* @param job
|
||||
* @param jobParameters
|
||||
*/
|
||||
public JobLaunchRequest(Job job, JobParameters jobParameters) {
|
||||
super();
|
||||
this.job = job;
|
||||
this.jobParameters = jobParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link Job} to be executed
|
||||
*/
|
||||
public Job getJob() {
|
||||
return this.job;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link JobParameters} for this request
|
||||
*/
|
||||
public JobParameters getJobParameters() {
|
||||
return this.jobParameters;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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.integration.launch;
|
||||
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobExecutionException;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.UnexpectedJobExecutionException;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
|
||||
/**
|
||||
* Message handler which uses strategies to convert a Message into a job and a
|
||||
* set of job parameters
|
||||
* @author Jonas Partner
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class JobLaunchingMessageHandler {
|
||||
|
||||
private final JobLauncher jobLauncher;
|
||||
|
||||
/**
|
||||
* @param jobLauncher
|
||||
*/
|
||||
public JobLaunchingMessageHandler(JobLauncher jobLauncher) {
|
||||
super();
|
||||
this.jobLauncher = jobLauncher;
|
||||
}
|
||||
|
||||
@ServiceActivator
|
||||
public JobExecution launch(JobLaunchRequest request) {
|
||||
Job job = request.getJob();
|
||||
JobParameters jobParameters = request.getJobParameters();
|
||||
|
||||
try {
|
||||
JobExecution execution = jobLauncher.run(job, jobParameters);
|
||||
return execution;
|
||||
}
|
||||
catch (JobExecutionException e) {
|
||||
throw new UnexpectedJobExecutionException("Exception executing job: ["+request+"]", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package org.springframework.batch.integration.partition;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.step.StepLocator;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link StepLocator} implementation that just looks in its enclosing bean
|
||||
* factory for components of type {@link Step}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class BeanFactoryStepLocator implements StepLocator, BeanFactoryAware {
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a bean with the provided name of type {@link Step}.
|
||||
* @see StepLocator#getStep(String)
|
||||
*/
|
||||
public Step getStep(String stepName) {
|
||||
return (Step) beanFactory.getBean(stepName, Step.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look in the bean factory for all beans of type {@link Step}.
|
||||
* @throws IllegalStateException if the {@link BeanFactory} is not listable
|
||||
* @see StepLocator#getStepNames()
|
||||
*/
|
||||
public Collection<String> getStepNames() {
|
||||
Assert.state(beanFactory instanceof ListableBeanFactory, "BeanFactory is not listable.");
|
||||
return Arrays.asList(((ListableBeanFactory) beanFactory).getBeanNamesForType(Step.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package org.springframework.batch.integration.partition;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.partition.PartitionHandler;
|
||||
import org.springframework.batch.core.partition.StepExecutionSplitter;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.core.Message;
|
||||
import org.springframework.integration.core.MessageChannel;
|
||||
import org.springframework.integration.gateway.MessagingGateway;
|
||||
import org.springframework.integration.message.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link PartitionHandler} that uses {@link MessageChannel} instances to send
|
||||
* instructions to remote workers and receive their responses. The
|
||||
* {@link MessageChannel} provides a nice abstraction so that the location of
|
||||
* the workers and the transport used to communicate with them can be changed at
|
||||
* run time. The communication with the remote workers does not need to be
|
||||
* transactional or have guaranteed delivery, so a local thread pool based
|
||||
* implementation works as well as a remote web service or JMS implementation.
|
||||
* If a remote worker fails or doesn't send a reply message, the job will fail
|
||||
* and can be restarted to pick up missing messages and processing. The remote
|
||||
* workers need access to the Spring Batch {@link JobRepository} so that the
|
||||
* shared state across those restarts can be managed centrally.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@MessageEndpoint
|
||||
public class MessageChannelPartitionHandler implements PartitionHandler {
|
||||
|
||||
private int gridSize = 1;
|
||||
|
||||
private MessagingGateway messagingGateway;
|
||||
|
||||
private String stepName;
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(stepName, "A step name must be provided for the remote workers.");
|
||||
Assert.state(messagingGateway != null, "The MessagingGateway must be set");
|
||||
}
|
||||
|
||||
/**
|
||||
* A pre-configured gateway for sending and receiving messages to the remote
|
||||
* workers. Using this property allows a large degree of control over the
|
||||
* timeouts and other properties of the send. It should have channels set up
|
||||
* internally:
|
||||
* <ul>
|
||||
* <li>request channel capable of accepting {@link StepExecutionRequest}
|
||||
* payloads</li>
|
||||
* <li>reply channel that returns a list of {@link StepExecution} results</li>
|
||||
* </ul>
|
||||
* The timeout for the repoy should be set sufficiently long that the remote
|
||||
* steps have time to complete.
|
||||
*
|
||||
* @param messagingGateway the {@link MessagingGateway} to set
|
||||
*/
|
||||
public void setMessagingGateway(MessagingGateway messagingGateway) {
|
||||
this.messagingGateway = messagingGateway;
|
||||
}
|
||||
|
||||
/**
|
||||
* Passed to the {@link StepExecutionSplitter} in the
|
||||
* {@link #handle(StepExecutionSplitter, StepExecution)} method, instructing
|
||||
* it how many {@link StepExecution} instances are required, ideally. The
|
||||
* {@link StepExecutionSplitter} is allowed to ignore the grid size in the
|
||||
* case of a restart, since the input data partitions must be preserved.
|
||||
*
|
||||
* @param gridSize the number of step executions that will be created
|
||||
*/
|
||||
public void setGridSize(int gridSize) {
|
||||
this.gridSize = gridSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the {@link Step} that will be used to execute the partitioned
|
||||
* {@link StepExecution}. This is a regular Spring Batch step, with all the
|
||||
* business logic required to complete an execution based on the input
|
||||
* parameters in its {@link StepExecution} context. The name will be
|
||||
* translated into a {@link Step} instance by the remote worker.
|
||||
*
|
||||
* @param stepName the name of the {@link Step} instance to execute business
|
||||
* logic
|
||||
*/
|
||||
public void setStepName(String stepName) {
|
||||
this.stepName = stepName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param messages the messages to be aggregated
|
||||
* @return the list as it was passed in
|
||||
*/
|
||||
@Aggregator(sendPartialResultsOnTimeout = true)
|
||||
public List<?> aggregate(List<?> messages) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends {@link StepExecutionRequest} objects to the request channel of the
|
||||
* {@link MessagingGateway}, and then receives the result back as a list of
|
||||
* {@link StepExecution} on a reply channel. Use the
|
||||
* {@link #aggregate(List)} method as an aggregator of the individual remote
|
||||
* replies. The receive timeout needs to be set realistically in the
|
||||
* {@link MessagingGateway} <b>and</b> the aggregator, so that there is a
|
||||
* good chance of all work being done.
|
||||
*
|
||||
* @see PartitionHandler#handle(StepExecutionSplitter, StepExecution)
|
||||
*/
|
||||
public Collection<StepExecution> handle(StepExecutionSplitter stepExecutionSplitter,
|
||||
StepExecution masterStepExecution) throws Exception {
|
||||
|
||||
Set<StepExecution> split = stepExecutionSplitter.split(masterStepExecution, gridSize);
|
||||
int count = 0;
|
||||
for (StepExecution stepExecution : split) {
|
||||
messagingGateway.send(createMessage(count++, split.size(), new StepExecutionRequest(stepName, stepExecution
|
||||
.getJobExecutionId(), stepExecution.getId())));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<StepExecution> result = (Collection<StepExecution>) messagingGateway.receive();
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private Message<StepExecutionRequest> createMessage(int sequenceNumber, int sequenceSize,
|
||||
StepExecutionRequest stepExecutionRequest) {
|
||||
return MessageBuilder.withPayload(stepExecutionRequest).setSequenceNumber(sequenceNumber).setSequenceSize(
|
||||
sequenceSize).setCorrelationId(
|
||||
stepExecutionRequest.getJobExecutionId() + ":" + stepExecutionRequest.getStepName()).build();
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package org.springframework.batch.integration.partition;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class StepExecutionRequest implements Serializable {
|
||||
|
||||
private final Long stepExecutionId;
|
||||
|
||||
private final String stepName;
|
||||
|
||||
private final Long jobExecutionId;
|
||||
|
||||
public StepExecutionRequest(String stepName, Long jobExecutionId, Long stepExecutionId) {
|
||||
this.stepName = stepName;
|
||||
this.jobExecutionId = jobExecutionId;
|
||||
this.stepExecutionId = stepExecutionId;
|
||||
}
|
||||
|
||||
public Long getJobExecutionId() {
|
||||
return jobExecutionId;
|
||||
}
|
||||
|
||||
public Long getStepExecutionId() {
|
||||
return stepExecutionId;
|
||||
}
|
||||
|
||||
public String getStepName() {
|
||||
return stepName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("StepExecutionRequest: [jobExecutionId=%d, stepExecutionId=%d, stepName=%s]",
|
||||
jobExecutionId, stepExecutionId, stepName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package org.springframework.batch.integration.partition;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.step.StepLocator;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
|
||||
/**
|
||||
* A {@link MessageEndpoint} that can handle a {@link StepExecutionRequest} and
|
||||
* return a {@link StepExecution} as the result. Typically these need to be
|
||||
* aggregated into a response to a partition handler.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@MessageEndpoint
|
||||
public class StepExecutionRequestHandler {
|
||||
|
||||
private JobExplorer jobExplorer;
|
||||
|
||||
private StepLocator stepLocator;
|
||||
|
||||
/**
|
||||
* Used to locate a {@link Step} to execute for each request.
|
||||
* @param stepLocator a {@link StepLocator}
|
||||
*/
|
||||
public void setStepLocator(StepLocator stepLocator) {
|
||||
this.stepLocator = stepLocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* An explorer that should be used to check for {@link StepExecution}
|
||||
* completion.
|
||||
*
|
||||
* @param jobExplorer a {@link JobExplorer} that is linked to the shared
|
||||
* repository used by all remote workers.
|
||||
*/
|
||||
public void setJobExplorer(JobExplorer jobExplorer) {
|
||||
this.jobExplorer = jobExplorer;
|
||||
}
|
||||
|
||||
@ServiceActivator
|
||||
public StepExecution handle(StepExecutionRequest request) {
|
||||
|
||||
Long jobExecutionId = request.getJobExecutionId();
|
||||
Long stepExecutionId = request.getStepExecutionId();
|
||||
StepExecution stepExecution = jobExplorer.getStepExecution(jobExecutionId, stepExecutionId);
|
||||
if (stepExecution == null) {
|
||||
// TODO: create new Exception
|
||||
throw new IllegalStateException("No StepExecution could be located for this request: " + request);
|
||||
}
|
||||
|
||||
String stepName = request.getStepName();
|
||||
Step step = stepLocator.getStep(stepName);
|
||||
if (step == null) {
|
||||
// TODO: create new Exception
|
||||
throw new IllegalStateException(String.format("No Step with name [%s] could be located.", stepName));
|
||||
}
|
||||
|
||||
try {
|
||||
step.execute(stepExecution);
|
||||
}
|
||||
catch (JobInterruptedException e) {
|
||||
stepExecution.setStatus(BatchStatus.STOPPED);
|
||||
// TODO: maybe update stepExecution in repository
|
||||
}
|
||||
catch (Throwable e) {
|
||||
stepExecution.addFailureException(e);
|
||||
stepExecution.setStatus(BatchStatus.FAILED);
|
||||
// TODO: maybe update stepExecution in repository
|
||||
}
|
||||
|
||||
return stepExecution;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user