Apply spring-javaformat style for consistency with other projects
Resolves #4118
This commit is contained in:
@@ -1,129 +1,123 @@
|
||||
/*
|
||||
* Copyright 2006-2019 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
|
||||
*
|
||||
* https://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.async;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.scope.context.StepContext;
|
||||
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.lang.Nullable;
|
||||
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.
|
||||
*
|
||||
* Because the {@link Future} is typically unwrapped in the {@link ItemWriter},
|
||||
* there are lifecycle and stats limitations (since the framework doesn't know
|
||||
* what the result of the processor is). While not an exhaustive list, things like
|
||||
* {@link StepExecution#filterCount} will not reflect the number of filtered items
|
||||
* and {@link org.springframework.batch.core.ItemProcessListener#onProcessError(Object, Exception)}
|
||||
* will not be called.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @param <I> the input object type
|
||||
* @param <O> the output object type (will be wrapped in a Future)
|
||||
* @see AsyncItemWriter
|
||||
*/
|
||||
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)
|
||||
*/
|
||||
@Nullable
|
||||
public Future<O> process(final I item) throws Exception {
|
||||
final StepExecution stepExecution = getStepExecution();
|
||||
FutureTask<O> task = new FutureTask<>(new Callable<O>() {
|
||||
public O call() throws Exception {
|
||||
if (stepExecution != null) {
|
||||
StepSynchronizationManager.register(stepExecution);
|
||||
}
|
||||
try {
|
||||
return delegate.process(item);
|
||||
}
|
||||
finally {
|
||||
if (stepExecution != null) {
|
||||
StepSynchronizationManager.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
taskExecutor.execute(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current step execution if there is one
|
||||
*/
|
||||
private StepExecution getStepExecution() {
|
||||
StepContext context = StepSynchronizationManager.getContext();
|
||||
if (context==null) {
|
||||
return null;
|
||||
}
|
||||
StepExecution stepExecution = context.getStepExecution();
|
||||
return stepExecution;
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2006-2019 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
|
||||
*
|
||||
* https://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.async;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.scope.context.StepContext;
|
||||
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.task.SyncTaskExecutor;
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.lang.Nullable;
|
||||
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.
|
||||
*
|
||||
* Because the {@link Future} is typically unwrapped in the {@link ItemWriter}, there are
|
||||
* lifecycle and stats limitations (since the framework doesn't know what the result of
|
||||
* the processor is). While not an exhaustive list, things like
|
||||
* {@link StepExecution#filterCount} will not reflect the number of filtered items and
|
||||
* {@link org.springframework.batch.core.ItemProcessListener#onProcessError(Object, Exception)}
|
||||
* will not be called.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @param <I> the input object type
|
||||
* @param <O> the output object type (will be wrapped in a Future)
|
||||
* @see AsyncItemWriter
|
||||
*/
|
||||
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)
|
||||
*/
|
||||
@Nullable
|
||||
public Future<O> process(final I item) throws Exception {
|
||||
final StepExecution stepExecution = getStepExecution();
|
||||
FutureTask<O> task = new FutureTask<>(new Callable<O>() {
|
||||
public O call() throws Exception {
|
||||
if (stepExecution != null) {
|
||||
StepSynchronizationManager.register(stepExecution);
|
||||
}
|
||||
try {
|
||||
return delegate.process(item);
|
||||
}
|
||||
finally {
|
||||
if (stepExecution != null) {
|
||||
StepSynchronizationManager.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
taskExecutor.execute(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current step execution if there is one
|
||||
*/
|
||||
private StepExecution getStepExecution() {
|
||||
StepContext context = StepSynchronizationManager.getContext();
|
||||
if (context == null) {
|
||||
return null;
|
||||
}
|
||||
StepExecution stepExecution = context.getStepExecution();
|
||||
return stepExecution;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,108 +1,110 @@
|
||||
/*
|
||||
* Copyright 2006-2018 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
|
||||
*
|
||||
* https://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.async;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.ItemStreamException;
|
||||
import org.springframework.batch.item.ItemStreamWriter;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class AsyncItemWriter<T> implements ItemStreamWriter<Future<T>>, InitializingBean {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AsyncItemWriter.class);
|
||||
|
||||
private ItemWriter<T> delegate;
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(delegate, "A delegate ItemWriter must be provided.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delegate ItemWriter that does the actual writing of the Future results
|
||||
*/
|
||||
public void setDelegate(ItemWriter<T> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* In the processing of the {@link java.util.concurrent.Future}s passed, nulls are <em>not</em> passed to the
|
||||
* delegate since they are considered filtered out by the {@link org.springframework.batch.integration.async.AsyncItemProcessor}'s
|
||||
* delegated {@link org.springframework.batch.item.ItemProcessor}. If the unwrapping
|
||||
* of the {@link Future} results in an {@link ExecutionException}, that will be
|
||||
* unwrapped and the cause will be thrown.
|
||||
*
|
||||
* @param items {@link java.util.concurrent.Future}s to be unwrapped and passed to the delegate
|
||||
* @throws Exception The exception returned by the Future if one was thrown
|
||||
*/
|
||||
public void write(List<? extends Future<T>> items) throws Exception {
|
||||
List<T> list = new ArrayList<>();
|
||||
for (Future<T> future : items) {
|
||||
try {
|
||||
T item = future.get();
|
||||
|
||||
if(item != null) {
|
||||
list.add(future.get());
|
||||
}
|
||||
}
|
||||
catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause();
|
||||
|
||||
if(cause != null && cause instanceof Exception) {
|
||||
logger.debug("An exception was thrown while processing an item", e);
|
||||
|
||||
throw (Exception) cause;
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate.write(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(ExecutionContext executionContext) throws ItemStreamException {
|
||||
if (delegate instanceof ItemStream) {
|
||||
((ItemStream) delegate).open(executionContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(ExecutionContext executionContext) throws ItemStreamException {
|
||||
if (delegate instanceof ItemStream) {
|
||||
((ItemStream) delegate).update(executionContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws ItemStreamException {
|
||||
if (delegate instanceof ItemStream) {
|
||||
((ItemStream) delegate).close();
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright 2006-2018 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
|
||||
*
|
||||
* https://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.async;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.ItemStreamException;
|
||||
import org.springframework.batch.item.ItemStreamWriter;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class AsyncItemWriter<T> implements ItemStreamWriter<Future<T>>, InitializingBean {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AsyncItemWriter.class);
|
||||
|
||||
private ItemWriter<T> delegate;
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(delegate, "A delegate ItemWriter must be provided.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param delegate ItemWriter that does the actual writing of the Future results
|
||||
*/
|
||||
public void setDelegate(ItemWriter<T> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* In the processing of the {@link java.util.concurrent.Future}s passed, nulls are
|
||||
* <em>not</em> passed to the delegate since they are considered filtered out by the
|
||||
* {@link org.springframework.batch.integration.async.AsyncItemProcessor}'s delegated
|
||||
* {@link org.springframework.batch.item.ItemProcessor}. If the unwrapping of the
|
||||
* {@link Future} results in an {@link ExecutionException}, that will be unwrapped and
|
||||
* the cause will be thrown.
|
||||
* @param items {@link java.util.concurrent.Future}s to be unwrapped and passed to the
|
||||
* delegate
|
||||
* @throws Exception The exception returned by the Future if one was thrown
|
||||
*/
|
||||
public void write(List<? extends Future<T>> items) throws Exception {
|
||||
List<T> list = new ArrayList<>();
|
||||
for (Future<T> future : items) {
|
||||
try {
|
||||
T item = future.get();
|
||||
|
||||
if (item != null) {
|
||||
list.add(future.get());
|
||||
}
|
||||
}
|
||||
catch (ExecutionException e) {
|
||||
Throwable cause = e.getCause();
|
||||
|
||||
if (cause != null && cause instanceof Exception) {
|
||||
logger.debug("An exception was thrown while processing an item", e);
|
||||
|
||||
throw (Exception) cause;
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate.write(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(ExecutionContext executionContext) throws ItemStreamException {
|
||||
if (delegate instanceof ItemStream) {
|
||||
((ItemStream) delegate).open(executionContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(ExecutionContext executionContext) throws ItemStreamException {
|
||||
if (delegate instanceof ItemStream) {
|
||||
((ItemStream) delegate).update(executionContext);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws ItemStreamException {
|
||||
if (delegate instanceof ItemStream) {
|
||||
((ItemStream) delegate).close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,11 +24,10 @@ import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
|
||||
/**
|
||||
* A {@link ChannelInterceptor} that adds the current {@link StepExecution} (if
|
||||
* there is one) as a header to the message. Downstream asynchronous handlers
|
||||
* can then take advantage of the step context without needing to be step
|
||||
* scoped, which is a problem for handlers executing in another thread because
|
||||
* the scope context is not available.
|
||||
* A {@link ChannelInterceptor} that adds the current {@link StepExecution} (if there is
|
||||
* one) as a header to the message. Downstream asynchronous handlers can then take
|
||||
* advantage of the step context without needing to be step scoped, which is a problem for
|
||||
* handlers executing in another thread because the scope context is not available.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Components for executing item processing asynchronously and writing the results when processing is complete.
|
||||
* Components for executing item processing asynchronously and writing the results when
|
||||
* processing is complete.
|
||||
*
|
||||
* @author Michael Minella
|
||||
* @author Mahmoud Ben Hassine
|
||||
|
||||
@@ -19,24 +19,21 @@ 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.
|
||||
*
|
||||
* Exception indicating that a failure or early completion condition was detected in a
|
||||
* remote worker.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class AsynchronousFailureException extends ItemWriterException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Create a new {@link AsynchronousFailureException} based on a message and
|
||||
* another exception.
|
||||
*
|
||||
* @param message
|
||||
* the message for this exception
|
||||
* @param cause
|
||||
* the other exception
|
||||
* 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);
|
||||
@@ -44,9 +41,7 @@ public class AsynchronousFailureException extends ItemWriterException {
|
||||
|
||||
/**
|
||||
* Create a new {@link AsynchronousFailureException} based on a message.
|
||||
*
|
||||
* @param message
|
||||
* the message for this exception
|
||||
* @param message the message for this exception
|
||||
*/
|
||||
public AsynchronousFailureException(String message) {
|
||||
super(message);
|
||||
|
||||
@@ -16,28 +16,27 @@
|
||||
|
||||
package org.springframework.batch.integration.chunk;
|
||||
|
||||
|
||||
/**
|
||||
* Interface for a remote worker in the Remote Chunking pattern. A request comes from a manager process containing some
|
||||
* items to be processed. Once the items are done with a response needs to be generated containing a summary of the
|
||||
* result.
|
||||
*
|
||||
* Interface for a remote worker in the Remote Chunking pattern. A request comes from a
|
||||
* manager process containing some items to be processed. Once the items are done with a
|
||||
* response needs to be generated containing a summary of the result.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @param <T> the type of the items to be processed (it is recommended to use a Memento like a primary key)
|
||||
* @param <T> the type of the items to be processed (it is recommended to use a Memento
|
||||
* like a primary key)
|
||||
*/
|
||||
public interface ChunkHandler<T> {
|
||||
|
||||
/**
|
||||
* Handle the chunk, processing all the items and returning a response summarising the result. If the result is a
|
||||
* failure then the response should say so. The handler only throws an exception if it needs to roll back a
|
||||
* transaction and knows that the request will be re-delivered (if not to the same handler then to one processing
|
||||
* the same Step).
|
||||
*
|
||||
* Handle the chunk, processing all the items and returning a response summarising the
|
||||
* result. If the result is a failure then the response should say so. The handler
|
||||
* only throws an exception if it needs to roll back a transaction and knows that the
|
||||
* request will be re-delivered (if not to the same handler then to one processing the
|
||||
* same Step).
|
||||
* @param chunk a request containing the chunk to process
|
||||
* @return a response summarising the result
|
||||
*
|
||||
* @throws Exception if the handler needs to roll back a transaction and have the chunk re-delivered
|
||||
* @throws Exception if the handler needs to roll back a transaction and have the
|
||||
* chunk re-delivered
|
||||
*/
|
||||
ChunkResponse handleChunk(ChunkRequest<T> chunk) throws Exception;
|
||||
|
||||
|
||||
@@ -1,344 +1,344 @@
|
||||
/*
|
||||
* Copyright 2006-2021 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
|
||||
*
|
||||
* https://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.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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.StepExecutionListener;
|
||||
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.core.MessagingTemplate;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class ChunkMessageChannelItemWriter<T> implements StepExecutionListener, ItemWriter<T>,
|
||||
ItemStream, StepContributionSource {
|
||||
|
||||
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 MessagingTemplate messagingGateway;
|
||||
|
||||
private final LocalState localState = new LocalState();
|
||||
|
||||
private long throttleLimit = DEFAULT_THROTTLE_LIMIT;
|
||||
|
||||
private final int DEFAULT_MAX_WAIT_TIMEOUTS = 40;
|
||||
|
||||
private int maxWaitTimeouts = DEFAULT_MAX_WAIT_TIMEOUTS;
|
||||
|
||||
private PollableChannel replyChannel;
|
||||
|
||||
/**
|
||||
* The maximum number of times to wait at the end of a step for a non-null result from the remote workers. This is a
|
||||
* multiplier on the receive timeout set separately on the gateway. The ideal value is a compromise between allowing
|
||||
* slow workers time to finish, and responsiveness if there is a dead worker. Defaults to 40.
|
||||
*
|
||||
* @param maxWaitTimeouts the maximum number of wait timeouts
|
||||
*/
|
||||
public void setMaxWaitTimeouts(int maxWaitTimeouts) {
|
||||
this.maxWaitTimeouts = maxWaitTimeouts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 setMessagingOperations(MessagingTemplate messagingGateway) {
|
||||
this.messagingGateway = messagingGateway;
|
||||
}
|
||||
|
||||
public void setReplyChannel(PollableChannel replyChannel) {
|
||||
this.replyChannel = replyChannel;
|
||||
}
|
||||
|
||||
public void write(List<? extends T> items) throws Exception {
|
||||
|
||||
// Block until expecting <= throttle limit
|
||||
while (localState.getExpecting() > throttleLimit) {
|
||||
getNextResult();
|
||||
}
|
||||
|
||||
if (!items.isEmpty()) {
|
||||
|
||||
ChunkRequest<T> request = localState.getRequest(items);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Dispatching chunk: " + request);
|
||||
}
|
||||
messagingGateway.send(new GenericMessage<>(request));
|
||||
localState.incrementExpected();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeStep(StepExecution stepExecution) {
|
||||
localState.setStepExecution(stepExecution);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@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());
|
||||
}
|
||||
finally {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Finished waiting for results in step listener. Still expecting: "
|
||||
+ localState.getExpecting());
|
||||
}
|
||||
|
||||
for (StepContribution contribution : getStepContributions()) {
|
||||
stepExecution.apply(contribution);
|
||||
}
|
||||
}
|
||||
if (timedOut) {
|
||||
stepExecution.setStatus(BatchStatus.FAILED);
|
||||
return ExitStatus.FAILED.addExitDescription("Timed out waiting for " + localState.getExpecting()
|
||||
+ " backlog 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.open(executionContext.getInt(EXPECTED), executionContext.getInt(ACTUAL));
|
||||
if (!waitForResults()) {
|
||||
throw new ItemStreamException("Timed out waiting for back log on open");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void update(ExecutionContext executionContext) throws ItemStreamException {
|
||||
executionContext.putInt(EXPECTED, localState.expected.intValue());
|
||||
executionContext.putInt(ACTUAL, localState.actual.intValue());
|
||||
}
|
||||
|
||||
public Collection<StepContribution> getStepContributions() {
|
||||
List<StepContribution> contributions = new ArrayList<>();
|
||||
for (ChunkResponse response : localState.pollChunkResponses()) {
|
||||
StepContribution contribution = response.getStepContribution();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Applying: " + response);
|
||||
}
|
||||
contributions.add(contribution);
|
||||
}
|
||||
return contributions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() throws AsynchronousFailureException {
|
||||
int count = 0;
|
||||
int maxCount = maxWaitTimeouts;
|
||||
Throwable failure = null;
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Waiting for " + localState.getExpecting() + " results");
|
||||
}
|
||||
while (localState.getExpecting() > 0 && count++ < maxCount) {
|
||||
try {
|
||||
getNextResult();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.error("Detected error in remote result. Trying to recover " + localState.getExpecting()
|
||||
+ " outstanding results before completing.", t);
|
||||
failure = t;
|
||||
}
|
||||
}
|
||||
if (failure != null) {
|
||||
throw wrapIfNecessary(failure);
|
||||
}
|
||||
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)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void getNextResult() throws AsynchronousFailureException {
|
||||
Message<ChunkResponse> message = (Message<ChunkResponse>) messagingGateway.receive(replyChannel);
|
||||
if (message != null) {
|
||||
ChunkResponse payload = message.getPayload();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Found result: " + payload);
|
||||
}
|
||||
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() + "].");
|
||||
if (payload.isRedelivered()) {
|
||||
logger
|
||||
.warn("Redelivered result detected, which may indicate stale state. In the best case, we just picked up a timed out message "
|
||||
+ "from a previous failed execution. In the worst case (and if this is not a restart), "
|
||||
+ "the step may now timeout. In that case if you believe that all messages "
|
||||
+ "from workers have been sent, the business state "
|
||||
+ "is probably inconsistent, and the step will fail.");
|
||||
localState.incrementRedelivered();
|
||||
}
|
||||
localState.pushResponse(payload);
|
||||
localState.incrementActual();
|
||||
if (!payload.isSuccessful()) {
|
||||
throw new AsynchronousFailureException("Failure or interrupt detected in handler: "
|
||||
+ payload.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-throws the original throwable if it is unchecked, wraps checked exceptions into
|
||||
* {@link AsynchronousFailureException}.
|
||||
*/
|
||||
private static AsynchronousFailureException wrapIfNecessary(Throwable throwable) {
|
||||
if (throwable instanceof Error) {
|
||||
throw (Error) throwable;
|
||||
}
|
||||
else if (throwable instanceof AsynchronousFailureException) {
|
||||
return (AsynchronousFailureException) throwable;
|
||||
}
|
||||
else {
|
||||
return new AsynchronousFailureException("Exception in remote process", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private static class LocalState {
|
||||
|
||||
private final AtomicInteger current = new AtomicInteger(-1);
|
||||
|
||||
private final AtomicInteger actual = new AtomicInteger();
|
||||
|
||||
private final AtomicInteger expected = new AtomicInteger();
|
||||
|
||||
private final AtomicInteger redelivered = new AtomicInteger();
|
||||
|
||||
private StepExecution stepExecution;
|
||||
|
||||
private final Queue<ChunkResponse> contributions = new LinkedBlockingQueue<>();
|
||||
|
||||
public int getExpecting() {
|
||||
return expected.get() - actual.get();
|
||||
}
|
||||
|
||||
public <T> ChunkRequest<T> getRequest(List<? extends T> items) {
|
||||
return new ChunkRequest<>(current.incrementAndGet(), items, getJobId(), createStepContribution());
|
||||
}
|
||||
|
||||
public void open(int expectedValue, int actualValue) {
|
||||
actual.set(actualValue);
|
||||
expected.set(expectedValue);
|
||||
}
|
||||
|
||||
public Collection<ChunkResponse> pollChunkResponses() {
|
||||
Collection<ChunkResponse> set = new ArrayList<>();
|
||||
synchronized (contributions) {
|
||||
ChunkResponse item = contributions.poll();
|
||||
while (item != null) {
|
||||
set.add(item);
|
||||
item = contributions.poll();
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public void pushResponse(ChunkResponse stepContribution) {
|
||||
synchronized (contributions) {
|
||||
contributions.add(stepContribution);
|
||||
}
|
||||
}
|
||||
|
||||
public void incrementRedelivered() {
|
||||
redelivered.incrementAndGet();
|
||||
}
|
||||
|
||||
public void incrementActual() {
|
||||
actual.incrementAndGet();
|
||||
}
|
||||
|
||||
public void incrementExpected() {
|
||||
expected.incrementAndGet();
|
||||
}
|
||||
|
||||
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.set(0);
|
||||
actual.set(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2006-2021 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
|
||||
*
|
||||
* https://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.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
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.StepExecutionListener;
|
||||
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.core.MessagingTemplate;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
public class ChunkMessageChannelItemWriter<T>
|
||||
implements StepExecutionListener, ItemWriter<T>, ItemStream, StepContributionSource {
|
||||
|
||||
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 MessagingTemplate messagingGateway;
|
||||
|
||||
private final LocalState localState = new LocalState();
|
||||
|
||||
private long throttleLimit = DEFAULT_THROTTLE_LIMIT;
|
||||
|
||||
private final int DEFAULT_MAX_WAIT_TIMEOUTS = 40;
|
||||
|
||||
private int maxWaitTimeouts = DEFAULT_MAX_WAIT_TIMEOUTS;
|
||||
|
||||
private PollableChannel replyChannel;
|
||||
|
||||
/**
|
||||
* The maximum number of times to wait at the end of a step for a non-null result from
|
||||
* the remote workers. This is a multiplier on the receive timeout set separately on
|
||||
* the gateway. The ideal value is a compromise between allowing slow workers time to
|
||||
* finish, and responsiveness if there is a dead worker. Defaults to 40.
|
||||
* @param maxWaitTimeouts the maximum number of wait timeouts
|
||||
*/
|
||||
public void setMaxWaitTimeouts(int maxWaitTimeouts) {
|
||||
this.maxWaitTimeouts = maxWaitTimeouts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 setMessagingOperations(MessagingTemplate messagingGateway) {
|
||||
this.messagingGateway = messagingGateway;
|
||||
}
|
||||
|
||||
public void setReplyChannel(PollableChannel replyChannel) {
|
||||
this.replyChannel = replyChannel;
|
||||
}
|
||||
|
||||
public void write(List<? extends T> items) throws Exception {
|
||||
|
||||
// Block until expecting <= throttle limit
|
||||
while (localState.getExpecting() > throttleLimit) {
|
||||
getNextResult();
|
||||
}
|
||||
|
||||
if (!items.isEmpty()) {
|
||||
|
||||
ChunkRequest<T> request = localState.getRequest(items);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Dispatching chunk: " + request);
|
||||
}
|
||||
messagingGateway.send(new GenericMessage<>(request));
|
||||
localState.incrementExpected();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeStep(StepExecution stepExecution) {
|
||||
localState.setStepExecution(stepExecution);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@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());
|
||||
}
|
||||
finally {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Finished waiting for results in step listener. Still expecting: "
|
||||
+ localState.getExpecting());
|
||||
}
|
||||
|
||||
for (StepContribution contribution : getStepContributions()) {
|
||||
stepExecution.apply(contribution);
|
||||
}
|
||||
}
|
||||
if (timedOut) {
|
||||
stepExecution.setStatus(BatchStatus.FAILED);
|
||||
return ExitStatus.FAILED.addExitDescription(
|
||||
"Timed out waiting for " + localState.getExpecting() + " backlog 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.open(executionContext.getInt(EXPECTED), executionContext.getInt(ACTUAL));
|
||||
if (!waitForResults()) {
|
||||
throw new ItemStreamException("Timed out waiting for back log on open");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void update(ExecutionContext executionContext) throws ItemStreamException {
|
||||
executionContext.putInt(EXPECTED, localState.expected.intValue());
|
||||
executionContext.putInt(ACTUAL, localState.actual.intValue());
|
||||
}
|
||||
|
||||
public Collection<StepContribution> getStepContributions() {
|
||||
List<StepContribution> contributions = new ArrayList<>();
|
||||
for (ChunkResponse response : localState.pollChunkResponses()) {
|
||||
StepContribution contribution = response.getStepContribution();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Applying: " + response);
|
||||
}
|
||||
contributions.add(contribution);
|
||||
}
|
||||
return contributions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() throws AsynchronousFailureException {
|
||||
int count = 0;
|
||||
int maxCount = maxWaitTimeouts;
|
||||
Throwable failure = null;
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Waiting for " + localState.getExpecting() + " results");
|
||||
}
|
||||
while (localState.getExpecting() > 0 && count++ < maxCount) {
|
||||
try {
|
||||
getNextResult();
|
||||
}
|
||||
catch (Throwable t) {
|
||||
logger.error("Detected error in remote result. Trying to recover " + localState.getExpecting()
|
||||
+ " outstanding results before completing.", t);
|
||||
failure = t;
|
||||
}
|
||||
}
|
||||
if (failure != null) {
|
||||
throw wrapIfNecessary(failure);
|
||||
}
|
||||
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)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void getNextResult() throws AsynchronousFailureException {
|
||||
Message<ChunkResponse> message = (Message<ChunkResponse>) messagingGateway.receive(replyChannel);
|
||||
if (message != null) {
|
||||
ChunkResponse payload = message.getPayload();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Found result: " + payload);
|
||||
}
|
||||
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() + "].");
|
||||
if (payload.isRedelivered()) {
|
||||
logger.warn(
|
||||
"Redelivered result detected, which may indicate stale state. In the best case, we just picked up a timed out message "
|
||||
+ "from a previous failed execution. In the worst case (and if this is not a restart), "
|
||||
+ "the step may now timeout. In that case if you believe that all messages "
|
||||
+ "from workers have been sent, the business state "
|
||||
+ "is probably inconsistent, and the step will fail.");
|
||||
localState.incrementRedelivered();
|
||||
}
|
||||
localState.pushResponse(payload);
|
||||
localState.incrementActual();
|
||||
if (!payload.isSuccessful()) {
|
||||
throw new AsynchronousFailureException(
|
||||
"Failure or interrupt detected in handler: " + payload.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-throws the original throwable if it is unchecked, wraps checked exceptions into
|
||||
* {@link AsynchronousFailureException}.
|
||||
*/
|
||||
private static AsynchronousFailureException wrapIfNecessary(Throwable throwable) {
|
||||
if (throwable instanceof Error) {
|
||||
throw (Error) throwable;
|
||||
}
|
||||
else if (throwable instanceof AsynchronousFailureException) {
|
||||
return (AsynchronousFailureException) throwable;
|
||||
}
|
||||
else {
|
||||
return new AsynchronousFailureException("Exception in remote process", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
private static class LocalState {
|
||||
|
||||
private final AtomicInteger current = new AtomicInteger(-1);
|
||||
|
||||
private final AtomicInteger actual = new AtomicInteger();
|
||||
|
||||
private final AtomicInteger expected = new AtomicInteger();
|
||||
|
||||
private final AtomicInteger redelivered = new AtomicInteger();
|
||||
|
||||
private StepExecution stepExecution;
|
||||
|
||||
private final Queue<ChunkResponse> contributions = new LinkedBlockingQueue<>();
|
||||
|
||||
public int getExpecting() {
|
||||
return expected.get() - actual.get();
|
||||
}
|
||||
|
||||
public <T> ChunkRequest<T> getRequest(List<? extends T> items) {
|
||||
return new ChunkRequest<>(current.incrementAndGet(), items, getJobId(), createStepContribution());
|
||||
}
|
||||
|
||||
public void open(int expectedValue, int actualValue) {
|
||||
actual.set(actualValue);
|
||||
expected.set(expectedValue);
|
||||
}
|
||||
|
||||
public Collection<ChunkResponse> pollChunkResponses() {
|
||||
Collection<ChunkResponse> set = new ArrayList<>();
|
||||
synchronized (contributions) {
|
||||
ChunkResponse item = contributions.poll();
|
||||
while (item != null) {
|
||||
set.add(item);
|
||||
item = contributions.poll();
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public void pushResponse(ChunkResponse stepContribution) {
|
||||
synchronized (contributions) {
|
||||
contributions.add(stepContribution);
|
||||
}
|
||||
}
|
||||
|
||||
public void incrementRedelivered() {
|
||||
redelivered.incrementAndGet();
|
||||
}
|
||||
|
||||
public void incrementActual() {
|
||||
actual.incrementAndGet();
|
||||
}
|
||||
|
||||
public void incrementExpected() {
|
||||
expected.incrementAndGet();
|
||||
}
|
||||
|
||||
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.set(0);
|
||||
actual.set(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,13 +33,13 @@ import org.springframework.retry.RetryException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link ChunkHandler} based on a {@link ChunkProcessor}. Knows how to distinguish between a processor that is fault
|
||||
* tolerant, and one that is not. If the processor is fault tolerant then exceptions can be propagated on the assumption
|
||||
* that there will be a roll back and the request will be re-delivered.
|
||||
* A {@link ChunkHandler} based on a {@link ChunkProcessor}. Knows how to distinguish
|
||||
* between a processor that is fault tolerant, and one that is not. If the processor is
|
||||
* fault tolerant then exceptions can be propagated on the assumption that there will be a
|
||||
* roll back and the request will be re-delivered.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Michael Minella
|
||||
*
|
||||
* @param <S> the type of the items in the chunk to be handled
|
||||
*/
|
||||
@MessageEndpoint
|
||||
@@ -60,7 +60,6 @@ public class ChunkProcessorChunkHandler<S> implements ChunkHandler<S>, Initializ
|
||||
|
||||
/**
|
||||
* Public setter for the {@link ChunkProcessor}.
|
||||
*
|
||||
* @param chunkProcessor the chunkProcessor to set
|
||||
*/
|
||||
public void setChunkProcessor(ChunkProcessor<S> chunkProcessor) {
|
||||
@@ -83,8 +82,8 @@ public class ChunkProcessorChunkHandler<S> implements ChunkHandler<S>, Initializ
|
||||
Throwable failure = process(chunkRequest, stepContribution);
|
||||
if (failure != null) {
|
||||
logger.debug("Failed chunk", failure);
|
||||
return new ChunkResponse(false, chunkRequest.getSequence(), chunkRequest.getJobId(), stepContribution, failure.getClass().getName()
|
||||
+ ": " + failure.getMessage());
|
||||
return new ChunkResponse(false, chunkRequest.getSequence(), chunkRequest.getJobId(), stepContribution,
|
||||
failure.getClass().getName() + ": " + failure.getMessage());
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
||||
@@ -22,11 +22,9 @@ import java.util.Collection;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
|
||||
/**
|
||||
* Encapsulation of a chunk of items to be processed remotely as part of a step
|
||||
* execution.
|
||||
*
|
||||
* Encapsulation of a chunk of items to be processed remotely as part of a step execution.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @param <T> the type of the items to process
|
||||
*/
|
||||
public class ChunkRequest<T> implements Serializable {
|
||||
|
||||
@@ -22,11 +22,12 @@ import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Encapsulates a response to processing a chunk of items, summarising the result as a {@link StepContribution}.
|
||||
*
|
||||
* Encapsulates a response to processing a chunk of items, summarising the result as a
|
||||
* {@link StepContribution}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Mahmoud Ben Hassine
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class ChunkResponse implements Serializable {
|
||||
|
||||
@@ -39,7 +40,7 @@ public class ChunkResponse implements Serializable {
|
||||
private final boolean status;
|
||||
|
||||
private final String message;
|
||||
|
||||
|
||||
private final boolean redelivered;
|
||||
|
||||
private final int sequence;
|
||||
@@ -52,7 +53,8 @@ public class ChunkResponse implements Serializable {
|
||||
this(status, sequence, jobId, stepContribution, null);
|
||||
}
|
||||
|
||||
public ChunkResponse(boolean status, int sequence, Long jobId, StepContribution stepContribution, @Nullable String message) {
|
||||
public ChunkResponse(boolean status, int sequence, Long jobId, StepContribution stepContribution,
|
||||
@Nullable String message) {
|
||||
this(status, sequence, jobId, stepContribution, message, false);
|
||||
}
|
||||
|
||||
@@ -60,7 +62,8 @@ public class ChunkResponse implements Serializable {
|
||||
this(input.status, input.sequence, input.jobId, input.stepContribution, input.message, redelivered);
|
||||
}
|
||||
|
||||
public ChunkResponse(boolean status, int sequence, Long jobId, StepContribution stepContribution, @Nullable String message, boolean redelivered) {
|
||||
public ChunkResponse(boolean status, int sequence, Long jobId, StepContribution stepContribution,
|
||||
@Nullable String message, boolean redelivered) {
|
||||
this.status = status;
|
||||
this.sequence = sequence;
|
||||
this.jobId = jobId;
|
||||
@@ -76,7 +79,7 @@ public class ChunkResponse implements Serializable {
|
||||
public Long getJobId() {
|
||||
return jobId;
|
||||
}
|
||||
|
||||
|
||||
public int getSequence() {
|
||||
return sequence;
|
||||
}
|
||||
@@ -84,7 +87,7 @@ public class ChunkResponse implements Serializable {
|
||||
public boolean isSuccessful() {
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
public boolean isRedelivered() {
|
||||
return redelivered;
|
||||
}
|
||||
@@ -98,8 +101,8 @@ public class ChunkResponse implements Serializable {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + ": jobId=" + jobId + ", sequence=" + sequence + ", stepContribution=" + stepContribution
|
||||
+ ", successful=" + status;
|
||||
return getClass().getSimpleName() + ": jobId=" + jobId + ", sequence=" + sequence + ", stepContribution="
|
||||
+ stepContribution + ", successful=" + status;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
/*
|
||||
* Copyright 2009-2010 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
|
||||
*
|
||||
* https://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.messaging.handler.annotation.Header;
|
||||
import org.springframework.jms.support.JmsHeaders;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class JmsRedeliveredExtractor {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(JmsRedeliveredExtractor.class);
|
||||
|
||||
public ChunkResponse extract(ChunkResponse input, @Header(JmsHeaders.REDELIVERED) boolean redelivered) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Extracted redelivered flag for response, value="+redelivered);
|
||||
}
|
||||
return new ChunkResponse(input, redelivered);
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2009-2010 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
|
||||
*
|
||||
* https://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.messaging.handler.annotation.Header;
|
||||
import org.springframework.jms.support.JmsHeaders;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class JmsRedeliveredExtractor {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(JmsRedeliveredExtractor.class);
|
||||
|
||||
public ChunkResponse extract(ChunkResponse input, @Header(JmsHeaders.REDELIVERED) boolean redelivered) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Extracted redelivered flag for response, value=" + redelivered);
|
||||
}
|
||||
return new ChunkResponse(input, redelivered);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* A {@link ChannelInterceptor} that turns a pollable channel into a "pass-thru channel": if a client calls
|
||||
* <code>receive()</code> on the channel it will delegate to a {@link MessageSource} to pull the message directly from
|
||||
* an external source. This is particularly useful in combination with a message channel in thread scope, in which case
|
||||
* the <code>receive()</code> can join a transaction which was started by the caller.
|
||||
* A {@link ChannelInterceptor} that turns a pollable channel into a "pass-thru channel":
|
||||
* if a client calls <code>receive()</code> on the channel it will delegate to a
|
||||
* {@link MessageSource} to pull the message directly from an external source. This is
|
||||
* particularly useful in combination with a message channel in thread scope, in which
|
||||
* case the <code>receive()</code> can join a transaction which was started by the caller.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
@@ -41,9 +41,8 @@ public class MessageSourcePollerInterceptor implements ChannelInterceptor, Initi
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional MessageChannel for injecting the message received from the source (defaults to the channel intercepted
|
||||
* in {@link #preReceive(MessageChannel)}).
|
||||
*
|
||||
* Optional MessageChannel for injecting the message received from the source
|
||||
* (defaults to the channel intercepted in {@link #preReceive(MessageChannel)}).
|
||||
* @param channel the channel to set
|
||||
*/
|
||||
public void setChannel(MessageChannel channel) {
|
||||
@@ -66,8 +65,8 @@ public class MessageSourcePollerInterceptor implements ChannelInterceptor, Initi
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive from the {@link MessageSource} and send immediately to the input channel, so that the call that we are
|
||||
* intercepting always a message to receive.
|
||||
* Receive from the {@link MessageSource} and send immediately to the input channel,
|
||||
* so that the call that we are intercepting always a message to receive.
|
||||
*
|
||||
* @see ChannelInterceptor#preReceive(MessageChannel)
|
||||
*/
|
||||
|
||||
@@ -36,16 +36,18 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Convenient factory bean for a chunk handler that also converts an existing chunk-oriented step into a remote chunk
|
||||
* manager. The idea is to lift the existing chunk processor out of a Step that works locally, and replace it with a one
|
||||
* that writes chunks into a message channel. The existing step hands its business chunk processing responsibility over
|
||||
* to the handler produced by the factory, which then needs to be set up as a worker on the other end of the channel the
|
||||
* chunks are being sent to. Once this chunk handler is installed the application is playing the role of both the manager
|
||||
* and the worker listeners in the Remote Chunking pattern for the Step in question.
|
||||
*
|
||||
* Convenient factory bean for a chunk handler that also converts an existing
|
||||
* chunk-oriented step into a remote chunk manager. The idea is to lift the existing chunk
|
||||
* processor out of a Step that works locally, and replace it with a one that writes
|
||||
* chunks into a message channel. The existing step hands its business chunk processing
|
||||
* responsibility over to the handler produced by the factory, which then needs to be set
|
||||
* up as a worker on the other end of the channel the chunks are being sent to. Once this
|
||||
* chunk handler is installed the application is playing the role of both the manager and
|
||||
* the worker listeners in the Remote Chunking pattern for the Step in question.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Mahmoud Ben Hassine
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class RemoteChunkHandlerFactoryBean<T> implements FactoryBean<ChunkHandler<T>> {
|
||||
|
||||
@@ -59,7 +61,6 @@ public class RemoteChunkHandlerFactoryBean<T> implements FactoryBean<ChunkHandle
|
||||
|
||||
/**
|
||||
* The local step that is to be converted to a remote chunk manager.
|
||||
*
|
||||
* @param step the step to set
|
||||
*/
|
||||
public void setStep(TaskletStep step) {
|
||||
@@ -67,9 +68,9 @@ public class RemoteChunkHandlerFactoryBean<T> implements FactoryBean<ChunkHandle
|
||||
}
|
||||
|
||||
/**
|
||||
* The item writer to be injected into the step. Its responsibility is to send chunks of items to remote workers.
|
||||
* Usually in practice it will be a {@link ChunkMessageChannelItemWriter}.
|
||||
*
|
||||
* The item writer to be injected into the step. Its responsibility is to send chunks
|
||||
* of items to remote workers. Usually in practice it will be a
|
||||
* {@link ChunkMessageChannelItemWriter}.
|
||||
* @param chunkWriter the chunk writer to set
|
||||
*/
|
||||
public void setChunkWriter(ItemWriter<T> chunkWriter) {
|
||||
@@ -78,8 +79,8 @@ public class RemoteChunkHandlerFactoryBean<T> implements FactoryBean<ChunkHandle
|
||||
|
||||
/**
|
||||
* A source of {@link StepContribution} instances coming back from remote workers.
|
||||
*
|
||||
* @param stepContributionSource the step contribution source to set (defaults to the chunk writer)
|
||||
* @param stepContributionSource the step contribution source to set (defaults to the
|
||||
* chunk writer)
|
||||
*/
|
||||
public void setStepContributionSource(StepContributionSource stepContributionSource) {
|
||||
this.stepContributionSource = stepContributionSource;
|
||||
@@ -87,7 +88,7 @@ public class RemoteChunkHandlerFactoryBean<T> implements FactoryBean<ChunkHandle
|
||||
|
||||
/**
|
||||
* The type of object created by this factory. Returns {@link ChunkHandler} class.
|
||||
*
|
||||
*
|
||||
* @see FactoryBean#getObjectType()
|
||||
*/
|
||||
public Class<?> getObjectType() {
|
||||
@@ -96,7 +97,7 @@ public class RemoteChunkHandlerFactoryBean<T> implements FactoryBean<ChunkHandle
|
||||
|
||||
/**
|
||||
* Optimization for the bean factory (always returns true).
|
||||
*
|
||||
*
|
||||
* @see FactoryBean#isSingleton()
|
||||
*/
|
||||
public boolean isSingleton() {
|
||||
@@ -104,10 +105,10 @@ public class RemoteChunkHandlerFactoryBean<T> implements FactoryBean<ChunkHandle
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link ChunkHandler} from the {@link ChunkProcessor} extracted from the {@link #setStep(TaskletStep)
|
||||
* step} provided. Also modifies the step to send chunks to the chunk handler via the
|
||||
* {@link #setChunkWriter(ItemWriter) chunk writer}.
|
||||
*
|
||||
* Builds a {@link ChunkHandler} from the {@link ChunkProcessor} extracted from the
|
||||
* {@link #setStep(TaskletStep) step} provided. Also modifies the step to send chunks
|
||||
* to the chunk handler via the {@link #setChunkWriter(ItemWriter) chunk writer}.
|
||||
*
|
||||
* @see FactoryBean#getObject()
|
||||
*/
|
||||
public ChunkHandler<T> getObject() throws Exception {
|
||||
@@ -124,8 +125,8 @@ public class RemoteChunkHandlerFactoryBean<T> implements FactoryBean<ChunkHandle
|
||||
}
|
||||
|
||||
Tasklet tasklet = getTasklet(step);
|
||||
Assert.state(tasklet instanceof ChunkOrientedTasklet<?>, "Tasklet must be ChunkOrientedTasklet in step="
|
||||
+ step.getName());
|
||||
Assert.state(tasklet instanceof ChunkOrientedTasklet<?>,
|
||||
"Tasklet must be ChunkOrientedTasklet in step=" + step.getName());
|
||||
|
||||
ChunkProcessor<T> chunkProcessor = getChunkProcessor((ChunkOrientedTasklet<?>) tasklet);
|
||||
Assert.state(chunkProcessor != null, "ChunkProcessor must be accessible in Tasklet in step=" + step.getName());
|
||||
@@ -161,35 +162,37 @@ public class RemoteChunkHandlerFactoryBean<T> implements FactoryBean<ChunkHandle
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the chunk processor in the tasklet provided with one that can act as a manager in the Remote Chunking
|
||||
* pattern.
|
||||
*
|
||||
* Replace the chunk processor in the tasklet provided with one that can act as a
|
||||
* manager in the Remote Chunking pattern.
|
||||
* @param tasklet a ChunkOrientedTasklet
|
||||
* @param chunkWriter an ItemWriter that can send the chunks to remote workers
|
||||
* @param stepContributionSource a StepContributionSource used to gather results from the workers
|
||||
* @param stepContributionSource a StepContributionSource used to gather results from
|
||||
* the workers
|
||||
*/
|
||||
private void replaceChunkProcessor(ChunkOrientedTasklet<?> tasklet, ItemWriter<T> chunkWriter,
|
||||
final StepContributionSource stepContributionSource) {
|
||||
setField(tasklet, "chunkProcessor", new SimpleChunkProcessor<T, T>(new PassThroughItemProcessor<>(),
|
||||
chunkWriter) {
|
||||
@Override
|
||||
protected void write(StepContribution contribution, Chunk<T> inputs, Chunk<T> outputs) throws Exception {
|
||||
doWrite(outputs.getItems());
|
||||
// Do not update the step contribution until the chunks are
|
||||
// actually processed
|
||||
updateStepContribution(contribution, stepContributionSource);
|
||||
}
|
||||
});
|
||||
setField(tasklet, "chunkProcessor",
|
||||
new SimpleChunkProcessor<T, T>(new PassThroughItemProcessor<>(), chunkWriter) {
|
||||
@Override
|
||||
protected void write(StepContribution contribution, Chunk<T> inputs, Chunk<T> outputs)
|
||||
throws Exception {
|
||||
doWrite(outputs.getItems());
|
||||
// Do not update the step contribution until the chunks are
|
||||
// actually processed
|
||||
updateStepContribution(contribution, stepContributionSource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a StepContribution with all the data from a StepContributionSource. The filter and write counts plus the
|
||||
* exit status will be updated to reflect the data in the source.
|
||||
*
|
||||
* Update a StepContribution with all the data from a StepContributionSource. The
|
||||
* filter and write counts plus the exit status will be updated to reflect the data in
|
||||
* the source.
|
||||
* @param contribution the current contribution
|
||||
* @param stepContributionSource a source of StepContributions
|
||||
*/
|
||||
protected void updateStepContribution(StepContribution contribution, StepContributionSource stepContributionSource) {
|
||||
protected void updateStepContribution(StepContribution contribution,
|
||||
StepContributionSource stepContributionSource) {
|
||||
for (StepContribution result : stepContributionSource.getStepContributions()) {
|
||||
contribution.incrementFilterCount(result.getFilterCount());
|
||||
contribution.incrementWriteCount(result.getWriteCount());
|
||||
|
||||
@@ -44,38 +44,46 @@ import org.springframework.transaction.interceptor.TransactionAttribute;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Builder for a manager step in a remote chunking setup. This builder creates and
|
||||
* sets a {@link ChunkMessageChannelItemWriter} on the manager step.
|
||||
* Builder for a manager step in a remote chunking setup. This builder creates and sets a
|
||||
* {@link ChunkMessageChannelItemWriter} on the manager step.
|
||||
*
|
||||
* <p>If no {@code messagingTemplate} is provided through
|
||||
* {@link RemoteChunkingManagerStepBuilder#messagingTemplate(MessagingTemplate)},
|
||||
* this builder will create one and set its default channel to the {@code outputChannel}
|
||||
* provided through {@link RemoteChunkingManagerStepBuilder#outputChannel(MessageChannel)}.</p>
|
||||
* <p>
|
||||
* If no {@code messagingTemplate} is provided through
|
||||
* {@link RemoteChunkingManagerStepBuilder#messagingTemplate(MessagingTemplate)}, this
|
||||
* builder will create one and set its default channel to the {@code outputChannel}
|
||||
* provided through
|
||||
* {@link RemoteChunkingManagerStepBuilder#outputChannel(MessageChannel)}.
|
||||
* </p>
|
||||
*
|
||||
* <p>If a {@code messagingTemplate} is provided, it is assumed that it is fully configured
|
||||
* <p>
|
||||
* If a {@code messagingTemplate} is provided, it is assumed that it is fully configured
|
||||
* and that its default channel is set to an output channel on which requests to workers
|
||||
* will be sent.</p>
|
||||
* will be sent.
|
||||
* </p>
|
||||
*
|
||||
* @param <I> type of input items
|
||||
* @param <O> type of output items
|
||||
*
|
||||
* @since 4.2
|
||||
* @author Mahmoud Ben Hassine
|
||||
*/
|
||||
public class RemoteChunkingManagerStepBuilder<I, O> extends FaultTolerantStepBuilder<I, O> {
|
||||
|
||||
private MessagingTemplate messagingTemplate;
|
||||
|
||||
private PollableChannel inputChannel;
|
||||
|
||||
private MessageChannel outputChannel;
|
||||
|
||||
private final int DEFAULT_MAX_WAIT_TIMEOUTS = 40;
|
||||
|
||||
private static final long DEFAULT_THROTTLE_LIMIT = 6;
|
||||
|
||||
private int maxWaitTimeouts = DEFAULT_MAX_WAIT_TIMEOUTS;
|
||||
|
||||
private long throttleLimit = DEFAULT_THROTTLE_LIMIT;
|
||||
|
||||
/**
|
||||
* Create a new {@link RemoteChunkingManagerStepBuilder}.
|
||||
*
|
||||
* @param stepName name of the manager step
|
||||
*/
|
||||
public RemoteChunkingManagerStepBuilder(String stepName) {
|
||||
@@ -83,10 +91,9 @@ public class RemoteChunkingManagerStepBuilder<I, O> extends FaultTolerantStepBui
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the input channel on which replies from workers will be received.
|
||||
* The provided input channel will be set as a reply channel on the
|
||||
* Set the input channel on which replies from workers will be received. The provided
|
||||
* input channel will be set as a reply channel on the
|
||||
* {@link ChunkMessageChannelItemWriter} created by this builder.
|
||||
*
|
||||
* @param inputChannel the input channel
|
||||
* @return this builder instance for fluent chaining
|
||||
*
|
||||
@@ -99,12 +106,14 @@ public class RemoteChunkingManagerStepBuilder<I, O> extends FaultTolerantStepBui
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the output channel on which requests to workers will be sent. By using
|
||||
* this setter, a default messaging template will be created and the output
|
||||
* channel will be set as its default channel.
|
||||
* <p>Use either this setter or {@link RemoteChunkingManagerStepBuilder#messagingTemplate(MessagingTemplate)}
|
||||
* to provide a fully configured messaging template.</p>
|
||||
*
|
||||
* Set the output channel on which requests to workers will be sent. By using this
|
||||
* setter, a default messaging template will be created and the output channel will be
|
||||
* set as its default channel.
|
||||
* <p>
|
||||
* Use either this setter or
|
||||
* {@link RemoteChunkingManagerStepBuilder#messagingTemplate(MessagingTemplate)} to
|
||||
* provide a fully configured messaging template.
|
||||
* </p>
|
||||
* @param outputChannel the output channel.
|
||||
* @return this builder instance for fluent chaining
|
||||
*
|
||||
@@ -117,12 +126,14 @@ public class RemoteChunkingManagerStepBuilder<I, O> extends FaultTolerantStepBui
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link MessagingTemplate} to use to send data to workers.
|
||||
* <strong>The default channel of the messaging template must be set</strong>.
|
||||
* <p>Use either this setter to provide a fully configured messaging template or
|
||||
* provide an output channel through {@link RemoteChunkingManagerStepBuilder#outputChannel(MessageChannel)}
|
||||
* and a default messaging template will be created.</p>
|
||||
*
|
||||
* Set the {@link MessagingTemplate} to use to send data to workers. <strong>The
|
||||
* default channel of the messaging template must be set</strong>.
|
||||
* <p>
|
||||
* Use either this setter to provide a fully configured messaging template or provide
|
||||
* an output channel through
|
||||
* {@link RemoteChunkingManagerStepBuilder#outputChannel(MessageChannel)} and a
|
||||
* default messaging template will be created.
|
||||
* </p>
|
||||
* @param messagingTemplate the messaging template to use
|
||||
* @return this builder instance for fluent chaining
|
||||
* @see RemoteChunkingManagerStepBuilder#outputChannel(MessageChannel)
|
||||
@@ -134,10 +145,10 @@ public class RemoteChunkingManagerStepBuilder<I, O> extends FaultTolerantStepBui
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of times to wait at the end of a step for a non-null result from the remote workers. This is a
|
||||
* multiplier on the receive timeout set separately on the gateway. The ideal value is a compromise between allowing
|
||||
* slow workers time to finish, and responsiveness if there is a dead worker. Defaults to 40.
|
||||
*
|
||||
* The maximum number of times to wait at the end of a step for a non-null result from
|
||||
* the remote workers. This is a multiplier on the receive timeout set separately on
|
||||
* the gateway. The ideal value is a compromise between allowing slow workers time to
|
||||
* finish, and responsiveness if there is a dead worker. Defaults to 40.
|
||||
* @param maxWaitTimeouts the maximum number of wait timeouts
|
||||
* @return this builder instance for fluent chaining
|
||||
* @see ChunkMessageChannelItemWriter#setMaxWaitTimeouts(int)
|
||||
@@ -149,9 +160,8 @@ public class RemoteChunkingManagerStepBuilder<I, O> extends FaultTolerantStepBui
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the throttle limit. This limits the number of pending requests for chunk processing to avoid
|
||||
* overwhelming the receivers.
|
||||
*
|
||||
* 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
|
||||
* @return this builder instance for fluent chaining
|
||||
* @see ChunkMessageChannelItemWriter#setThrottleLimit(long)
|
||||
@@ -164,7 +174,6 @@ public class RemoteChunkingManagerStepBuilder<I, O> extends FaultTolerantStepBui
|
||||
|
||||
/**
|
||||
* Build a manager {@link TaskletStep}.
|
||||
*
|
||||
* @return the configured manager step
|
||||
* @see RemoteChunkHandlerFactoryBean
|
||||
*/
|
||||
@@ -194,10 +203,9 @@ public class RemoteChunkingManagerStepBuilder<I, O> extends FaultTolerantStepBui
|
||||
}
|
||||
|
||||
/*
|
||||
* The following methods override those from parent builders and return
|
||||
* the current builder type.
|
||||
* FIXME: Change parent builders to be generic and return current builder
|
||||
* type in each method.
|
||||
* The following methods override those from parent builders and return the current
|
||||
* builder type. FIXME: Change parent builders to be generic and return current
|
||||
* builder type in each method.
|
||||
*/
|
||||
|
||||
@Override
|
||||
@@ -339,23 +347,22 @@ public class RemoteChunkingManagerStepBuilder<I, O> extends FaultTolerantStepBui
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will throw a {@link UnsupportedOperationException} since
|
||||
* the item writer of the manager step in a remote chunking setup will be
|
||||
* automatically set to an instance of {@link ChunkMessageChannelItemWriter}.
|
||||
*
|
||||
* When building a manager step for remote chunking, no item writer must be
|
||||
* provided.
|
||||
* This method will throw a {@link UnsupportedOperationException} since the item
|
||||
* writer of the manager step in a remote chunking setup will be automatically set to
|
||||
* an instance of {@link ChunkMessageChannelItemWriter}.
|
||||
*
|
||||
* When building a manager step for remote chunking, no item writer must be provided.
|
||||
* @throws UnsupportedOperationException if an item writer is provided
|
||||
* @see ChunkMessageChannelItemWriter
|
||||
* @see RemoteChunkHandlerFactoryBean#setChunkWriter(ItemWriter)
|
||||
*/
|
||||
@Override
|
||||
public RemoteChunkingManagerStepBuilder<I, O> writer(ItemWriter<? super O> writer) throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException("When configuring a manager step " +
|
||||
"for remote chunking, the item writer will be automatically set " +
|
||||
"to an instance of ChunkMessageChannelItemWriter. The item writer " +
|
||||
"must not be provided in this case.");
|
||||
public RemoteChunkingManagerStepBuilder<I, O> writer(ItemWriter<? super O> writer)
|
||||
throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException(
|
||||
"When configuring a manager step " + "for remote chunking, the item writer will be automatically set "
|
||||
+ "to an instance of ChunkMessageChannelItemWriter. The item writer "
|
||||
+ "must not be provided in this case.");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -417,4 +424,5 @@ public class RemoteChunkingManagerStepBuilder<I, O> extends FaultTolerantStepBui
|
||||
super.processor(itemProcessor);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
/**
|
||||
* Convenient factory for a {@link RemoteChunkingManagerStepBuilder} which sets
|
||||
* the {@link JobRepository} and {@link PlatformTransactionManager} automatically.
|
||||
* Convenient factory for a {@link RemoteChunkingManagerStepBuilder} which sets the
|
||||
* {@link JobRepository} and {@link PlatformTransactionManager} automatically.
|
||||
*
|
||||
* @since 4.2
|
||||
* @author Mahmoud Ben Hassine
|
||||
@@ -33,12 +33,10 @@ public class RemoteChunkingManagerStepBuilderFactory {
|
||||
|
||||
/**
|
||||
* Create a new {@link RemoteChunkingManagerStepBuilderFactory}.
|
||||
*
|
||||
* @param jobRepository the job repository to use
|
||||
* @param transactionManager the transaction manager to use
|
||||
*/
|
||||
public RemoteChunkingManagerStepBuilderFactory(
|
||||
JobRepository jobRepository,
|
||||
public RemoteChunkingManagerStepBuilderFactory(JobRepository jobRepository,
|
||||
PlatformTransactionManager transactionManager) {
|
||||
|
||||
this.jobRepository = jobRepository;
|
||||
@@ -48,15 +46,13 @@ public class RemoteChunkingManagerStepBuilderFactory {
|
||||
/**
|
||||
* Creates a {@link RemoteChunkingManagerStepBuilder} and initializes its job
|
||||
* repository and transaction manager.
|
||||
*
|
||||
* @param name the name of the step
|
||||
* @param <I> type of input items
|
||||
* @param <O> type of output items
|
||||
* @return a {@link RemoteChunkingManagerStepBuilder}
|
||||
*/
|
||||
public <I, O> RemoteChunkingManagerStepBuilder<I, O> get(String name) {
|
||||
return new RemoteChunkingManagerStepBuilder<I, O>(name)
|
||||
.repository(this.jobRepository)
|
||||
return new RemoteChunkingManagerStepBuilder<I, O>(name).repository(this.jobRepository)
|
||||
.transactionManager(this.transactionManager);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,18 +28,16 @@ import org.springframework.util.Assert;
|
||||
* Builder for a worker in a remote chunking setup. This builder:
|
||||
*
|
||||
* <ul>
|
||||
* <li>creates a {@link ChunkProcessorChunkHandler} with the provided
|
||||
* item processor and writer. If no item processor is provided, a
|
||||
* {@link PassThroughItemProcessor} will be used</li>
|
||||
* <li>creates an {@link IntegrationFlow} with the
|
||||
* {@link ChunkProcessorChunkHandler} as a service activator which listens
|
||||
* to incoming requests on <code>inputChannel</code> and sends replies
|
||||
* on <code>outputChannel</code></li>
|
||||
* <li>creates a {@link ChunkProcessorChunkHandler} with the provided item processor and
|
||||
* writer. If no item processor is provided, a {@link PassThroughItemProcessor} will be
|
||||
* used</li>
|
||||
* <li>creates an {@link IntegrationFlow} with the {@link ChunkProcessorChunkHandler} as a
|
||||
* service activator which listens to incoming requests on <code>inputChannel</code> and
|
||||
* sends replies on <code>outputChannel</code></li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <I> type of input items
|
||||
* @param <O> type of output items
|
||||
*
|
||||
* @since 4.1
|
||||
* @author Mahmoud Ben Hassine
|
||||
*/
|
||||
@@ -48,14 +46,15 @@ public class RemoteChunkingWorkerBuilder<I, O> {
|
||||
private static final String SERVICE_ACTIVATOR_METHOD_NAME = "handleChunk";
|
||||
|
||||
private ItemProcessor<I, O> itemProcessor;
|
||||
|
||||
private ItemWriter<O> itemWriter;
|
||||
|
||||
private MessageChannel inputChannel;
|
||||
|
||||
private MessageChannel outputChannel;
|
||||
|
||||
/**
|
||||
* Set the {@link ItemProcessor} to use to process items sent by the manager
|
||||
* step.
|
||||
*
|
||||
* Set the {@link ItemProcessor} to use to process items sent by the manager step.
|
||||
* @param itemProcessor to use
|
||||
* @return this builder instance for fluent chaining
|
||||
*/
|
||||
@@ -67,7 +66,6 @@ public class RemoteChunkingWorkerBuilder<I, O> {
|
||||
|
||||
/**
|
||||
* Set the {@link ItemWriter} to use to write items sent by the manager step.
|
||||
*
|
||||
* @param itemWriter to use
|
||||
* @return this builder instance for fluent chaining
|
||||
*/
|
||||
@@ -79,7 +77,6 @@ public class RemoteChunkingWorkerBuilder<I, O> {
|
||||
|
||||
/**
|
||||
* Set the input channel on which items sent by the manager are received.
|
||||
*
|
||||
* @param inputChannel the input channel
|
||||
* @return this builder instance for fluent chaining
|
||||
*/
|
||||
@@ -91,7 +88,6 @@ public class RemoteChunkingWorkerBuilder<I, O> {
|
||||
|
||||
/**
|
||||
* Set the output channel on which replies will be sent to the manager step.
|
||||
*
|
||||
* @param outputChannel the output channel
|
||||
* @return this builder instance for fluent chaining
|
||||
*/
|
||||
@@ -103,18 +99,17 @@ public class RemoteChunkingWorkerBuilder<I, O> {
|
||||
|
||||
/**
|
||||
* Create an {@link IntegrationFlow} with a {@link ChunkProcessorChunkHandler}
|
||||
* configured as a service activator listening to the input channel and replying
|
||||
* on the output channel.
|
||||
*
|
||||
* configured as a service activator listening to the input channel and replying on
|
||||
* the output channel.
|
||||
* @return the integration flow
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public IntegrationFlow build() {
|
||||
Assert.notNull(this.itemWriter, "An ItemWriter must be provided");
|
||||
Assert.notNull(this.inputChannel, "An InputChannel must be provided");
|
||||
Assert.notNull(this.outputChannel, "An OutputChannel must be provided");
|
||||
|
||||
if(this.itemProcessor == null) {
|
||||
if (this.itemProcessor == null) {
|
||||
this.itemProcessor = new PassThroughItemProcessor();
|
||||
}
|
||||
SimpleChunkProcessor<I, O> chunkProcessor = new SimpleChunkProcessor<>(this.itemProcessor, this.itemWriter);
|
||||
@@ -122,11 +117,8 @@ public class RemoteChunkingWorkerBuilder<I, O> {
|
||||
ChunkProcessorChunkHandler<I> chunkProcessorChunkHandler = new ChunkProcessorChunkHandler<>();
|
||||
chunkProcessorChunkHandler.setChunkProcessor(chunkProcessor);
|
||||
|
||||
return IntegrationFlows
|
||||
.from(this.inputChannel)
|
||||
.handle(chunkProcessorChunkHandler, SERVICE_ACTIVATOR_METHOD_NAME)
|
||||
.channel(this.outputChannel)
|
||||
.get();
|
||||
return IntegrationFlows.from(this.inputChannel)
|
||||
.handle(chunkProcessorChunkHandler, SERVICE_ACTIVATOR_METHOD_NAME).channel(this.outputChannel).get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,18 +22,17 @@ import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
|
||||
/**
|
||||
* A source of {@link StepContribution} instances that can be aggregated and used to update an ongoing
|
||||
* {@link StepExecution}.
|
||||
*
|
||||
* A source of {@link StepContribution} instances that can be aggregated and used to
|
||||
* update an ongoing {@link StepExecution}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public interface StepContributionSource {
|
||||
|
||||
/**
|
||||
* Get the currently available contributions and drain the source. The next call would return an empty collection,
|
||||
* unless new contributions have arrived.
|
||||
*
|
||||
* Get the currently available contributions and drain the source. The next call would
|
||||
* return an empty collection, unless new contributions have arrived.
|
||||
* @return a collection of {@link StepContribution} instances
|
||||
*/
|
||||
Collection<StepContribution> getStepContributions();
|
||||
|
||||
@@ -51,9 +51,7 @@ public class BatchIntegrationConfiguration implements InitializingBean {
|
||||
private RemotePartitioningWorkerStepBuilderFactory remotePartitioningWorkerStepBuilderFactory;
|
||||
|
||||
@Autowired
|
||||
public BatchIntegrationConfiguration(
|
||||
JobRepository jobRepository,
|
||||
JobExplorer jobExplorer,
|
||||
public BatchIntegrationConfiguration(JobRepository jobRepository, JobExplorer jobExplorer,
|
||||
PlatformTransactionManager transactionManager) {
|
||||
|
||||
this.jobRepository = jobRepository;
|
||||
@@ -67,7 +65,7 @@ public class BatchIntegrationConfiguration implements InitializingBean {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public <I,O> RemoteChunkingWorkerBuilder<I, O> remoteChunkingWorkerBuilder() {
|
||||
public <I, O> RemoteChunkingWorkerBuilder<I, O> remoteChunkingWorkerBuilder() {
|
||||
return remoteChunkingWorkerBuilder;
|
||||
}
|
||||
|
||||
@@ -86,9 +84,10 @@ public class BatchIntegrationConfiguration implements InitializingBean {
|
||||
this.remoteChunkingManagerStepBuilderFactory = new RemoteChunkingManagerStepBuilderFactory(this.jobRepository,
|
||||
this.transactionManager);
|
||||
this.remoteChunkingWorkerBuilder = new RemoteChunkingWorkerBuilder<>();
|
||||
this.remotePartitioningManagerStepBuilderFactory = new RemotePartitioningManagerStepBuilderFactory(this.jobRepository,
|
||||
this.jobExplorer, this.transactionManager);
|
||||
this.remotePartitioningWorkerStepBuilderFactory = new RemotePartitioningWorkerStepBuilderFactory(this.jobRepository,
|
||||
this.jobExplorer, this.transactionManager);
|
||||
this.remotePartitioningManagerStepBuilderFactory = new RemotePartitioningManagerStepBuilderFactory(
|
||||
this.jobRepository, this.jobExplorer, this.transactionManager);
|
||||
this.remotePartitioningWorkerStepBuilderFactory = new RemotePartitioningWorkerStepBuilderFactory(
|
||||
this.jobRepository, this.jobExplorer, this.transactionManager);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,24 +29,25 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.integration.config.EnableIntegration;
|
||||
|
||||
/**
|
||||
* Enable Spring Batch Integration features and provide a base configuration for
|
||||
* setting up remote chunking or partitioning infrastructure beans.
|
||||
* Enable Spring Batch Integration features and provide a base configuration for setting
|
||||
* up remote chunking or partitioning infrastructure beans.
|
||||
*
|
||||
* By adding this annotation on a {@link org.springframework.context.annotation.Configuration}
|
||||
* class, it will be possible to autowire the following beans:
|
||||
* By adding this annotation on a
|
||||
* {@link org.springframework.context.annotation.Configuration} class, it will be possible
|
||||
* to autowire the following beans:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link RemoteChunkingManagerStepBuilderFactory}:
|
||||
* used to create a manager step of a remote chunking setup by automatically
|
||||
* setting the job repository and transaction manager.</li>
|
||||
* <li>{@link RemoteChunkingWorkerBuilder}: used to create the integration
|
||||
* flow on the worker side of a remote chunking setup.</li>
|
||||
* <li>{@link RemotePartitioningManagerStepBuilderFactory}: used to create
|
||||
* a manager step of a remote partitioning setup by automatically setting
|
||||
* the job repository, job explorer, bean factory and transaction manager.</li>
|
||||
* <li>{@link RemotePartitioningWorkerStepBuilderFactory}: used to create
|
||||
* a worker step of a remote partitioning setup by automatically setting
|
||||
* the job repository, job explorer, bean factory and transaction manager.</li>
|
||||
* <li>{@link RemoteChunkingManagerStepBuilderFactory}: used to create a manager step of a
|
||||
* remote chunking setup by automatically setting the job repository and transaction
|
||||
* manager.</li>
|
||||
* <li>{@link RemoteChunkingWorkerBuilder}: used to create the integration flow on the
|
||||
* worker side of a remote chunking setup.</li>
|
||||
* <li>{@link RemotePartitioningManagerStepBuilderFactory}: used to create a manager step
|
||||
* of a remote partitioning setup by automatically setting the job repository, job
|
||||
* explorer, bean factory and transaction manager.</li>
|
||||
* <li>{@link RemotePartitioningWorkerStepBuilderFactory}: used to create a worker step of
|
||||
* a remote partitioning setup by automatically setting the job repository, job explorer,
|
||||
* bean factory and transaction manager.</li>
|
||||
* </ul>
|
||||
*
|
||||
* For remote chunking, an example of a configuration class would be:
|
||||
@@ -140,4 +141,5 @@ import org.springframework.integration.config.EnableIntegration;
|
||||
@EnableIntegration
|
||||
@Import(BatchIntegrationConfiguration.class)
|
||||
public @interface EnableBatchIntegration {
|
||||
|
||||
}
|
||||
|
||||
@@ -26,14 +26,18 @@ import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHa
|
||||
* @since 1.3
|
||||
*/
|
||||
public class BatchIntegrationNamespaceHandler extends AbstractIntegrationNamespaceHandler {
|
||||
/* (non-Javadoc)
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.beans.factory.xml.NamespaceHandler#init()
|
||||
*/
|
||||
public void init() {
|
||||
this.registerBeanDefinitionParser("job-launching-gateway", new JobLaunchingGatewayParser());
|
||||
this.registerBeanDefinitionParser("job-launching-gateway", new JobLaunchingGatewayParser());
|
||||
RemoteChunkingManagerParser remoteChunkingManagerParser = new RemoteChunkingManagerParser();
|
||||
this.registerBeanDefinitionParser("remote-chunking-manager", remoteChunkingManagerParser);
|
||||
RemoteChunkingWorkerParser remoteChunkingWorkerParser = new RemoteChunkingWorkerParser();
|
||||
this.registerBeanDefinitionParser("remote-chunking-worker", remoteChunkingWorkerParser);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,9 +28,8 @@ import org.w3c.dom.Element;
|
||||
|
||||
/**
|
||||
* The parser for the Job-Launching Gateway, which will instantiate a
|
||||
* {@link JobLaunchingGatewayParser}. If no {@link JobLauncher} reference has
|
||||
* been provided, this parse will use the use the globally registered bean
|
||||
* 'jobLauncher'.
|
||||
* {@link JobLaunchingGatewayParser}. If no {@link JobLauncher} reference has been
|
||||
* provided, this parse will use the use the globally registered bean 'jobLauncher'.
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
* @since 1.3
|
||||
@@ -48,8 +47,8 @@ public class JobLaunchingGatewayParser extends AbstractConsumerEndpointParser {
|
||||
@Override
|
||||
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
|
||||
|
||||
final BeanDefinitionBuilder jobLaunchingGatewayBuilder =
|
||||
BeanDefinitionBuilder.genericBeanDefinition(JobLaunchingGateway.class);
|
||||
final BeanDefinitionBuilder jobLaunchingGatewayBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(JobLaunchingGateway.class);
|
||||
|
||||
final String jobLauncher = element.getAttribute("job-launcher");
|
||||
|
||||
@@ -63,7 +62,8 @@ public class JobLaunchingGatewayParser extends AbstractConsumerEndpointParser {
|
||||
jobLaunchingGatewayBuilder.addConstructorArgReference("jobLauncher");
|
||||
}
|
||||
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(jobLaunchingGatewayBuilder, element, "reply-timeout", "sendTimeout");
|
||||
IntegrationNamespaceUtils.setValueIfAttributeDefined(jobLaunchingGatewayBuilder, element, "reply-timeout",
|
||||
"sendTimeout");
|
||||
|
||||
final String replyChannel = element.getAttribute("reply-channel");
|
||||
|
||||
|
||||
@@ -36,13 +36,21 @@ import org.w3c.dom.Element;
|
||||
* @since 3.1
|
||||
*/
|
||||
public class RemoteChunkingManagerParser extends AbstractBeanDefinitionParser {
|
||||
|
||||
private static final String MESSAGE_TEMPLATE_ATTRIBUTE = "message-template";
|
||||
|
||||
private static final String STEP_ATTRIBUTE = "step";
|
||||
|
||||
private static final String REPLY_CHANNEL_ATTRIBUTE = "reply-channel";
|
||||
|
||||
private static final String MESSAGING_OPERATIONS_PROPERTY = "messagingOperations";
|
||||
|
||||
private static final String REPLY_CHANNEL_PROPERTY = "replyChannel";
|
||||
|
||||
private static final String CHUNK_WRITER_PROPERTY = "chunkWriter";
|
||||
|
||||
private static final String STEP_PROPERTY = "step";
|
||||
|
||||
private static final String CHUNK_HANDLER_BEAN_NAME_PREFIX = "remoteChunkHandlerFactoryBean_";
|
||||
|
||||
@Override
|
||||
@@ -61,24 +69,22 @@ public class RemoteChunkingManagerParser extends AbstractBeanDefinitionParser {
|
||||
|
||||
BeanDefinitionRegistry beanDefinitionRegistry = parserContext.getRegistry();
|
||||
|
||||
BeanDefinition chunkMessageChannelItemWriter =
|
||||
BeanDefinitionBuilder
|
||||
.genericBeanDefinition(ChunkMessageChannelItemWriter.class)
|
||||
.addPropertyReference(MESSAGING_OPERATIONS_PROPERTY, messageTemplate)
|
||||
.addPropertyReference(REPLY_CHANNEL_PROPERTY, replyChannel)
|
||||
.getBeanDefinition();
|
||||
BeanDefinition chunkMessageChannelItemWriter = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(ChunkMessageChannelItemWriter.class)
|
||||
.addPropertyReference(MESSAGING_OPERATIONS_PROPERTY, messageTemplate)
|
||||
.addPropertyReference(REPLY_CHANNEL_PROPERTY, replyChannel).getBeanDefinition();
|
||||
|
||||
beanDefinitionRegistry.registerBeanDefinition(id, chunkMessageChannelItemWriter);
|
||||
|
||||
BeanDefinition remoteChunkHandlerFactoryBean =
|
||||
BeanDefinitionBuilder
|
||||
.genericBeanDefinition(RemoteChunkHandlerFactoryBean.class)
|
||||
.addPropertyValue(CHUNK_WRITER_PROPERTY, chunkMessageChannelItemWriter)
|
||||
.addPropertyValue(STEP_PROPERTY, step)
|
||||
.getBeanDefinition();
|
||||
BeanDefinition remoteChunkHandlerFactoryBean = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(RemoteChunkHandlerFactoryBean.class)
|
||||
.addPropertyValue(CHUNK_WRITER_PROPERTY, chunkMessageChannelItemWriter)
|
||||
.addPropertyValue(STEP_PROPERTY, step).getBeanDefinition();
|
||||
|
||||
beanDefinitionRegistry.registerBeanDefinition(CHUNK_HANDLER_BEAN_NAME_PREFIX + step, remoteChunkHandlerFactoryBean);
|
||||
beanDefinitionRegistry.registerBeanDefinition(CHUNK_HANDLER_BEAN_NAME_PREFIX + step,
|
||||
remoteChunkHandlerFactoryBean);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,13 +45,21 @@ import org.springframework.util.StringUtils;
|
||||
* @since 3.1
|
||||
*/
|
||||
public class RemoteChunkingWorkerParser extends AbstractBeanDefinitionParser {
|
||||
|
||||
private static final String INPUT_CHANNEL_ATTRIBUTE = "input-channel";
|
||||
|
||||
private static final String OUTPUT_CHANNEL_ATTRIBUTE = "output-channel";
|
||||
|
||||
private static final String ITEM_PROCESSOR_ATTRIBUTE = "item-processor";
|
||||
|
||||
private static final String ITEM_WRITER_ATTRIBUTE = "item-writer";
|
||||
|
||||
private static final String ITEM_PROCESSOR_PROPERTY_NAME = "itemProcessor";
|
||||
|
||||
private static final String ITEM_WRITER_PROPERTY_NAME = "itemWriter";
|
||||
|
||||
private static final String CHUNK_PROCESSOR_PROPERTY_NAME = "chunkProcessor";
|
||||
|
||||
private static final String CHUNK_PROCESSOR_CHUNK_HANDLER_BEAN_NAME_PREFIX = "chunkProcessorChunkHandler_";
|
||||
|
||||
@Override
|
||||
@@ -72,24 +80,24 @@ public class RemoteChunkingWorkerParser extends AbstractBeanDefinitionParser {
|
||||
|
||||
BeanDefinitionRegistry beanDefinitionRegistry = parserContext.getRegistry();
|
||||
|
||||
BeanDefinitionBuilder chunkProcessorBuilder =
|
||||
BeanDefinitionBuilder
|
||||
.genericBeanDefinition(SimpleChunkProcessor.class)
|
||||
.addPropertyReference(ITEM_WRITER_PROPERTY_NAME, itemWriter);
|
||||
BeanDefinitionBuilder chunkProcessorBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(SimpleChunkProcessor.class)
|
||||
.addPropertyReference(ITEM_WRITER_PROPERTY_NAME, itemWriter);
|
||||
|
||||
if(StringUtils.hasText(itemProcessor)) {
|
||||
if (StringUtils.hasText(itemProcessor)) {
|
||||
chunkProcessorBuilder.addPropertyReference(ITEM_PROCESSOR_PROPERTY_NAME, itemProcessor);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
chunkProcessorBuilder.addPropertyValue(ITEM_PROCESSOR_PROPERTY_NAME, new PassThroughItemProcessor<>());
|
||||
}
|
||||
|
||||
BeanDefinition chunkProcessorChunkHandler =
|
||||
BeanDefinitionBuilder
|
||||
.genericBeanDefinition(ChunkProcessorChunkHandler.class)
|
||||
.addPropertyValue(CHUNK_PROCESSOR_PROPERTY_NAME, chunkProcessorBuilder.getBeanDefinition())
|
||||
.getBeanDefinition();
|
||||
BeanDefinition chunkProcessorChunkHandler = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(ChunkProcessorChunkHandler.class)
|
||||
.addPropertyValue(CHUNK_PROCESSOR_PROPERTY_NAME, chunkProcessorBuilder.getBeanDefinition())
|
||||
.getBeanDefinition();
|
||||
|
||||
beanDefinitionRegistry.registerBeanDefinition(CHUNK_PROCESSOR_CHUNK_HANDLER_BEAN_NAME_PREFIX + id, chunkProcessorChunkHandler);
|
||||
beanDefinitionRegistry.registerBeanDefinition(CHUNK_PROCESSOR_CHUNK_HANDLER_BEAN_NAME_PREFIX + id,
|
||||
chunkProcessorChunkHandler);
|
||||
|
||||
new ServiceActivatorParser(id).parse(element, parserContext);
|
||||
|
||||
@@ -97,9 +105,13 @@ public class RemoteChunkingWorkerParser extends AbstractBeanDefinitionParser {
|
||||
}
|
||||
|
||||
private static class ServiceActivatorParser extends AbstractConsumerEndpointParser {
|
||||
|
||||
private static final String TARGET_METHOD_NAME_PROPERTY_NAME = "targetMethodName";
|
||||
|
||||
private static final String TARGET_OBJECT_PROPERTY_NAME = "targetObject";
|
||||
|
||||
private static final String HANDLE_CHUNK_METHOD_NAME = "handleChunk";
|
||||
|
||||
private static final String CHUNK_PROCESSOR_CHUNK_HANDLER_BEAN_NAME_PREFIX = "chunkProcessorChunkHandler_";
|
||||
|
||||
private String id;
|
||||
@@ -110,10 +122,14 @@ public class RemoteChunkingWorkerParser extends AbstractBeanDefinitionParser {
|
||||
|
||||
@Override
|
||||
protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(ServiceActivatorFactoryBean.class);
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(ServiceActivatorFactoryBean.class);
|
||||
builder.addPropertyValue(TARGET_METHOD_NAME_PROPERTY_NAME, HANDLE_CHUNK_METHOD_NAME);
|
||||
builder.addPropertyValue(TARGET_OBJECT_PROPERTY_NAME, new RuntimeBeanReference(CHUNK_PROCESSOR_CHUNK_HANDLER_BEAN_NAME_PREFIX + id));
|
||||
builder.addPropertyValue(TARGET_OBJECT_PROPERTY_NAME,
|
||||
new RuntimeBeanReference(CHUNK_PROCESSOR_CHUNK_HANDLER_BEAN_NAME_PREFIX + id));
|
||||
return builder;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,10 +19,11 @@ 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.
|
||||
*
|
||||
* Encapsulation of a {@link Job} and its {@link JobParameters} forming a request for a
|
||||
* job to be launched.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class JobLaunchRequest {
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.springframework.batch.core.JobExecutionException;
|
||||
|
||||
/**
|
||||
* Interface for handling a {@link JobLaunchRequest} and returning a {@link JobExecution}.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -25,11 +25,10 @@ import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The {@link JobLaunchingGateway} is used to launch Batch Jobs. Internally it
|
||||
* delegates to a {@link JobLaunchingMessageHandler}.
|
||||
* The {@link JobLaunchingGateway} is used to launch Batch Jobs. Internally it delegates
|
||||
* to a {@link JobLaunchingMessageHandler}.
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
*
|
||||
* @since 1.3
|
||||
*/
|
||||
public class JobLaunchingGateway extends AbstractReplyProducingMessageHandler {
|
||||
@@ -38,7 +37,6 @@ public class JobLaunchingGateway extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
/**
|
||||
* Constructor taking a {@link JobLauncher} as parameter.
|
||||
*
|
||||
* @param jobLauncher Must not be null.
|
||||
*
|
||||
*/
|
||||
@@ -48,15 +46,12 @@ public class JobLaunchingGateway extends AbstractReplyProducingMessageHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches a Batch Job using the provided request {@link Message}. The payload
|
||||
* of the {@link Message} <em>must</em> be an instance of {@link JobLaunchRequest}.
|
||||
*
|
||||
* Launches a Batch Job using the provided request {@link Message}. The payload of the
|
||||
* {@link Message} <em>must</em> be an instance of {@link JobLaunchRequest}.
|
||||
* @param requestMessage must not be null.
|
||||
* @return Generally a {@link JobExecution} will always be returned. An
|
||||
* exception ({@link MessageHandlingException}) will only be thrown if there
|
||||
* is a failure to start the job. The cause of the exception will be a
|
||||
* {@link JobExecutionException}.
|
||||
*
|
||||
* @return Generally a {@link JobExecution} will always be returned. An exception
|
||||
* ({@link MessageHandlingException}) will only be thrown if there is a failure to
|
||||
* start the job. The cause of the exception will be a {@link JobExecutionException}.
|
||||
* @throws MessageHandlingException when a job cannot be launched
|
||||
*/
|
||||
@Override
|
||||
@@ -74,7 +69,8 @@ public class JobLaunchingGateway extends AbstractReplyProducingMessageHandler {
|
||||
|
||||
try {
|
||||
jobExecution = this.jobLaunchingMessageHandler.launch(jobLaunchRequest);
|
||||
} catch (JobExecutionException e) {
|
||||
}
|
||||
catch (JobExecutionException e) {
|
||||
throw new MessageHandlingException(requestMessage, e);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,9 @@ 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
|
||||
* Message handler which uses strategies to convert a Message into a job and a set of job
|
||||
* parameters
|
||||
*
|
||||
* @author Jonas Partner
|
||||
* @author Dave Syer
|
||||
* @author Gunnar Hillert
|
||||
@@ -35,7 +37,8 @@ public class JobLaunchingMessageHandler implements JobLaunchRequestHandler {
|
||||
private final JobLauncher jobLauncher;
|
||||
|
||||
/**
|
||||
* @param jobLauncher {@link org.springframework.batch.core.launch.JobLauncher} used to execute Spring Batch jobs
|
||||
* @param jobLauncher {@link org.springframework.batch.core.launch.JobLauncher} used
|
||||
* to execute Spring Batch jobs
|
||||
*/
|
||||
public JobLaunchingMessageHandler(JobLauncher jobLauncher) {
|
||||
super();
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
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
|
||||
* @author Mahmoud Ben Hassine
|
||||
*
|
||||
*/
|
||||
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 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));
|
||||
}
|
||||
|
||||
}
|
||||
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
|
||||
* @author Mahmoud Ben Hassine
|
||||
*
|
||||
*/
|
||||
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 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,319 +1,335 @@
|
||||
/*
|
||||
* Copyright 2009-2021 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
|
||||
*
|
||||
* https://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.partition;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.explore.support.JobExplorerFactoryBean;
|
||||
import org.springframework.batch.core.partition.PartitionHandler;
|
||||
import org.springframework.batch.core.partition.StepExecutionSplitter;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.poller.DirectPoller;
|
||||
import org.springframework.batch.poller.Poller;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.integration.MessageTimeoutException;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.Payloads;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* 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, 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.
|
||||
*
|
||||
* While a {@link org.springframework.messaging.MessageChannel} is used for sending the requests to the workers, the
|
||||
* worker's responses can be obtained in one of two ways:
|
||||
* <ul>
|
||||
* <li>A reply channel - Workers will respond with messages that will be aggregated via this component.</li>
|
||||
* <li>Polling the job repository - Since the state of each worker is maintained independently within the job
|
||||
* repository, we can poll the store to determine the state without the need of the workers to formally respond.</li>
|
||||
* </ul>
|
||||
*
|
||||
* Note: The reply channel for this is instance based. Sharing this component across
|
||||
* multiple step instances may result in the crossing of messages. It's recommended that
|
||||
* this component be step or job scoped.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Will Schipp
|
||||
* @author Michael Minella
|
||||
* @author Mahmoud Ben Hassine
|
||||
*
|
||||
*/
|
||||
@MessageEndpoint
|
||||
public class MessageChannelPartitionHandler implements PartitionHandler, InitializingBean {
|
||||
|
||||
private static Log logger = LogFactory.getLog(MessageChannelPartitionHandler.class);
|
||||
|
||||
private int gridSize = 1;
|
||||
|
||||
private MessagingTemplate messagingGateway;
|
||||
|
||||
private String stepName;
|
||||
|
||||
private long pollInterval = 10000;
|
||||
|
||||
private JobExplorer jobExplorer;
|
||||
|
||||
private boolean pollRepositoryForResults = false;
|
||||
|
||||
private long timeout = -1;
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
/**
|
||||
* pollable channel for the replies
|
||||
*/
|
||||
private PollableChannel replyChannel;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(stepName, "A step name must be provided for the remote workers.");
|
||||
Assert.state(messagingGateway != null, "The MessagingOperations must be set");
|
||||
|
||||
pollRepositoryForResults = !(dataSource == null && jobExplorer == null);
|
||||
|
||||
if(pollRepositoryForResults) {
|
||||
logger.debug("MessageChannelPartitionHandler is configured to poll the job repository for worker results");
|
||||
}
|
||||
|
||||
if(dataSource != null && jobExplorer == null) {
|
||||
JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
|
||||
jobExplorerFactoryBean.setDataSource(dataSource);
|
||||
jobExplorerFactoryBean.afterPropertiesSet();
|
||||
jobExplorer = jobExplorerFactoryBean.getObject();
|
||||
}
|
||||
|
||||
if (!pollRepositoryForResults && replyChannel == null) {
|
||||
replyChannel = new QueueChannel();
|
||||
}//end if
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* When using job repository polling, the time limit to wait.
|
||||
*
|
||||
* @param timeout milliseconds to wait, defaults to -1 (no timeout).
|
||||
*/
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.batch.core.explore.JobExplorer} to use to query the job repository. Either this or
|
||||
* a {@link javax.sql.DataSource} is required when using job repository polling.
|
||||
*
|
||||
* @param jobExplorer {@link org.springframework.batch.core.explore.JobExplorer} to use for lookups
|
||||
*/
|
||||
public void setJobExplorer(JobExplorer jobExplorer) {
|
||||
this.jobExplorer = jobExplorer;
|
||||
}
|
||||
|
||||
/**
|
||||
* How often to poll the job repository for the status of the workers.
|
||||
*
|
||||
* @param pollInterval milliseconds between polls, defaults to 10000 (10 seconds).
|
||||
*/
|
||||
public void setPollInterval(long pollInterval) {
|
||||
this.pollInterval = pollInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link javax.sql.DataSource} pointing to the job repository
|
||||
*
|
||||
* @param dataSource {@link javax.sql.DataSource} that points to the job repository's store
|
||||
*/
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 reply should be set
|
||||
* sufficiently long that the remote steps have time to complete.
|
||||
*
|
||||
* @param messagingGateway the {@link org.springframework.integration.core.MessagingTemplate} to set
|
||||
*/
|
||||
public void setMessagingOperations(MessagingTemplate 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(sendPartialResultsOnExpiry = "true")
|
||||
public List<?> aggregate(@Payloads List<?> messages) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void setReplyChannel(PollableChannel replyChannel) {
|
||||
this.replyChannel = replyChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends {@link StepExecutionRequest} objects to the request channel of the {@link MessagingTemplate}, 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 MessagingTemplate} <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,
|
||||
final StepExecution managerStepExecution) throws Exception {
|
||||
|
||||
final Set<StepExecution> split = stepExecutionSplitter.split(managerStepExecution, gridSize);
|
||||
|
||||
if(CollectionUtils.isEmpty(split)) {
|
||||
return split;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (StepExecution stepExecution : split) {
|
||||
Message<StepExecutionRequest> request = createMessage(count++, split.size(), new StepExecutionRequest(
|
||||
stepName, stepExecution.getJobExecutionId(), stepExecution.getId()), replyChannel);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Sending request: " + request);
|
||||
}
|
||||
messagingGateway.send(request);
|
||||
}
|
||||
|
||||
if(!pollRepositoryForResults) {
|
||||
return receiveReplies(replyChannel);
|
||||
}
|
||||
else {
|
||||
return pollReplies(managerStepExecution, split);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<StepExecution> pollReplies(final StepExecution managerStepExecution, final Set<StepExecution> split) throws Exception {
|
||||
final Collection<StepExecution> result = new ArrayList<>(split.size());
|
||||
|
||||
Callable<Collection<StepExecution>> callback = new Callable<Collection<StepExecution>>() {
|
||||
@Override
|
||||
public Collection<StepExecution> call() throws Exception {
|
||||
|
||||
for(Iterator<StepExecution> stepExecutionIterator = split.iterator(); stepExecutionIterator.hasNext(); ) {
|
||||
StepExecution curStepExecution = stepExecutionIterator.next();
|
||||
|
||||
if(!result.contains(curStepExecution)) {
|
||||
StepExecution partitionStepExecution =
|
||||
jobExplorer.getStepExecution(managerStepExecution.getJobExecutionId(), curStepExecution.getId());
|
||||
|
||||
if(!partitionStepExecution.getStatus().isRunning()) {
|
||||
result.add(partitionStepExecution);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Currently waiting on %s partitions to finish", split.size()));
|
||||
}
|
||||
|
||||
if(result.size() == split.size()) {
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Poller<Collection<StepExecution>> poller = new DirectPoller<>(pollInterval);
|
||||
Future<Collection<StepExecution>> resultsFuture = poller.poll(callback);
|
||||
|
||||
if(timeout >= 0) {
|
||||
return resultsFuture.get(timeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
else {
|
||||
return resultsFuture.get();
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<StepExecution> receiveReplies(PollableChannel currentReplyChannel) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<Collection<StepExecution>> message = (Message<Collection<StepExecution>>) messagingGateway.receive(currentReplyChannel);
|
||||
|
||||
if(message == null) {
|
||||
throw new MessageTimeoutException("Timeout occurred before all partitions returned");
|
||||
} else if (logger.isDebugEnabled()) {
|
||||
logger.debug("Received replies: " + message);
|
||||
}
|
||||
|
||||
return message.getPayload();
|
||||
}
|
||||
|
||||
private Message<StepExecutionRequest> createMessage(int sequenceNumber, int sequenceSize,
|
||||
StepExecutionRequest stepExecutionRequest, PollableChannel replyChannel) {
|
||||
return MessageBuilder.withPayload(stepExecutionRequest).setSequenceNumber(sequenceNumber)
|
||||
.setSequenceSize(sequenceSize)
|
||||
.setCorrelationId(stepExecutionRequest.getJobExecutionId() + ":" + stepExecutionRequest.getStepName())
|
||||
.setReplyChannel(replyChannel)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright 2009-2021 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
|
||||
*
|
||||
* https://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.partition;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.explore.JobExplorer;
|
||||
import org.springframework.batch.core.explore.support.JobExplorerFactoryBean;
|
||||
import org.springframework.batch.core.partition.PartitionHandler;
|
||||
import org.springframework.batch.core.partition.StepExecutionSplitter;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.poller.DirectPoller;
|
||||
import org.springframework.batch.poller.Poller;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.integration.MessageTimeoutException;
|
||||
import org.springframework.integration.annotation.Aggregator;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.Payloads;
|
||||
import org.springframework.integration.channel.QueueChannel;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* 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, 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.
|
||||
*
|
||||
* While a {@link org.springframework.messaging.MessageChannel} is used for sending the
|
||||
* requests to the workers, the worker's responses can be obtained in one of two ways:
|
||||
* <ul>
|
||||
* <li>A reply channel - Workers will respond with messages that will be aggregated via
|
||||
* this component.</li>
|
||||
* <li>Polling the job repository - Since the state of each worker is maintained
|
||||
* independently within the job repository, we can poll the store to determine the state
|
||||
* without the need of the workers to formally respond.</li>
|
||||
* </ul>
|
||||
*
|
||||
* Note: The reply channel for this is instance based. Sharing this component across
|
||||
* multiple step instances may result in the crossing of messages. It's recommended that
|
||||
* this component be step or job scoped.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Will Schipp
|
||||
* @author Michael Minella
|
||||
* @author Mahmoud Ben Hassine
|
||||
*
|
||||
*/
|
||||
@MessageEndpoint
|
||||
public class MessageChannelPartitionHandler implements PartitionHandler, InitializingBean {
|
||||
|
||||
private static Log logger = LogFactory.getLog(MessageChannelPartitionHandler.class);
|
||||
|
||||
private int gridSize = 1;
|
||||
|
||||
private MessagingTemplate messagingGateway;
|
||||
|
||||
private String stepName;
|
||||
|
||||
private long pollInterval = 10000;
|
||||
|
||||
private JobExplorer jobExplorer;
|
||||
|
||||
private boolean pollRepositoryForResults = false;
|
||||
|
||||
private long timeout = -1;
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
/**
|
||||
* pollable channel for the replies
|
||||
*/
|
||||
private PollableChannel replyChannel;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(stepName, "A step name must be provided for the remote workers.");
|
||||
Assert.state(messagingGateway != null, "The MessagingOperations must be set");
|
||||
|
||||
pollRepositoryForResults = !(dataSource == null && jobExplorer == null);
|
||||
|
||||
if (pollRepositoryForResults) {
|
||||
logger.debug("MessageChannelPartitionHandler is configured to poll the job repository for worker results");
|
||||
}
|
||||
|
||||
if (dataSource != null && jobExplorer == null) {
|
||||
JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
|
||||
jobExplorerFactoryBean.setDataSource(dataSource);
|
||||
jobExplorerFactoryBean.afterPropertiesSet();
|
||||
jobExplorer = jobExplorerFactoryBean.getObject();
|
||||
}
|
||||
|
||||
if (!pollRepositoryForResults && replyChannel == null) {
|
||||
replyChannel = new QueueChannel();
|
||||
} // end if
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* When using job repository polling, the time limit to wait.
|
||||
* @param timeout milliseconds to wait, defaults to -1 (no timeout).
|
||||
*/
|
||||
public void setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.batch.core.explore.JobExplorer} to use to query the job
|
||||
* repository. Either this or a {@link javax.sql.DataSource} is required when using
|
||||
* job repository polling.
|
||||
* @param jobExplorer {@link org.springframework.batch.core.explore.JobExplorer} to
|
||||
* use for lookups
|
||||
*/
|
||||
public void setJobExplorer(JobExplorer jobExplorer) {
|
||||
this.jobExplorer = jobExplorer;
|
||||
}
|
||||
|
||||
/**
|
||||
* How often to poll the job repository for the status of the workers.
|
||||
* @param pollInterval milliseconds between polls, defaults to 10000 (10 seconds).
|
||||
*/
|
||||
public void setPollInterval(long pollInterval) {
|
||||
this.pollInterval = pollInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link javax.sql.DataSource} pointing to the job repository
|
||||
* @param dataSource {@link javax.sql.DataSource} that points to the job repository's
|
||||
* store
|
||||
*/
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 reply should be set sufficiently long that the remote steps
|
||||
* have time to complete.
|
||||
* @param messagingGateway the
|
||||
* {@link org.springframework.integration.core.MessagingTemplate} to set
|
||||
*/
|
||||
public void setMessagingOperations(MessagingTemplate 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(sendPartialResultsOnExpiry = "true")
|
||||
public List<?> aggregate(@Payloads List<?> messages) {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void setReplyChannel(PollableChannel replyChannel) {
|
||||
this.replyChannel = replyChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends {@link StepExecutionRequest} objects to the request channel of the
|
||||
* {@link MessagingTemplate}, 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 MessagingTemplate} <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,
|
||||
final StepExecution managerStepExecution) throws Exception {
|
||||
|
||||
final Set<StepExecution> split = stepExecutionSplitter.split(managerStepExecution, gridSize);
|
||||
|
||||
if (CollectionUtils.isEmpty(split)) {
|
||||
return split;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
for (StepExecution stepExecution : split) {
|
||||
Message<StepExecutionRequest> request = createMessage(count++, split.size(),
|
||||
new StepExecutionRequest(stepName, stepExecution.getJobExecutionId(), stepExecution.getId()),
|
||||
replyChannel);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Sending request: " + request);
|
||||
}
|
||||
messagingGateway.send(request);
|
||||
}
|
||||
|
||||
if (!pollRepositoryForResults) {
|
||||
return receiveReplies(replyChannel);
|
||||
}
|
||||
else {
|
||||
return pollReplies(managerStepExecution, split);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<StepExecution> pollReplies(final StepExecution managerStepExecution,
|
||||
final Set<StepExecution> split) throws Exception {
|
||||
final Collection<StepExecution> result = new ArrayList<>(split.size());
|
||||
|
||||
Callable<Collection<StepExecution>> callback = new Callable<Collection<StepExecution>>() {
|
||||
@Override
|
||||
public Collection<StepExecution> call() throws Exception {
|
||||
|
||||
for (Iterator<StepExecution> stepExecutionIterator = split.iterator(); stepExecutionIterator
|
||||
.hasNext();) {
|
||||
StepExecution curStepExecution = stepExecutionIterator.next();
|
||||
|
||||
if (!result.contains(curStepExecution)) {
|
||||
StepExecution partitionStepExecution = jobExplorer
|
||||
.getStepExecution(managerStepExecution.getJobExecutionId(), curStepExecution.getId());
|
||||
|
||||
if (!partitionStepExecution.getStatus().isRunning()) {
|
||||
result.add(partitionStepExecution);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Currently waiting on %s partitions to finish", split.size()));
|
||||
}
|
||||
|
||||
if (result.size() == split.size()) {
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Poller<Collection<StepExecution>> poller = new DirectPoller<>(pollInterval);
|
||||
Future<Collection<StepExecution>> resultsFuture = poller.poll(callback);
|
||||
|
||||
if (timeout >= 0) {
|
||||
return resultsFuture.get(timeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
else {
|
||||
return resultsFuture.get();
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<StepExecution> receiveReplies(PollableChannel currentReplyChannel) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<Collection<StepExecution>> message = (Message<Collection<StepExecution>>) messagingGateway
|
||||
.receive(currentReplyChannel);
|
||||
|
||||
if (message == null) {
|
||||
throw new MessageTimeoutException("Timeout occurred before all partitions returned");
|
||||
}
|
||||
else if (logger.isDebugEnabled()) {
|
||||
logger.debug("Received replies: " + message);
|
||||
}
|
||||
|
||||
return message.getPayload();
|
||||
}
|
||||
|
||||
private Message<StepExecutionRequest> createMessage(int sequenceNumber, int sequenceSize,
|
||||
StepExecutionRequest stepExecutionRequest, PollableChannel replyChannel) {
|
||||
return MessageBuilder.withPayload(stepExecutionRequest).setSequenceNumber(sequenceNumber)
|
||||
.setSequenceSize(sequenceSize)
|
||||
.setCorrelationId(stepExecutionRequest.getJobExecutionId() + ":" + stepExecutionRequest.getStepName())
|
||||
.setReplyChannel(replyChannel).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,14 +42,19 @@ import org.springframework.util.Assert;
|
||||
* Builder for a manager step in a remote partitioning setup. This builder creates and
|
||||
* sets a {@link MessageChannelPartitionHandler} on the manager step.
|
||||
*
|
||||
* <p>If no {@code messagingTemplate} is provided through
|
||||
* {@link RemotePartitioningManagerStepBuilder#messagingTemplate(MessagingTemplate)},
|
||||
* this builder will create one and set its default channel to the {@code outputChannel}
|
||||
* provided through {@link RemotePartitioningManagerStepBuilder#outputChannel(MessageChannel)}.</p>
|
||||
* <p>
|
||||
* If no {@code messagingTemplate} is provided through
|
||||
* {@link RemotePartitioningManagerStepBuilder#messagingTemplate(MessagingTemplate)}, this
|
||||
* builder will create one and set its default channel to the {@code outputChannel}
|
||||
* provided through
|
||||
* {@link RemotePartitioningManagerStepBuilder#outputChannel(MessageChannel)}.
|
||||
* </p>
|
||||
*
|
||||
* <p>If a {@code messagingTemplate} is provided, it is assumed that it is fully configured
|
||||
* <p>
|
||||
* If a {@code messagingTemplate} is provided, it is assumed that it is fully configured
|
||||
* and that its default channel is set to an output channel on which requests to workers
|
||||
* will be sent.</p>
|
||||
* will be sent.
|
||||
* </p>
|
||||
*
|
||||
* @since 4.2
|
||||
* @author Mahmoud Ben Hassine
|
||||
@@ -57,14 +62,21 @@ import org.springframework.util.Assert;
|
||||
public class RemotePartitioningManagerStepBuilder extends PartitionStepBuilder {
|
||||
|
||||
private static final long DEFAULT_POLL_INTERVAL = 10000L;
|
||||
|
||||
private static final long DEFAULT_TIMEOUT = -1L;
|
||||
|
||||
private MessagingTemplate messagingTemplate;
|
||||
|
||||
private MessageChannel inputChannel;
|
||||
|
||||
private MessageChannel outputChannel;
|
||||
|
||||
private JobExplorer jobExplorer;
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private long pollInterval = DEFAULT_POLL_INTERVAL;
|
||||
|
||||
private long timeout = DEFAULT_TIMEOUT;
|
||||
|
||||
/**
|
||||
@@ -87,12 +99,14 @@ public class RemotePartitioningManagerStepBuilder extends PartitionStepBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the output channel on which requests to workers will be sent. By using
|
||||
* this setter, a default messaging template will be created and the output
|
||||
* channel will be set as its default channel.
|
||||
* <p>Use either this setter or {@link RemotePartitioningManagerStepBuilder#messagingTemplate(MessagingTemplate)}
|
||||
* to provide a fully configured messaging template.</p>
|
||||
*
|
||||
* Set the output channel on which requests to workers will be sent. By using this
|
||||
* setter, a default messaging template will be created and the output channel will be
|
||||
* set as its default channel.
|
||||
* <p>
|
||||
* Use either this setter or
|
||||
* {@link RemotePartitioningManagerStepBuilder#messagingTemplate(MessagingTemplate)}
|
||||
* to provide a fully configured messaging template.
|
||||
* </p>
|
||||
* @param outputChannel the output channel.
|
||||
* @return this builder instance for fluent chaining
|
||||
* @see RemotePartitioningManagerStepBuilder#messagingTemplate(MessagingTemplate)
|
||||
@@ -104,12 +118,14 @@ public class RemotePartitioningManagerStepBuilder extends PartitionStepBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link MessagingTemplate} to use to send data to workers.
|
||||
* <strong>The default channel of the messaging template must be set</strong>.
|
||||
* <p>Use either this setter to provide a fully configured messaging template or
|
||||
* provide an output channel through {@link RemotePartitioningManagerStepBuilder#outputChannel(MessageChannel)}
|
||||
* and a default messaging template will be created.</p>
|
||||
*
|
||||
* Set the {@link MessagingTemplate} to use to send data to workers. <strong>The
|
||||
* default channel of the messaging template must be set</strong>.
|
||||
* <p>
|
||||
* Use either this setter to provide a fully configured messaging template or provide
|
||||
* an output channel through
|
||||
* {@link RemotePartitioningManagerStepBuilder#outputChannel(MessageChannel)} and a
|
||||
* default messaging template will be created.
|
||||
* </p>
|
||||
* @param messagingTemplate the messaging template to use
|
||||
* @return this builder instance for fluent chaining
|
||||
* @see RemotePartitioningManagerStepBuilder#outputChannel(MessageChannel)
|
||||
@@ -132,7 +148,8 @@ public class RemotePartitioningManagerStepBuilder extends PartitionStepBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* How often to poll the job repository for the status of the workers. Defaults to 10 seconds.
|
||||
* How often to poll the job repository for the status of the workers. Defaults to 10
|
||||
* seconds.
|
||||
* @param pollInterval the poll interval value in milliseconds
|
||||
* @return this builder instance for fluent chaining
|
||||
*/
|
||||
@@ -143,7 +160,8 @@ public class RemotePartitioningManagerStepBuilder extends PartitionStepBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* When using job repository polling, the time limit to wait. Defaults to -1 (no timeout).
|
||||
* When using job repository polling, the time limit to wait. Defaults to -1 (no
|
||||
* timeout).
|
||||
* @param timeout the timeout value in milliseconds
|
||||
* @return this builder instance for fluent chaining
|
||||
*/
|
||||
@@ -189,15 +207,10 @@ public class RemotePartitioningManagerStepBuilder extends PartitionStepBuilder {
|
||||
else {
|
||||
PollableChannel replies = new QueueChannel();
|
||||
partitionHandler.setReplyChannel(replies);
|
||||
StandardIntegrationFlow standardIntegrationFlow = IntegrationFlows
|
||||
.from(this.inputChannel)
|
||||
.aggregate(aggregatorSpec -> aggregatorSpec.processor(partitionHandler))
|
||||
.channel(replies)
|
||||
.get();
|
||||
StandardIntegrationFlow standardIntegrationFlow = IntegrationFlows.from(this.inputChannel)
|
||||
.aggregate(aggregatorSpec -> aggregatorSpec.processor(partitionHandler)).channel(replies).get();
|
||||
IntegrationFlowContext integrationFlowContext = this.beanFactory.getBean(IntegrationFlowContext.class);
|
||||
integrationFlowContext.registration(standardIntegrationFlow)
|
||||
.autoStartup(false)
|
||||
.register();
|
||||
integrationFlowContext.registration(standardIntegrationFlow).autoStartup(false).register();
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -282,24 +295,23 @@ public class RemotePartitioningManagerStepBuilder extends PartitionStepBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will throw a {@link UnsupportedOperationException} since
|
||||
* the partition handler of the manager step will be automatically set to an
|
||||
* instance of {@link MessageChannelPartitionHandler}.
|
||||
*
|
||||
* When building a manager step for remote partitioning using this builder,
|
||||
* no partition handler must be provided.
|
||||
* This method will throw a {@link UnsupportedOperationException} since the partition
|
||||
* handler of the manager step will be automatically set to an instance of
|
||||
* {@link MessageChannelPartitionHandler}.
|
||||
*
|
||||
* When building a manager step for remote partitioning using this builder, no
|
||||
* partition handler must be provided.
|
||||
* @param partitionHandler a partition handler
|
||||
* @return this builder instance for fluent chaining
|
||||
* @throws UnsupportedOperationException if a partition handler is provided
|
||||
*/
|
||||
@Override
|
||||
public RemotePartitioningManagerStepBuilder partitionHandler(PartitionHandler partitionHandler) throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException("When configuring a manager step " +
|
||||
"for remote partitioning using the RemotePartitioningManagerStepBuilder, " +
|
||||
"the partition handler will be automatically set to an instance " +
|
||||
"of MessageChannelPartitionHandler. The partition handler must " +
|
||||
"not be provided in this case.");
|
||||
public RemotePartitioningManagerStepBuilder partitionHandler(PartitionHandler partitionHandler)
|
||||
throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException("When configuring a manager step "
|
||||
+ "for remote partitioning using the RemotePartitioningManagerStepBuilder, "
|
||||
+ "the partition handler will be automatically set to an instance "
|
||||
+ "of MessageChannelPartitionHandler. The partition handler must " + "not be provided in this case.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
/**
|
||||
* Convenient factory for a {@link RemotePartitioningManagerStepBuilder} which sets
|
||||
* the {@link JobRepository}, {@link JobExplorer}, {@link BeanFactory} and
|
||||
* Convenient factory for a {@link RemotePartitioningManagerStepBuilder} which sets the
|
||||
* {@link JobRepository}, {@link JobExplorer}, {@link BeanFactory} and
|
||||
* {@link PlatformTransactionManager} automatically.
|
||||
*
|
||||
* @since 4.2
|
||||
@@ -34,10 +34,12 @@ import org.springframework.transaction.PlatformTransactionManager;
|
||||
public class RemotePartitioningManagerStepBuilderFactory implements BeanFactoryAware {
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
final private JobExplorer jobExplorer;
|
||||
final private JobRepository jobRepository;
|
||||
final private PlatformTransactionManager transactionManager;
|
||||
|
||||
final private JobExplorer jobExplorer;
|
||||
|
||||
final private JobRepository jobRepository;
|
||||
|
||||
final private PlatformTransactionManager transactionManager;
|
||||
|
||||
/**
|
||||
* Create a new {@link RemotePartitioningManagerStepBuilderFactory}.
|
||||
@@ -45,8 +47,8 @@ public class RemotePartitioningManagerStepBuilderFactory implements BeanFactoryA
|
||||
* @param jobExplorer the job explorer to use
|
||||
* @param transactionManager the transaction manager to use
|
||||
*/
|
||||
public RemotePartitioningManagerStepBuilderFactory(JobRepository jobRepository,
|
||||
JobExplorer jobExplorer, PlatformTransactionManager transactionManager) {
|
||||
public RemotePartitioningManagerStepBuilderFactory(JobRepository jobRepository, JobExplorer jobExplorer,
|
||||
PlatformTransactionManager transactionManager) {
|
||||
|
||||
this.jobRepository = jobRepository;
|
||||
this.jobExplorer = jobExplorer;
|
||||
@@ -65,10 +67,8 @@ public class RemotePartitioningManagerStepBuilderFactory implements BeanFactoryA
|
||||
* @return a {@link RemotePartitioningManagerStepBuilder}
|
||||
*/
|
||||
public RemotePartitioningManagerStepBuilder get(String name) {
|
||||
return new RemotePartitioningManagerStepBuilder(name)
|
||||
.repository(this.jobRepository)
|
||||
.jobExplorer(this.jobExplorer)
|
||||
.beanFactory(this.beanFactory)
|
||||
return new RemotePartitioningManagerStepBuilder(name).repository(this.jobRepository)
|
||||
.jobExplorer(this.jobExplorer).beanFactory(this.beanFactory)
|
||||
.transactionManager(this.transactionManager);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,20 +46,20 @@ import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Builder for a worker step in a remote partitioning setup. This builder
|
||||
* creates an {@link IntegrationFlow} that:
|
||||
* Builder for a worker step in a remote partitioning setup. This builder creates an
|
||||
* {@link IntegrationFlow} that:
|
||||
*
|
||||
* <ul>
|
||||
* <li>listens to {@link StepExecutionRequest}s coming from the manager
|
||||
* on the input channel</li>
|
||||
* <li>invokes the {@link StepExecutionRequestHandler} to execute the worker
|
||||
* step for each incoming request. The worker step is located using the provided
|
||||
* {@link StepLocator}. If no {@link StepLocator} is provided, a {@link BeanFactoryStepLocator}
|
||||
* configured with the current {@link BeanFactory} will be used
|
||||
* <li>replies to the manager on the output channel (when the manager step is
|
||||
* configured to aggregate replies from workers). If no output channel
|
||||
* is provided, a {@link NullChannel} will be used (assuming the manager side
|
||||
* is configured to poll the job repository for workers status)</li>
|
||||
* <li>listens to {@link StepExecutionRequest}s coming from the manager on the input
|
||||
* channel</li>
|
||||
* <li>invokes the {@link StepExecutionRequestHandler} to execute the worker step for each
|
||||
* incoming request. The worker step is located using the provided {@link StepLocator}. If
|
||||
* no {@link StepLocator} is provided, a {@link BeanFactoryStepLocator} configured with
|
||||
* the current {@link BeanFactory} will be used
|
||||
* <li>replies to the manager on the output channel (when the manager step is configured
|
||||
* to aggregate replies from workers). If no output channel is provided, a
|
||||
* {@link NullChannel} will be used (assuming the manager side is configured to poll the
|
||||
* job repository for workers status)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @since 4.1
|
||||
@@ -68,12 +68,17 @@ import org.springframework.util.Assert;
|
||||
public class RemotePartitioningWorkerStepBuilder extends StepBuilder {
|
||||
|
||||
private static final String SERVICE_ACTIVATOR_METHOD_NAME = "handle";
|
||||
|
||||
private static final Log logger = LogFactory.getLog(RemotePartitioningWorkerStepBuilder.class);
|
||||
|
||||
private MessageChannel inputChannel;
|
||||
|
||||
private MessageChannel outputChannel;
|
||||
|
||||
private JobExplorer jobExplorer;
|
||||
|
||||
private StepLocator stepLocator;
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
/**
|
||||
@@ -85,8 +90,8 @@ public class RemotePartitioningWorkerStepBuilder extends StepBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the input channel on which step execution requests sent by the manager
|
||||
* are received.
|
||||
* Set the input channel on which step execution requests sent by the manager are
|
||||
* received.
|
||||
* @param inputChannel the input channel
|
||||
* @return this builder instance for fluent chaining
|
||||
*/
|
||||
@@ -220,8 +225,8 @@ public class RemotePartitioningWorkerStepBuilder extends StepBuilder {
|
||||
|
||||
/**
|
||||
* Create an {@link IntegrationFlow} with a {@link StepExecutionRequestHandler}
|
||||
* configured as a service activator listening to the input channel and replying
|
||||
* on the output channel.
|
||||
* configured as a service activator listening to the input channel and replying on
|
||||
* the output channel.
|
||||
*/
|
||||
private void configureWorkerIntegrationFlow() {
|
||||
Assert.notNull(this.inputChannel, "An InputChannel must be provided");
|
||||
@@ -234,8 +239,8 @@ public class RemotePartitioningWorkerStepBuilder extends StepBuilder {
|
||||
}
|
||||
if (this.outputChannel == null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("The output channel is set to a NullChannel. " +
|
||||
"The manager step must poll the job repository for workers status.");
|
||||
logger.debug("The output channel is set to a NullChannel. "
|
||||
+ "The manager step must poll the job repository for workers status.");
|
||||
}
|
||||
this.outputChannel = new NullChannel();
|
||||
}
|
||||
@@ -244,15 +249,10 @@ public class RemotePartitioningWorkerStepBuilder extends StepBuilder {
|
||||
stepExecutionRequestHandler.setJobExplorer(this.jobExplorer);
|
||||
stepExecutionRequestHandler.setStepLocator(this.stepLocator);
|
||||
|
||||
StandardIntegrationFlow standardIntegrationFlow = IntegrationFlows
|
||||
.from(this.inputChannel)
|
||||
.handle(stepExecutionRequestHandler, SERVICE_ACTIVATOR_METHOD_NAME)
|
||||
.channel(this.outputChannel)
|
||||
.get();
|
||||
StandardIntegrationFlow standardIntegrationFlow = IntegrationFlows.from(this.inputChannel)
|
||||
.handle(stepExecutionRequestHandler, SERVICE_ACTIVATOR_METHOD_NAME).channel(this.outputChannel).get();
|
||||
IntegrationFlowContext integrationFlowContext = this.beanFactory.getBean(IntegrationFlowContext.class);
|
||||
integrationFlowContext.registration(standardIntegrationFlow)
|
||||
.autoStartup(false)
|
||||
.register();
|
||||
integrationFlowContext.registration(standardIntegrationFlow).autoStartup(false).register();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
/**
|
||||
* Convenient factory for a {@link RemotePartitioningWorkerStepBuilder} which sets
|
||||
* the {@link JobRepository}, {@link JobExplorer}, {@link BeanFactory} and
|
||||
* Convenient factory for a {@link RemotePartitioningWorkerStepBuilder} which sets the
|
||||
* {@link JobRepository}, {@link JobExplorer}, {@link BeanFactory} and
|
||||
* {@link PlatformTransactionManager} automatically.
|
||||
*
|
||||
* @since 4.1
|
||||
@@ -34,10 +34,12 @@ import org.springframework.transaction.PlatformTransactionManager;
|
||||
public class RemotePartitioningWorkerStepBuilderFactory implements BeanFactoryAware {
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
final private JobExplorer jobExplorer;
|
||||
final private JobRepository jobRepository;
|
||||
final private PlatformTransactionManager transactionManager;
|
||||
|
||||
final private JobExplorer jobExplorer;
|
||||
|
||||
final private JobRepository jobRepository;
|
||||
|
||||
final private PlatformTransactionManager transactionManager;
|
||||
|
||||
/**
|
||||
* Create a new {@link RemotePartitioningWorkerStepBuilderFactory}.
|
||||
@@ -45,9 +47,8 @@ public class RemotePartitioningWorkerStepBuilderFactory implements BeanFactoryAw
|
||||
* @param jobExplorer the job explorer to use
|
||||
* @param transactionManager the transaction manager to use
|
||||
*/
|
||||
public RemotePartitioningWorkerStepBuilderFactory(JobRepository jobRepository,
|
||||
JobExplorer jobExplorer,
|
||||
PlatformTransactionManager transactionManager) {
|
||||
public RemotePartitioningWorkerStepBuilderFactory(JobRepository jobRepository, JobExplorer jobExplorer,
|
||||
PlatformTransactionManager transactionManager) {
|
||||
|
||||
this.jobExplorer = jobExplorer;
|
||||
this.jobRepository = jobRepository;
|
||||
@@ -66,10 +67,8 @@ public class RemotePartitioningWorkerStepBuilderFactory implements BeanFactoryAw
|
||||
* @return a {@link RemotePartitioningWorkerStepBuilder}
|
||||
*/
|
||||
public RemotePartitioningWorkerStepBuilder get(String name) {
|
||||
return new RemotePartitioningWorkerStepBuilder(name)
|
||||
.repository(this.jobRepository)
|
||||
.jobExplorer(this.jobExplorer)
|
||||
.beanFactory(this.beanFactory)
|
||||
return new RemotePartitioningWorkerStepBuilder(name).repository(this.jobRepository)
|
||||
.jobExplorer(this.jobExplorer).beanFactory(this.beanFactory)
|
||||
.transactionManager(this.transactionManager);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
/*
|
||||
* Copyright 2009-2018 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
|
||||
*
|
||||
* https://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.partition;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Class encapsulating information required to request a step execution in
|
||||
* a remote partitioning setup.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Mahmoud Ben Hassine
|
||||
*/
|
||||
public class StepExecutionRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long stepExecutionId;
|
||||
|
||||
private String stepName;
|
||||
|
||||
private Long jobExecutionId;
|
||||
|
||||
private StepExecutionRequest() {
|
||||
//For Jackson deserialization
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link StepExecutionRequest} instance.
|
||||
* @param stepName the name of the step to execute
|
||||
* @param jobExecutionId the id of the job execution
|
||||
* @param stepExecutionId the id of the step execution
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2009-2018 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
|
||||
*
|
||||
* https://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.partition;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Class encapsulating information required to request a step execution in a remote
|
||||
* partitioning setup.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Mahmoud Ben Hassine
|
||||
*/
|
||||
public class StepExecutionRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long stepExecutionId;
|
||||
|
||||
private String stepName;
|
||||
|
||||
private Long jobExecutionId;
|
||||
|
||||
private StepExecutionRequest() {
|
||||
// For Jackson deserialization
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link StepExecutionRequest} instance.
|
||||
* @param stepName the name of the step to execute
|
||||
* @param jobExecutionId the id of the job execution
|
||||
* @param stepExecutionId the id of the step execution
|
||||
*/
|
||||
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,80 +1,78 @@
|
||||
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.NoSuchStepException;
|
||||
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) {
|
||||
throw new NoSuchStepException("No StepExecution could be located for this request: " + request);
|
||||
}
|
||||
|
||||
String stepName = request.getStepName();
|
||||
Step step = stepLocator.getStep(stepName);
|
||||
if (step == null) {
|
||||
throw new NoSuchStepException(String.format("No Step with name [%s] could be located.", stepName));
|
||||
}
|
||||
|
||||
try {
|
||||
step.execute(stepExecution);
|
||||
}
|
||||
catch (JobInterruptedException e) {
|
||||
stepExecution.setStatus(BatchStatus.STOPPED);
|
||||
// The receiver should update the stepExecution in repository
|
||||
}
|
||||
catch (Throwable e) {
|
||||
stepExecution.addFailureException(e);
|
||||
stepExecution.setStatus(BatchStatus.FAILED);
|
||||
// The receiver should update the stepExecution in repository
|
||||
}
|
||||
|
||||
return stepExecution;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
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.NoSuchStepException;
|
||||
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) {
|
||||
throw new NoSuchStepException("No StepExecution could be located for this request: " + request);
|
||||
}
|
||||
|
||||
String stepName = request.getStepName();
|
||||
Step step = stepLocator.getStep(stepName);
|
||||
if (step == null) {
|
||||
throw new NoSuchStepException(String.format("No Step with name [%s] could be located.", stepName));
|
||||
}
|
||||
|
||||
try {
|
||||
step.execute(stepExecution);
|
||||
}
|
||||
catch (JobInterruptedException e) {
|
||||
stepExecution.setStatus(BatchStatus.STOPPED);
|
||||
// The receiver should update the stepExecution in repository
|
||||
}
|
||||
catch (Throwable e) {
|
||||
stepExecution.addFailureException(e);
|
||||
stepExecution.setStatus(BatchStatus.FAILED);
|
||||
// The receiver should update the stepExecution in repository
|
||||
}
|
||||
|
||||
return stepExecution;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@ import org.springframework.batch.core.step.AbstractStep;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Provides a wrapper for an existing {@link Step}, delegating execution to it,
|
||||
* but serving all other operations locally.
|
||||
*
|
||||
* Provides a wrapper for an existing {@link Step}, delegating execution to it, but
|
||||
* serving all other operations locally.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class DelegateStep extends AbstractStep {
|
||||
|
||||
@@ -38,13 +38,13 @@ public class DelegateStep extends AbstractStep {
|
||||
public void setDelegate(Step delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check mandatory properties (delegate).
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.state(delegate!=null, "A delegate Step must be provided");
|
||||
Assert.state(delegate != null, "A delegate Step must be provided");
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,112 +1,136 @@
|
||||
/*
|
||||
* Copyright 2006-2021 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
|
||||
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.repository.JobRestartException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Mahmoud Ben Hassine
|
||||
*
|
||||
*/
|
||||
public class JobRepositorySupport implements JobRepository {
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.repository.JobRepository#createJobExecution(org.springframework.batch.core.Job, org.springframework.batch.core.JobParameters)
|
||||
*/
|
||||
public JobExecution createJobExecution(String jobName, JobParameters jobParameters)
|
||||
throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
|
||||
return new JobExecution(new JobInstance(0L, jobName), jobParameters);
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.repository.JobRepository#getLastStepExecution(org.springframework.batch.core.JobInstance, org.springframework.batch.core.Step)
|
||||
*/
|
||||
@Nullable
|
||||
public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.repository.JobRepository#getStepExecutionCount(org.springframework.batch.core.JobInstance, org.springframework.batch.core.Step)
|
||||
*/
|
||||
public int getStepExecutionCount(JobInstance jobInstance, String stepName) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.repository.JobRepository#saveOrUpdate(org.springframework.batch.core.JobExecution)
|
||||
*/
|
||||
public void update(JobExecution jobExecution) {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.repository.JobRepository#saveOrUpdate(org.springframework.batch.core.StepExecution)
|
||||
*/
|
||||
public void saveOrUpdate(StepExecution stepExecution) {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.repository.JobRepository#saveOrUpdateExecutionContext(org.springframework.batch.core.StepExecution)
|
||||
*/
|
||||
public void updateExecutionContext(StepExecution stepExecution) {
|
||||
}
|
||||
|
||||
public void updateExecutionContext(JobExecution jobExecution) {
|
||||
}
|
||||
|
||||
public void add(StepExecution stepExecution) {
|
||||
}
|
||||
|
||||
public void update(StepExecution stepExecution) {
|
||||
}
|
||||
|
||||
public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.repository.JobRepository#getLastJobExecution(java.lang.String, org.springframework.batch.core.JobParameters)
|
||||
*/
|
||||
@Nullable
|
||||
public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addAll(Collection<StepExecution> stepExecutions) {
|
||||
if(stepExecutions != null) {
|
||||
for (StepExecution stepExecution : stepExecutions) {
|
||||
add(stepExecution);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public JobInstance createJobInstance(String jobName,
|
||||
JobParameters jobParameters) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2006-2021 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
|
||||
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.repository.JobRestartException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Mahmoud Ben Hassine
|
||||
*
|
||||
*/
|
||||
public class JobRepositorySupport implements JobRepository {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.batch.core.repository.JobRepository#createJobExecution(org.
|
||||
* springframework.batch.core.Job, org.springframework.batch.core.JobParameters)
|
||||
*/
|
||||
public JobExecution createJobExecution(String jobName, JobParameters jobParameters)
|
||||
throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
|
||||
return new JobExecution(new JobInstance(0L, jobName), jobParameters);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.batch.core.repository.JobRepository#getLastStepExecution(org.
|
||||
* springframework.batch.core.JobInstance, org.springframework.batch.core.Step)
|
||||
*/
|
||||
@Nullable
|
||||
public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.batch.core.repository.JobRepository#getStepExecutionCount(org.
|
||||
* springframework.batch.core.JobInstance, org.springframework.batch.core.Step)
|
||||
*/
|
||||
public int getStepExecutionCount(JobInstance jobInstance, String stepName) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.core.repository.JobRepository#saveOrUpdate(org.
|
||||
* springframework.batch.core.JobExecution)
|
||||
*/
|
||||
public void update(JobExecution jobExecution) {
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.core.repository.JobRepository#saveOrUpdate(org.
|
||||
* springframework.batch.core.StepExecution)
|
||||
*/
|
||||
public void saveOrUpdate(StepExecution stepExecution) {
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.core.repository.JobRepository#
|
||||
* saveOrUpdateExecutionContext(org.springframework.batch.core.StepExecution)
|
||||
*/
|
||||
public void updateExecutionContext(StepExecution stepExecution) {
|
||||
}
|
||||
|
||||
public void updateExecutionContext(JobExecution jobExecution) {
|
||||
}
|
||||
|
||||
public void add(StepExecution stepExecution) {
|
||||
}
|
||||
|
||||
public void update(StepExecution stepExecution) {
|
||||
}
|
||||
|
||||
public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.batch.core.repository.JobRepository#getLastJobExecution(java.
|
||||
* lang.String, org.springframework.batch.core.JobParameters)
|
||||
*/
|
||||
@Nullable
|
||||
public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void addAll(Collection<StepExecution> stepExecutions) {
|
||||
if (stepExecutions != null) {
|
||||
for (StepExecution stepExecution : stepExecutions) {
|
||||
add(stepExecution);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public JobInstance createJobInstance(String jobName, JobParameters jobParameters) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import org.springframework.batch.core.job.DefaultJobParametersValidator;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public class JobSupport implements Job {
|
||||
|
||||
|
||||
String name;
|
||||
|
||||
public JobSupport(String name){
|
||||
|
||||
public JobSupport(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@@ -25,12 +25,12 @@ public class JobSupport implements Job {
|
||||
public boolean isRestartable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
public JobParametersIncrementer getJobParametersIncrementer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public JobParametersValidator getJobParametersValidator() {
|
||||
return new DefaultJobParametersValidator();
|
||||
}
|
||||
|
||||
@@ -1,61 +1,60 @@
|
||||
package org.springframework.batch.integration;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class SmokeTests {
|
||||
|
||||
@Autowired
|
||||
private MessageChannel smokein;
|
||||
|
||||
@Autowired
|
||||
private PollableChannel smokeout;
|
||||
|
||||
|
||||
@Test
|
||||
public void testDummyWithSimpleAssert() throws Exception {
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVanillaSendAndReceive() throws Exception {
|
||||
smokein.send(new GenericMessage<>("foo"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<String> message = (Message<String>) smokeout.receive(100);
|
||||
String result = message == null ? null : message.getPayload();
|
||||
assertEquals("foo: 1", result);
|
||||
assertEquals(1, AnnotatedEndpoint.count);
|
||||
}
|
||||
|
||||
@MessageEndpoint
|
||||
static class AnnotatedEndpoint {
|
||||
|
||||
// This has to be static because Spring Integration registers the handler
|
||||
// more than once (every time a test instance is created), but only one of
|
||||
// them will get the message.
|
||||
private volatile static int count = 0;
|
||||
|
||||
@ServiceActivator(inputChannel = "smokein", outputChannel = "smokeout")
|
||||
public String process(String message) {
|
||||
count++;
|
||||
String result = message + ": " + count;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
package org.springframework.batch.integration;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class SmokeTests {
|
||||
|
||||
@Autowired
|
||||
private MessageChannel smokein;
|
||||
|
||||
@Autowired
|
||||
private PollableChannel smokeout;
|
||||
|
||||
@Test
|
||||
public void testDummyWithSimpleAssert() throws Exception {
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVanillaSendAndReceive() throws Exception {
|
||||
smokein.send(new GenericMessage<>("foo"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Message<String> message = (Message<String>) smokeout.receive(100);
|
||||
String result = message == null ? null : message.getPayload();
|
||||
assertEquals("foo: 1", result);
|
||||
assertEquals(1, AnnotatedEndpoint.count);
|
||||
}
|
||||
|
||||
@MessageEndpoint
|
||||
static class AnnotatedEndpoint {
|
||||
|
||||
// This has to be static because Spring Integration registers the handler
|
||||
// more than once (every time a test instance is created), but only one of
|
||||
// them will get the message.
|
||||
private volatile static int count = 0;
|
||||
|
||||
@ServiceActivator(inputChannel = "smokein", outputChannel = "smokeout")
|
||||
public String process(String message) {
|
||||
count++;
|
||||
String result = message + ": " + count;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,74 +1,84 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class StepSupport implements Step {
|
||||
|
||||
private String name;
|
||||
private int startLimit = 1;
|
||||
|
||||
/**
|
||||
* @param name
|
||||
*/
|
||||
public StepSupport(String name) {
|
||||
super();
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.Step#execute(org.springframework.batch.core.StepExecution)
|
||||
*/
|
||||
public void execute(StepExecution stepExecution) throws JobInterruptedException {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.Step#getName()
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.Step#getStartLimit()
|
||||
*/
|
||||
public int getStartLimit() {
|
||||
return startLimit;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.batch.core.Step#isAllowStartIfComplete()
|
||||
*/
|
||||
public boolean isAllowStartIfComplete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the startLimit.
|
||||
* @param startLimit the startLimit to set
|
||||
*/
|
||||
public void setStartLimit(int startLimit) {
|
||||
this.startLimit = startLimit;
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class StepSupport implements Step {
|
||||
|
||||
private String name;
|
||||
|
||||
private int startLimit = 1;
|
||||
|
||||
/**
|
||||
* @param name
|
||||
*/
|
||||
public StepSupport(String name) {
|
||||
super();
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.core.Step#execute(org.springframework.batch.core.
|
||||
* StepExecution)
|
||||
*/
|
||||
public void execute(StepExecution stepExecution) throws JobInterruptedException {
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.core.Step#getName()
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.core.Step#getStartLimit()
|
||||
*/
|
||||
public int getStartLimit() {
|
||||
return startLimit;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.batch.core.Step#isAllowStartIfComplete()
|
||||
*/
|
||||
public boolean isAllowStartIfComplete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the startLimit.
|
||||
* @param startLimit the startLimit to set
|
||||
*/
|
||||
public void setStartLimit(int startLimit) {
|
||||
this.startLimit = startLimit;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,119 +1,123 @@
|
||||
/*
|
||||
* Copyright 2006-2020 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
|
||||
*
|
||||
* https://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.async;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.MethodRule;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.model.FrameworkMethod;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.test.MetaDataInstanceFactory;
|
||||
import org.springframework.batch.test.StepScopeTestUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class AsyncItemProcessorMessagingGatewayTests {
|
||||
|
||||
private final AsyncItemProcessor<String, String> processor = new AsyncItemProcessor<>();
|
||||
|
||||
private final StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution(new JobParametersBuilder().addLong("factor", 2L).toJobParameters());;
|
||||
|
||||
@Rule
|
||||
public MethodRule rule = new MethodRule() {
|
||||
public Statement apply(final Statement base, FrameworkMethod method, Object target) {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
StepScopeTestUtils.doInStepScope(stepExecution, new Callable<Void>() {
|
||||
public Void call() throws Exception {
|
||||
try {
|
||||
base.evaluate();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@Autowired
|
||||
private ItemProcessor<String, String> delegate;
|
||||
|
||||
@Test
|
||||
public void testMultiExecution() throws Exception {
|
||||
processor.setDelegate(delegate);
|
||||
processor.setTaskExecutor(new SimpleAsyncTaskExecutor());
|
||||
List<Future<String>> list = new ArrayList<>();
|
||||
for (int count = 0; count < 10; count++) {
|
||||
list.add(processor.process("foo" + count));
|
||||
}
|
||||
for (Future<String> future : list) {
|
||||
String value = future.get();
|
||||
/**
|
||||
* This delegate is a Spring Integration MessagingGateway. It can
|
||||
* easily return null because of a timeout, but that will be treated
|
||||
* by Batch as a filtered item, whereas it is really more like a
|
||||
* skip. So we have to throw an exception in the processor if an
|
||||
* unexpected null value comes back.
|
||||
*/
|
||||
assertNotNull(value);
|
||||
assertTrue(value.matches("foo.*foo.*"));
|
||||
}
|
||||
}
|
||||
|
||||
@MessageEndpoint
|
||||
public static class Doubler {
|
||||
private int factor = 1;
|
||||
|
||||
public void setFactor(int factor) {
|
||||
this.factor = factor;
|
||||
}
|
||||
|
||||
@ServiceActivator
|
||||
public String cat(String value) {
|
||||
for (int i=1; i<factor; i++) {
|
||||
value += value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2006-2020 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
|
||||
*
|
||||
* https://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.async;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.MethodRule;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.model.FrameworkMethod;
|
||||
import org.junit.runners.model.Statement;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.test.MetaDataInstanceFactory;
|
||||
import org.springframework.batch.test.StepScopeTestUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class AsyncItemProcessorMessagingGatewayTests {
|
||||
|
||||
private final AsyncItemProcessor<String, String> processor = new AsyncItemProcessor<>();
|
||||
|
||||
private final StepExecution stepExecution = MetaDataInstanceFactory
|
||||
.createStepExecution(new JobParametersBuilder().addLong("factor", 2L).toJobParameters());
|
||||
|
||||
;
|
||||
|
||||
@Rule
|
||||
public MethodRule rule = new MethodRule() {
|
||||
public Statement apply(final Statement base, FrameworkMethod method, Object target) {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
StepScopeTestUtils.doInStepScope(stepExecution, new Callable<Void>() {
|
||||
public Void call() throws Exception {
|
||||
try {
|
||||
base.evaluate();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@Autowired
|
||||
private ItemProcessor<String, String> delegate;
|
||||
|
||||
@Test
|
||||
public void testMultiExecution() throws Exception {
|
||||
processor.setDelegate(delegate);
|
||||
processor.setTaskExecutor(new SimpleAsyncTaskExecutor());
|
||||
List<Future<String>> list = new ArrayList<>();
|
||||
for (int count = 0; count < 10; count++) {
|
||||
list.add(processor.process("foo" + count));
|
||||
}
|
||||
for (Future<String> future : list) {
|
||||
String value = future.get();
|
||||
/**
|
||||
* This delegate is a Spring Integration MessagingGateway. It can easily
|
||||
* return null because of a timeout, but that will be treated by Batch as a
|
||||
* filtered item, whereas it is really more like a skip. So we have to throw
|
||||
* an exception in the processor if an unexpected null value comes back.
|
||||
*/
|
||||
assertNotNull(value);
|
||||
assertTrue(value.matches("foo.*foo.*"));
|
||||
}
|
||||
}
|
||||
|
||||
@MessageEndpoint
|
||||
public static class Doubler {
|
||||
|
||||
private int factor = 1;
|
||||
|
||||
public void setFactor(int factor) {
|
||||
this.factor = factor;
|
||||
}
|
||||
|
||||
@ServiceActivator
|
||||
public String cat(String value) {
|
||||
for (int i = 1; i < factor; i++) {
|
||||
value += value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,90 +1,91 @@
|
||||
/*
|
||||
* Copyright 2006-2019 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
|
||||
*
|
||||
* https://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.async;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.core.scope.context.StepContext;
|
||||
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.test.MetaDataInstanceFactory;
|
||||
import org.springframework.batch.test.StepScopeTestUtils;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public class AsyncItemProcessorTests {
|
||||
|
||||
private AsyncItemProcessor<String, String> processor = new AsyncItemProcessor<>();
|
||||
|
||||
private ItemProcessor<String, String> delegate = new ItemProcessor<String, String>() {
|
||||
@Nullable
|
||||
public String process(String item) throws Exception {
|
||||
return item + item;
|
||||
};
|
||||
};
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNoDelegate() throws Exception {
|
||||
processor.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecution() throws Exception {
|
||||
processor.setDelegate(delegate);
|
||||
Future<String> result = processor.process("foo");
|
||||
assertEquals("foofoo", result.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecutionInStepScope() throws Exception {
|
||||
delegate = new ItemProcessor<String, String>() {
|
||||
@Nullable
|
||||
public String process(String item) throws Exception {
|
||||
StepContext context = StepSynchronizationManager.getContext();
|
||||
assertTrue(context != null && context.getStepExecution() != null);
|
||||
return item + item;
|
||||
};
|
||||
};
|
||||
processor.setDelegate(delegate);
|
||||
Future<String> result = StepScopeTestUtils.doInStepScope(MetaDataInstanceFactory.createStepExecution(), new Callable<Future<String>>() {
|
||||
public Future<String> call() throws Exception {
|
||||
return processor.process("foo");
|
||||
}
|
||||
});
|
||||
assertEquals("foofoo", result.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiExecution() throws Exception {
|
||||
processor.setDelegate(delegate);
|
||||
processor.setTaskExecutor(new SimpleAsyncTaskExecutor());
|
||||
List<Future<String>> list = new ArrayList<>();
|
||||
for (int count = 0; count < 10; count++) {
|
||||
list.add(processor.process("foo" + count));
|
||||
}
|
||||
for (Future<String> future : list) {
|
||||
assertTrue(future.get().matches("foo.*foo.*"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2006-2019 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
|
||||
*
|
||||
* https://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.async;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.core.scope.context.StepContext;
|
||||
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.test.MetaDataInstanceFactory;
|
||||
import org.springframework.batch.test.StepScopeTestUtils;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public class AsyncItemProcessorTests {
|
||||
|
||||
private AsyncItemProcessor<String, String> processor = new AsyncItemProcessor<>();
|
||||
|
||||
private ItemProcessor<String, String> delegate = new ItemProcessor<String, String>() {
|
||||
@Nullable
|
||||
public String process(String item) throws Exception {
|
||||
return item + item;
|
||||
};
|
||||
};
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNoDelegate() throws Exception {
|
||||
processor.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecution() throws Exception {
|
||||
processor.setDelegate(delegate);
|
||||
Future<String> result = processor.process("foo");
|
||||
assertEquals("foofoo", result.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecutionInStepScope() throws Exception {
|
||||
delegate = new ItemProcessor<String, String>() {
|
||||
@Nullable
|
||||
public String process(String item) throws Exception {
|
||||
StepContext context = StepSynchronizationManager.getContext();
|
||||
assertTrue(context != null && context.getStepExecution() != null);
|
||||
return item + item;
|
||||
};
|
||||
};
|
||||
processor.setDelegate(delegate);
|
||||
Future<String> result = StepScopeTestUtils.doInStepScope(MetaDataInstanceFactory.createStepExecution(),
|
||||
new Callable<Future<String>>() {
|
||||
public Future<String> call() throws Exception {
|
||||
return processor.process("foo");
|
||||
}
|
||||
});
|
||||
assertEquals("foofoo", result.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiExecution() throws Exception {
|
||||
processor.setDelegate(delegate);
|
||||
processor.setTaskExecutor(new SimpleAsyncTaskExecutor());
|
||||
List<Future<String>> list = new ArrayList<>();
|
||||
for (int count = 0; count < 10; count++) {
|
||||
list.add(processor.process("foo" + count));
|
||||
}
|
||||
for (Future<String> future : list) {
|
||||
assertTrue(future.get().matches("foo.*foo.*"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,7 +44,9 @@ import static org.junit.Assert.assertTrue;
|
||||
public class AsyncItemWriterTests {
|
||||
|
||||
private AsyncItemWriter<String> writer;
|
||||
|
||||
private List<String> writtenItems;
|
||||
|
||||
private TaskExecutor taskExecutor;
|
||||
|
||||
@Before
|
||||
@@ -174,7 +176,8 @@ public class AsyncItemWriterTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
public String get(long timeout, TimeUnit unit)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
@@ -228,8 +231,11 @@ public class AsyncItemWriterTests {
|
||||
private class ListItemWriter implements ItemWriter<String> {
|
||||
|
||||
protected List<String> items;
|
||||
|
||||
public boolean isOpened = false;
|
||||
|
||||
public boolean isUpdated = false;
|
||||
|
||||
public boolean isClosed = false;
|
||||
|
||||
public ListItemWriter(List<String> items) {
|
||||
@@ -240,12 +246,17 @@ public class AsyncItemWriterTests {
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
this.items.addAll(items);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ListItemStreamWriter implements ItemStreamWriter<String> {
|
||||
|
||||
public boolean isOpened = false;
|
||||
|
||||
public boolean isUpdated = false;
|
||||
|
||||
public boolean isClosed = false;
|
||||
|
||||
protected List<String> items;
|
||||
|
||||
public ListItemStreamWriter(List<String> items) {
|
||||
@@ -271,5 +282,7 @@ public class AsyncItemWriterTests {
|
||||
public void close() throws ItemStreamException {
|
||||
isClosed = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,118 +1,121 @@
|
||||
/*
|
||||
* Copyright 2006-2013 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
|
||||
*
|
||||
* https://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.async;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.MethodRule;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.model.FrameworkMethod;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.test.MetaDataInstanceFactory;
|
||||
import org.springframework.batch.test.StepScopeTestUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class PollingAsyncItemProcessorMessagingGatewayTests {
|
||||
|
||||
private AsyncItemProcessor<String, String> processor = new AsyncItemProcessor<>();
|
||||
|
||||
private StepExecution stepExecution = MetaDataInstanceFactory.createStepExecution(new JobParametersBuilder().addLong("factor", 2L).toJobParameters());;
|
||||
|
||||
@Rule
|
||||
public MethodRule rule = new MethodRule() {
|
||||
public Statement apply(final Statement base, FrameworkMethod method, Object target) {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
StepScopeTestUtils.doInStepScope(stepExecution, new Callable<Void>() {
|
||||
public Void call() throws Exception {
|
||||
try {
|
||||
base.evaluate();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@Autowired
|
||||
private ItemProcessor<String, String> delegate;
|
||||
|
||||
@Test
|
||||
public void testMultiExecution() throws Exception {
|
||||
processor.setDelegate(delegate);
|
||||
processor.setTaskExecutor(new SimpleAsyncTaskExecutor());
|
||||
List<Future<String>> list = new ArrayList<>();
|
||||
for (int count = 0; count < 10; count++) {
|
||||
list.add(processor.process("foo" + count));
|
||||
}
|
||||
for (Future<String> future : list) {
|
||||
String value = future.get();
|
||||
/**
|
||||
* This delegate is a Spring Integration MessagingGateway. It can
|
||||
* easily return null because of a timeout, but that will be treated
|
||||
* by Batch as a filtered item, whereas it is really more like a
|
||||
* skip. So we have to throw an exception in the processor if an
|
||||
* unexpected null value comes back.
|
||||
*/
|
||||
assertNotNull(value);
|
||||
assertTrue(value.matches("foo.*foo.*"));
|
||||
}
|
||||
}
|
||||
|
||||
@MessageEndpoint
|
||||
public static class Doubler {
|
||||
|
||||
@ServiceActivator
|
||||
public String cat(String value, @Header(value="stepExecution.jobExecution.jobParameters.getLong('factor')", required=false) Integer input) {
|
||||
long factor = input==null ? 1 : input;
|
||||
for (int i=1; i<factor; i++) {
|
||||
value += value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2006-2013 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
|
||||
*
|
||||
* https://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.async;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.MethodRule;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.model.FrameworkMethod;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.test.MetaDataInstanceFactory;
|
||||
import org.springframework.batch.test.StepScopeTestUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
public class PollingAsyncItemProcessorMessagingGatewayTests {
|
||||
|
||||
private AsyncItemProcessor<String, String> processor = new AsyncItemProcessor<>();
|
||||
|
||||
private StepExecution stepExecution = MetaDataInstanceFactory
|
||||
.createStepExecution(new JobParametersBuilder().addLong("factor", 2L).toJobParameters());
|
||||
|
||||
;
|
||||
|
||||
@Rule
|
||||
public MethodRule rule = new MethodRule() {
|
||||
public Statement apply(final Statement base, FrameworkMethod method, Object target) {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
StepScopeTestUtils.doInStepScope(stepExecution, new Callable<Void>() {
|
||||
public Void call() throws Exception {
|
||||
try {
|
||||
base.evaluate();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@Autowired
|
||||
private ItemProcessor<String, String> delegate;
|
||||
|
||||
@Test
|
||||
public void testMultiExecution() throws Exception {
|
||||
processor.setDelegate(delegate);
|
||||
processor.setTaskExecutor(new SimpleAsyncTaskExecutor());
|
||||
List<Future<String>> list = new ArrayList<>();
|
||||
for (int count = 0; count < 10; count++) {
|
||||
list.add(processor.process("foo" + count));
|
||||
}
|
||||
for (Future<String> future : list) {
|
||||
String value = future.get();
|
||||
/**
|
||||
* This delegate is a Spring Integration MessagingGateway. It can easily
|
||||
* return null because of a timeout, but that will be treated by Batch as a
|
||||
* filtered item, whereas it is really more like a skip. So we have to throw
|
||||
* an exception in the processor if an unexpected null value comes back.
|
||||
*/
|
||||
assertNotNull(value);
|
||||
assertTrue(value.matches("foo.*foo.*"));
|
||||
}
|
||||
}
|
||||
|
||||
@MessageEndpoint
|
||||
public static class Doubler {
|
||||
|
||||
@ServiceActivator
|
||||
public String cat(String value, @Header(value = "stepExecution.jobExecution.jobParameters.getLong('factor')",
|
||||
required = false) Integer input) {
|
||||
long factor = input == null ? 1 : input;
|
||||
for (int i = 1; i < factor; i++) {
|
||||
value += value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,331 +1,330 @@
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.Arrays;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.ExitStatus;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.job.SimpleJob;
|
||||
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
|
||||
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.repository.JobRestartException;
|
||||
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
|
||||
import org.springframework.batch.core.step.factory.SimpleStepFactoryBean;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
import org.springframework.batch.item.support.ListItemReader;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class ChunkMessageItemWriterIntegrationTests {
|
||||
|
||||
private final ChunkMessageChannelItemWriter<Object> writer = new ChunkMessageChannelItemWriter<>();
|
||||
|
||||
@Autowired
|
||||
@Qualifier("requests")
|
||||
private MessageChannel requests;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("replies")
|
||||
private PollableChannel replies;
|
||||
|
||||
private final SimpleStepFactoryBean<Object, Object> factory = new SimpleStepFactoryBean<>();
|
||||
|
||||
private JobRepository jobRepository;
|
||||
|
||||
private static long jobCounter;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
EmbeddedDatabase embeddedDatabase = new EmbeddedDatabaseBuilder()
|
||||
.addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
|
||||
.addScript("/org/springframework/batch/core/schema-hsqldb.sql")
|
||||
.build();
|
||||
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(embeddedDatabase);
|
||||
JobRepositoryFactoryBean repositoryFactoryBean = new JobRepositoryFactoryBean();
|
||||
repositoryFactoryBean.setDataSource(embeddedDatabase);
|
||||
repositoryFactoryBean.setTransactionManager(transactionManager);
|
||||
repositoryFactoryBean.afterPropertiesSet();
|
||||
jobRepository = repositoryFactoryBean.getObject();
|
||||
factory.setJobRepository(jobRepository);
|
||||
factory.setTransactionManager(transactionManager);
|
||||
factory.setBeanName("step");
|
||||
factory.setItemWriter(writer);
|
||||
factory.setCommitInterval(4);
|
||||
|
||||
MessagingTemplate gateway = new MessagingTemplate();
|
||||
writer.setMessagingOperations(gateway);
|
||||
|
||||
gateway.setDefaultChannel(requests);
|
||||
writer.setReplyChannel(replies);
|
||||
gateway.setReceiveTimeout(100);
|
||||
|
||||
TestItemWriter.count = 0;
|
||||
|
||||
// Drain queues
|
||||
Message<?> message = replies.receive(10);
|
||||
while (message != null) {
|
||||
System.err.println(message);
|
||||
message = replies.receive(10);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
while (replies.receive(10L) != null) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenWithNoState() throws Exception {
|
||||
writer.open(new ExecutionContext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateAndOpenWithState() throws Exception {
|
||||
ExecutionContext executionContext = new ExecutionContext();
|
||||
writer.update(executionContext);
|
||||
writer.open(executionContext);
|
||||
assertEquals(0, executionContext.getInt(ChunkMessageChannelItemWriter.EXPECTED));
|
||||
assertEquals(0, executionContext.getInt(ChunkMessageChannelItemWriter.ACTUAL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVanillaIteration() throws Exception {
|
||||
|
||||
factory.setItemReader(new ListItemReader<>(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("1,2,3,4,5,6"))));
|
||||
|
||||
Step step = factory.getObject();
|
||||
|
||||
StepExecution stepExecution = getStepExecution(step);
|
||||
step.execute(stepExecution);
|
||||
|
||||
waitForResults(6, 10);
|
||||
|
||||
assertEquals(6, TestItemWriter.count);
|
||||
assertEquals(6, stepExecution.getReadCount());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimulatedRestart() throws Exception {
|
||||
|
||||
factory.setItemReader(new ListItemReader<>(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("1,2,3,4,5,6"))));
|
||||
|
||||
Step step = factory.getObject();
|
||||
|
||||
StepExecution stepExecution = getStepExecution(step);
|
||||
|
||||
// Set up context with two messages (chunks) in the backlog
|
||||
stepExecution.getExecutionContext().putInt(ChunkMessageChannelItemWriter.EXPECTED, 6);
|
||||
stepExecution.getExecutionContext().putInt(ChunkMessageChannelItemWriter.ACTUAL, 4);
|
||||
// And make the back log real
|
||||
requests.send(getSimpleMessage("foo", stepExecution.getJobExecution().getJobId()));
|
||||
requests.send(getSimpleMessage("bar", stepExecution.getJobExecution().getJobId()));
|
||||
step.execute(stepExecution);
|
||||
|
||||
waitForResults(8, 10);
|
||||
|
||||
assertEquals(8, TestItemWriter.count);
|
||||
assertEquals(6, stepExecution.getReadCount());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimulatedRestartWithBadMessagesFromAnotherJob() throws Exception {
|
||||
|
||||
factory.setItemReader(new ListItemReader<>(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("1,2,3,4,5,6"))));
|
||||
|
||||
Step step = factory.getObject();
|
||||
|
||||
StepExecution stepExecution = getStepExecution(step);
|
||||
|
||||
// Set up context with two messages (chunks) in the backlog
|
||||
stepExecution.getExecutionContext().putInt(ChunkMessageChannelItemWriter.EXPECTED, 3);
|
||||
stepExecution.getExecutionContext().putInt(ChunkMessageChannelItemWriter.ACTUAL, 2);
|
||||
|
||||
// Speed up the eventual failure
|
||||
writer.setMaxWaitTimeouts(2);
|
||||
|
||||
// And make the back log real
|
||||
requests.send(getSimpleMessage("foo", 4321L));
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
|
||||
assertEquals(ExitStatus.FAILED.getExitCode(), stepExecution.getExitStatus().getExitCode());
|
||||
String message = stepExecution.getExitStatus().getExitDescription();
|
||||
assertTrue("Message does not contain 'wrong job': " + message, message.contains("wrong job"));
|
||||
|
||||
waitForResults(1, 10);
|
||||
|
||||
assertEquals(1, TestItemWriter.count);
|
||||
assertEquals(0, stepExecution.getReadCount());
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private GenericMessage<ChunkRequest> getSimpleMessage(String string, Long jobId) {
|
||||
StepContribution stepContribution = new JobExecution(new JobInstance(0L, "job"), new JobParameters())
|
||||
.createStepExecution("step").createStepContribution();
|
||||
ChunkRequest chunk = new ChunkRequest(0, StringUtils.commaDelimitedListToSet(string), jobId, stepContribution);
|
||||
GenericMessage<ChunkRequest> message = new GenericMessage<>(chunk);
|
||||
return message;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEarlyCompletionSignalledInHandler() throws Exception {
|
||||
|
||||
factory.setItemReader(new ListItemReader<>(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("1,fail,3,4,5,6"))));
|
||||
factory.setCommitInterval(2);
|
||||
|
||||
Step step = factory.getObject();
|
||||
|
||||
StepExecution stepExecution = getStepExecution(step);
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
|
||||
assertEquals(ExitStatus.FAILED.getExitCode(), stepExecution.getExitStatus().getExitCode());
|
||||
String message = stepExecution.getExitStatus().getExitDescription();
|
||||
assertTrue("Message does not contain 'fail': " + message, message.contains("fail"));
|
||||
|
||||
waitForResults(2, 10);
|
||||
|
||||
// The number of items processed is actually between 1 and 6, because
|
||||
// the one that failed might have been processed out of order.
|
||||
assertTrue(1 <= TestItemWriter.count);
|
||||
assertTrue(6 >= TestItemWriter.count);
|
||||
// But it should fail the step in any case
|
||||
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimulatedRestartWithNoBacklog() throws Exception {
|
||||
|
||||
factory.setItemReader(new ListItemReader<>(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("1,2,3,4,5,6"))));
|
||||
|
||||
Step step = factory.getObject();
|
||||
|
||||
StepExecution stepExecution = getStepExecution(step);
|
||||
|
||||
// Set up expectation of three messages (chunks) in the backlog
|
||||
stepExecution.getExecutionContext().putInt(ChunkMessageChannelItemWriter.EXPECTED, 6);
|
||||
stepExecution.getExecutionContext().putInt(ChunkMessageChannelItemWriter.ACTUAL, 3);
|
||||
|
||||
writer.setMaxWaitTimeouts(2);
|
||||
|
||||
/*
|
||||
* With no backlog we process all the items, but the listener can't
|
||||
* reconcile the expected number of items with the actual. An infinite
|
||||
* loop would be bad, so the best we can do is fail as fast as possible.
|
||||
*/
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
|
||||
assertEquals(ExitStatus.FAILED.getExitCode(), stepExecution.getExitStatus().getExitCode());
|
||||
String message = stepExecution.getExitStatus().getExitDescription();
|
||||
assertTrue("Message did not contain 'timed out': " + message, message.toLowerCase().contains("timed out"));
|
||||
|
||||
assertEquals(0, TestItemWriter.count);
|
||||
assertEquals(0, stepExecution.getReadCount());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This one is flakey - we try to force it to wait until after the step to
|
||||
* finish processing just by waiting for long enough.
|
||||
*/
|
||||
@Test
|
||||
public void testFailureInStepListener() throws Exception {
|
||||
|
||||
factory.setItemReader(new ListItemReader<>(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("wait,fail,3,4,5,6"))));
|
||||
|
||||
Step step = factory.getObject();
|
||||
|
||||
StepExecution stepExecution = getStepExecution(step);
|
||||
step.execute(stepExecution);
|
||||
|
||||
waitForResults(2, 10);
|
||||
|
||||
// The number of items processed is actually between 1 and 6, because
|
||||
// the one that failed might have been processed out of order.
|
||||
assertTrue(1 <= TestItemWriter.count);
|
||||
assertTrue(6 >= TestItemWriter.count);
|
||||
|
||||
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
|
||||
assertEquals(ExitStatus.FAILED.getExitCode(), stepExecution.getExitStatus().getExitCode());
|
||||
|
||||
String exitDescription = stepExecution.getExitStatus().getExitDescription();
|
||||
assertTrue("Exit description does not contain exception type name: " + exitDescription, exitDescription
|
||||
.contains(AsynchronousFailureException.class.getName()));
|
||||
|
||||
}
|
||||
|
||||
// TODO : test non-dispatch of empty chunk
|
||||
|
||||
private void waitForResults(int expected, int maxWait) throws InterruptedException {
|
||||
int count = 0;
|
||||
while (TestItemWriter.count < expected && count < maxWait) {
|
||||
count++;
|
||||
Thread.sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
private StepExecution getStepExecution(Step step) throws JobExecutionAlreadyRunningException, JobRestartException,
|
||||
JobInstanceAlreadyCompleteException {
|
||||
SimpleJob job = new SimpleJob();
|
||||
job.setName("job");
|
||||
JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), new JobParametersBuilder().addLong(
|
||||
"job.counter", jobCounter++).toJobParameters());
|
||||
StepExecution stepExecution = jobExecution.createStepExecution(step.getName());
|
||||
jobRepository.add(stepExecution);
|
||||
return stepExecution;
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2021 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
|
||||
*
|
||||
* https://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.Arrays;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.ExitStatus;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobInstance;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.job.SimpleJob;
|
||||
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
|
||||
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.repository.JobRestartException;
|
||||
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean;
|
||||
import org.springframework.batch.core.step.factory.SimpleStepFactoryBean;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
import org.springframework.batch.item.support.ListItemReader;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.integration.core.MessagingTemplate;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class ChunkMessageItemWriterIntegrationTests {
|
||||
|
||||
private final ChunkMessageChannelItemWriter<Object> writer = new ChunkMessageChannelItemWriter<>();
|
||||
|
||||
@Autowired
|
||||
@Qualifier("requests")
|
||||
private MessageChannel requests;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("replies")
|
||||
private PollableChannel replies;
|
||||
|
||||
private final SimpleStepFactoryBean<Object, Object> factory = new SimpleStepFactoryBean<>();
|
||||
|
||||
private JobRepository jobRepository;
|
||||
|
||||
private static long jobCounter;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
EmbeddedDatabase embeddedDatabase = new EmbeddedDatabaseBuilder()
|
||||
.addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
|
||||
.addScript("/org/springframework/batch/core/schema-hsqldb.sql").build();
|
||||
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(embeddedDatabase);
|
||||
JobRepositoryFactoryBean repositoryFactoryBean = new JobRepositoryFactoryBean();
|
||||
repositoryFactoryBean.setDataSource(embeddedDatabase);
|
||||
repositoryFactoryBean.setTransactionManager(transactionManager);
|
||||
repositoryFactoryBean.afterPropertiesSet();
|
||||
jobRepository = repositoryFactoryBean.getObject();
|
||||
factory.setJobRepository(jobRepository);
|
||||
factory.setTransactionManager(transactionManager);
|
||||
factory.setBeanName("step");
|
||||
factory.setItemWriter(writer);
|
||||
factory.setCommitInterval(4);
|
||||
|
||||
MessagingTemplate gateway = new MessagingTemplate();
|
||||
writer.setMessagingOperations(gateway);
|
||||
|
||||
gateway.setDefaultChannel(requests);
|
||||
writer.setReplyChannel(replies);
|
||||
gateway.setReceiveTimeout(100);
|
||||
|
||||
TestItemWriter.count = 0;
|
||||
|
||||
// Drain queues
|
||||
Message<?> message = replies.receive(10);
|
||||
while (message != null) {
|
||||
System.err.println(message);
|
||||
message = replies.receive(10);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
while (replies.receive(10L) != null) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenWithNoState() throws Exception {
|
||||
writer.open(new ExecutionContext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateAndOpenWithState() throws Exception {
|
||||
ExecutionContext executionContext = new ExecutionContext();
|
||||
writer.update(executionContext);
|
||||
writer.open(executionContext);
|
||||
assertEquals(0, executionContext.getInt(ChunkMessageChannelItemWriter.EXPECTED));
|
||||
assertEquals(0, executionContext.getInt(ChunkMessageChannelItemWriter.ACTUAL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVanillaIteration() throws Exception {
|
||||
|
||||
factory.setItemReader(
|
||||
new ListItemReader<>(Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,2,3,4,5,6"))));
|
||||
|
||||
Step step = factory.getObject();
|
||||
|
||||
StepExecution stepExecution = getStepExecution(step);
|
||||
step.execute(stepExecution);
|
||||
|
||||
waitForResults(6, 10);
|
||||
|
||||
assertEquals(6, TestItemWriter.count);
|
||||
assertEquals(6, stepExecution.getReadCount());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimulatedRestart() throws Exception {
|
||||
|
||||
factory.setItemReader(
|
||||
new ListItemReader<>(Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,2,3,4,5,6"))));
|
||||
|
||||
Step step = factory.getObject();
|
||||
|
||||
StepExecution stepExecution = getStepExecution(step);
|
||||
|
||||
// Set up context with two messages (chunks) in the backlog
|
||||
stepExecution.getExecutionContext().putInt(ChunkMessageChannelItemWriter.EXPECTED, 6);
|
||||
stepExecution.getExecutionContext().putInt(ChunkMessageChannelItemWriter.ACTUAL, 4);
|
||||
// And make the back log real
|
||||
requests.send(getSimpleMessage("foo", stepExecution.getJobExecution().getJobId()));
|
||||
requests.send(getSimpleMessage("bar", stepExecution.getJobExecution().getJobId()));
|
||||
step.execute(stepExecution);
|
||||
|
||||
waitForResults(8, 10);
|
||||
|
||||
assertEquals(8, TestItemWriter.count);
|
||||
assertEquals(6, stepExecution.getReadCount());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimulatedRestartWithBadMessagesFromAnotherJob() throws Exception {
|
||||
|
||||
factory.setItemReader(
|
||||
new ListItemReader<>(Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,2,3,4,5,6"))));
|
||||
|
||||
Step step = factory.getObject();
|
||||
|
||||
StepExecution stepExecution = getStepExecution(step);
|
||||
|
||||
// Set up context with two messages (chunks) in the backlog
|
||||
stepExecution.getExecutionContext().putInt(ChunkMessageChannelItemWriter.EXPECTED, 3);
|
||||
stepExecution.getExecutionContext().putInt(ChunkMessageChannelItemWriter.ACTUAL, 2);
|
||||
|
||||
// Speed up the eventual failure
|
||||
writer.setMaxWaitTimeouts(2);
|
||||
|
||||
// And make the back log real
|
||||
requests.send(getSimpleMessage("foo", 4321L));
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
|
||||
assertEquals(ExitStatus.FAILED.getExitCode(), stepExecution.getExitStatus().getExitCode());
|
||||
String message = stepExecution.getExitStatus().getExitDescription();
|
||||
assertTrue("Message does not contain 'wrong job': " + message, message.contains("wrong job"));
|
||||
|
||||
waitForResults(1, 10);
|
||||
|
||||
assertEquals(1, TestItemWriter.count);
|
||||
assertEquals(0, stepExecution.getReadCount());
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private GenericMessage<ChunkRequest> getSimpleMessage(String string, Long jobId) {
|
||||
StepContribution stepContribution = new JobExecution(new JobInstance(0L, "job"), new JobParameters())
|
||||
.createStepExecution("step").createStepContribution();
|
||||
ChunkRequest chunk = new ChunkRequest(0, StringUtils.commaDelimitedListToSet(string), jobId, stepContribution);
|
||||
GenericMessage<ChunkRequest> message = new GenericMessage<>(chunk);
|
||||
return message;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEarlyCompletionSignalledInHandler() throws Exception {
|
||||
|
||||
factory.setItemReader(
|
||||
new ListItemReader<>(Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,fail,3,4,5,6"))));
|
||||
factory.setCommitInterval(2);
|
||||
|
||||
Step step = factory.getObject();
|
||||
|
||||
StepExecution stepExecution = getStepExecution(step);
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
|
||||
assertEquals(ExitStatus.FAILED.getExitCode(), stepExecution.getExitStatus().getExitCode());
|
||||
String message = stepExecution.getExitStatus().getExitDescription();
|
||||
assertTrue("Message does not contain 'fail': " + message, message.contains("fail"));
|
||||
|
||||
waitForResults(2, 10);
|
||||
|
||||
// The number of items processed is actually between 1 and 6, because
|
||||
// the one that failed might have been processed out of order.
|
||||
assertTrue(1 <= TestItemWriter.count);
|
||||
assertTrue(6 >= TestItemWriter.count);
|
||||
// But it should fail the step in any case
|
||||
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimulatedRestartWithNoBacklog() throws Exception {
|
||||
|
||||
factory.setItemReader(
|
||||
new ListItemReader<>(Arrays.asList(StringUtils.commaDelimitedListToStringArray("1,2,3,4,5,6"))));
|
||||
|
||||
Step step = factory.getObject();
|
||||
|
||||
StepExecution stepExecution = getStepExecution(step);
|
||||
|
||||
// Set up expectation of three messages (chunks) in the backlog
|
||||
stepExecution.getExecutionContext().putInt(ChunkMessageChannelItemWriter.EXPECTED, 6);
|
||||
stepExecution.getExecutionContext().putInt(ChunkMessageChannelItemWriter.ACTUAL, 3);
|
||||
|
||||
writer.setMaxWaitTimeouts(2);
|
||||
|
||||
/*
|
||||
* With no backlog we process all the items, but the listener can't reconcile the
|
||||
* expected number of items with the actual. An infinite loop would be bad, so the
|
||||
* best we can do is fail as fast as possible.
|
||||
*/
|
||||
step.execute(stepExecution);
|
||||
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
|
||||
assertEquals(ExitStatus.FAILED.getExitCode(), stepExecution.getExitStatus().getExitCode());
|
||||
String message = stepExecution.getExitStatus().getExitDescription();
|
||||
assertTrue("Message did not contain 'timed out': " + message, message.toLowerCase().contains("timed out"));
|
||||
|
||||
assertEquals(0, TestItemWriter.count);
|
||||
assertEquals(0, stepExecution.getReadCount());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This one is flakey - we try to force it to wait until after the step to finish
|
||||
* processing just by waiting for long enough.
|
||||
*/
|
||||
@Test
|
||||
public void testFailureInStepListener() throws Exception {
|
||||
|
||||
factory.setItemReader(
|
||||
new ListItemReader<>(Arrays.asList(StringUtils.commaDelimitedListToStringArray("wait,fail,3,4,5,6"))));
|
||||
|
||||
Step step = factory.getObject();
|
||||
|
||||
StepExecution stepExecution = getStepExecution(step);
|
||||
step.execute(stepExecution);
|
||||
|
||||
waitForResults(2, 10);
|
||||
|
||||
// The number of items processed is actually between 1 and 6, because
|
||||
// the one that failed might have been processed out of order.
|
||||
assertTrue(1 <= TestItemWriter.count);
|
||||
assertTrue(6 >= TestItemWriter.count);
|
||||
|
||||
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
|
||||
assertEquals(ExitStatus.FAILED.getExitCode(), stepExecution.getExitStatus().getExitCode());
|
||||
|
||||
String exitDescription = stepExecution.getExitStatus().getExitDescription();
|
||||
assertTrue("Exit description does not contain exception type name: " + exitDescription,
|
||||
exitDescription.contains(AsynchronousFailureException.class.getName()));
|
||||
|
||||
}
|
||||
|
||||
// TODO : test non-dispatch of empty chunk
|
||||
|
||||
private void waitForResults(int expected, int maxWait) throws InterruptedException {
|
||||
int count = 0;
|
||||
while (TestItemWriter.count < expected && count < maxWait) {
|
||||
count++;
|
||||
Thread.sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
private StepExecution getStepExecution(Step step)
|
||||
throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException {
|
||||
SimpleJob job = new SimpleJob();
|
||||
job.setName("job");
|
||||
JobExecution jobExecution = jobRepository.createJobExecution(job.getName(),
|
||||
new JobParametersBuilder().addLong("job.counter", jobCounter++).toJobParameters());
|
||||
StepExecution stepExecution = jobExecution.createStepExecution(step.getName());
|
||||
jobRepository.add(stepExecution);
|
||||
return stepExecution;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
package org.springframework.batch.integration.chunk;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
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.batch.test.MetaDataInstanceFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class ChunkProcessorChunkHandlerTests {
|
||||
|
||||
private ChunkProcessorChunkHandler<Object> handler = new ChunkProcessorChunkHandler<>();
|
||||
|
||||
protected int count = 0;
|
||||
|
||||
@Test
|
||||
public void testVanillaHandleChunk() throws Exception {
|
||||
handler.setChunkProcessor(new ChunkProcessor<Object>() {
|
||||
public void process(StepContribution contribution, Chunk<Object> chunk) throws Exception {
|
||||
count += chunk.size();
|
||||
}
|
||||
});
|
||||
StepContribution stepContribution = MetaDataInstanceFactory.createStepExecution().createStepContribution();
|
||||
ChunkResponse response = handler.handleChunk(new ChunkRequest<>(0, StringUtils
|
||||
.commaDelimitedListToSet("foo,bar"), 12L, stepContribution));
|
||||
assertEquals(stepContribution, response.getStepContribution());
|
||||
assertEquals(12, response.getJobId().longValue());
|
||||
assertTrue(response.isSuccessful());
|
||||
assertEquals(2, count);
|
||||
}
|
||||
|
||||
}
|
||||
package org.springframework.batch.integration.chunk;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
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.batch.test.MetaDataInstanceFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class ChunkProcessorChunkHandlerTests {
|
||||
|
||||
private ChunkProcessorChunkHandler<Object> handler = new ChunkProcessorChunkHandler<>();
|
||||
|
||||
protected int count = 0;
|
||||
|
||||
@Test
|
||||
public void testVanillaHandleChunk() throws Exception {
|
||||
handler.setChunkProcessor(new ChunkProcessor<Object>() {
|
||||
public void process(StepContribution contribution, Chunk<Object> chunk) throws Exception {
|
||||
count += chunk.size();
|
||||
}
|
||||
});
|
||||
StepContribution stepContribution = MetaDataInstanceFactory.createStepExecution().createStepContribution();
|
||||
ChunkResponse response = handler.handleChunk(
|
||||
new ChunkRequest<>(0, StringUtils.commaDelimitedListToSet("foo,bar"), 12L, stepContribution));
|
||||
assertEquals(stepContribution, response.getStepContribution());
|
||||
assertEquals(12, response.getJobId().longValue());
|
||||
assertTrue(response.isSuccessful());
|
||||
assertEquals(2, count);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.test.MetaDataInstanceFactory;
|
||||
import org.springframework.util.SerializationUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ChunkRequestTests {
|
||||
|
||||
private ChunkRequest<String> request = new ChunkRequest<>(0, Arrays.asList("foo", "bar"),
|
||||
111L, MetaDataInstanceFactory.createStepExecution().createStepContribution());
|
||||
|
||||
@Test
|
||||
public void testGetJobId() {
|
||||
assertEquals(111L, request.getJobId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetItems() {
|
||||
assertEquals(2, request.getItems().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStepContribution() {
|
||||
assertNotNull(request.getStepContribution());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
System.err.println(request.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializable() throws Exception {
|
||||
@SuppressWarnings("unchecked")
|
||||
ChunkRequest<String> result = (ChunkRequest<String>) SerializationUtils.deserialize(SerializationUtils
|
||||
.serialize(request));
|
||||
assertNotNull(result.getStepContribution());
|
||||
assertEquals(111L, result.getJobId());
|
||||
assertEquals(2, result.getItems().size());
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.test.MetaDataInstanceFactory;
|
||||
import org.springframework.util.SerializationUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ChunkRequestTests {
|
||||
|
||||
private ChunkRequest<String> request = new ChunkRequest<>(0, Arrays.asList("foo", "bar"), 111L,
|
||||
MetaDataInstanceFactory.createStepExecution().createStepContribution());
|
||||
|
||||
@Test
|
||||
public void testGetJobId() {
|
||||
assertEquals(111L, request.getJobId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetItems() {
|
||||
assertEquals(2, request.getItems().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStepContribution() {
|
||||
assertNotNull(request.getStepContribution());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
System.err.println(request.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializable() throws Exception {
|
||||
@SuppressWarnings("unchecked")
|
||||
ChunkRequest<String> result = (ChunkRequest<String>) SerializationUtils
|
||||
.deserialize(SerializationUtils.serialize(request));
|
||||
assertNotNull(result.getStepContribution());
|
||||
assertEquals(111L, result.getJobId());
|
||||
assertEquals(2, result.getItems().size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
/*
|
||||
* Copyright 2006-2021 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
|
||||
*
|
||||
* https://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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.test.MetaDataInstanceFactory;
|
||||
import org.springframework.util.SerializationUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ChunkResponseTests {
|
||||
|
||||
private ChunkResponse response = new ChunkResponse(0, 111L, MetaDataInstanceFactory.createStepExecution()
|
||||
.createStepContribution());
|
||||
|
||||
@Test
|
||||
public void testGetJobId() {
|
||||
assertEquals(Long.valueOf(111L), response.getJobId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStepContribution() {
|
||||
assertNotNull(response.getStepContribution());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
System.err.println(response.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializable() throws Exception {
|
||||
ChunkResponse result = (ChunkResponse) SerializationUtils.deserialize(SerializationUtils.serialize(response));
|
||||
assertNotNull(result.getStepContribution());
|
||||
assertEquals(Long.valueOf(111L), result.getJobId());
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2006-2021 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
|
||||
*
|
||||
* https://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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.test.MetaDataInstanceFactory;
|
||||
import org.springframework.util.SerializationUtils;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class ChunkResponseTests {
|
||||
|
||||
private ChunkResponse response = new ChunkResponse(0, 111L,
|
||||
MetaDataInstanceFactory.createStepExecution().createStepContribution());
|
||||
|
||||
@Test
|
||||
public void testGetJobId() {
|
||||
assertEquals(Long.valueOf(111L), response.getJobId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStepContribution() {
|
||||
assertNotNull(response.getStepContribution());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
System.err.println(response.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializable() throws Exception {
|
||||
ChunkResponse result = (ChunkResponse) SerializationUtils.deserialize(SerializationUtils.serialize(response));
|
||||
assertNotNull(result.getStepContribution());
|
||||
assertEquals(Long.valueOf(111L), result.getJobId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ public class MessageSourcePollerInterceptorTests {
|
||||
public Message<String> receive() {
|
||||
return new GenericMessage<>(payload);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,89 +1,90 @@
|
||||
package org.springframework.batch.integration.chunk;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameter;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class RemoteChunkFaultTolerantStepIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
@Autowired
|
||||
private PollableChannel replies;
|
||||
|
||||
@Before
|
||||
public void drain() {
|
||||
Message<?> message = replies.receive(100L);
|
||||
while (message!=null) {
|
||||
// System.err.println(message);
|
||||
message = replies.receive(100L);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedStep() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters(Collections.singletonMap("item.three",
|
||||
new JobParameter("unsupported"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedStepOnError() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters(Collections.singletonMap("item.three",
|
||||
new JobParameter("error"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSunnyDayFaultTolerant() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters(Collections.singletonMap("item.three",
|
||||
new JobParameter("3"))));
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(9, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipsInWriter() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParametersBuilder().addString("item.three", "fail")
|
||||
.addLong("run.id", 1L).toJobParameters());
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
// The whole chunk gets skipped...
|
||||
assertEquals(2, stepExecution.getWriteSkipCount());
|
||||
}
|
||||
}
|
||||
package org.springframework.batch.integration.chunk;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameter;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class RemoteChunkFaultTolerantStepIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
@Autowired
|
||||
private PollableChannel replies;
|
||||
|
||||
@Before
|
||||
public void drain() {
|
||||
Message<?> message = replies.receive(100L);
|
||||
while (message != null) {
|
||||
// System.err.println(message);
|
||||
message = replies.receive(100L);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedStep() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParameters(Collections.singletonMap("item.three", new JobParameter("unsupported"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedStepOnError() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParameters(Collections.singletonMap("item.three", new JobParameter("error"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSunnyDayFaultTolerant() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParameters(Collections.singletonMap("item.three", new JobParameter("3"))));
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(9, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipsInWriter() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParametersBuilder().addString("item.three", "fail").addLong("run.id", 1L).toJobParameters());
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
// The whole chunk gets skipped...
|
||||
assertEquals(2, stepExecution.getWriteSkipCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,94 +1,96 @@
|
||||
package org.springframework.batch.integration.chunk;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameter;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class RemoteChunkFaultTolerantStepJdbcIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
@Autowired
|
||||
private PollableChannel replies;
|
||||
|
||||
@Before
|
||||
public void drain() {
|
||||
Message<?> message = replies.receive(100L);
|
||||
while (message!=null) {
|
||||
message = replies.receive(100L);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testFailedStep() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters(Collections.singletonMap("item.three",
|
||||
new JobParameter("unsupported"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testFailedStepOnError() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters(Collections.singletonMap("item.three",
|
||||
new JobParameter("error"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testSunnyDayFaultTolerant() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters(Collections.singletonMap("item.three",
|
||||
new JobParameter("3"))));
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(9, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testSkipsInWriter() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParametersBuilder().addString("item.three", "fail")
|
||||
.addLong("run.id", 1L).toJobParameters());
|
||||
// System.err.println(new SimpleJdbcTemplate(dataSource).queryForList("SELECT * FROM INT_MESSAGE_GROUP"));
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
// The whole chunk gets skipped...
|
||||
assertEquals(2, stepExecution.getWriteSkipCount());
|
||||
}
|
||||
}
|
||||
package org.springframework.batch.integration.chunk;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameter;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class RemoteChunkFaultTolerantStepJdbcIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
@Autowired
|
||||
private PollableChannel replies;
|
||||
|
||||
@Before
|
||||
public void drain() {
|
||||
Message<?> message = replies.receive(100L);
|
||||
while (message != null) {
|
||||
message = replies.receive(100L);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testFailedStep() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParameters(Collections.singletonMap("item.three", new JobParameter("unsupported"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testFailedStepOnError() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParameters(Collections.singletonMap("item.three", new JobParameter("error"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testSunnyDayFaultTolerant() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParameters(Collections.singletonMap("item.three", new JobParameter("3"))));
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(9, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testSkipsInWriter() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParametersBuilder().addString("item.three", "fail").addLong("run.id", 1L).toJobParameters());
|
||||
// System.err.println(new SimpleJdbcTemplate(dataSource).queryForList("SELECT *
|
||||
// FROM INT_MESSAGE_GROUP"));
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
// The whole chunk gets skipped...
|
||||
assertEquals(2, stepExecution.getWriteSkipCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,83 +1,84 @@
|
||||
package org.springframework.batch.integration.chunk;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameter;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@DirtiesContext
|
||||
public class RemoteChunkFaultTolerantStepJmsIntegrationTests {
|
||||
|
||||
@BeforeClass
|
||||
public static void clear() {
|
||||
FileSystemUtils.deleteRecursively(new File("activemq-data"));
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
@Test
|
||||
public void testFailedStep() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters(Collections.singletonMap("item.three",
|
||||
new JobParameter("unsupported"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedStepOnError() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters(Collections.singletonMap("item.three",
|
||||
new JobParameter("error"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSunnyDayFaultTolerant() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters(Collections.singletonMap("item.three",
|
||||
new JobParameter("3"))));
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(9, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipsInWriter() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParametersBuilder().addString("item.three", "fail")
|
||||
.addLong("run.id", 1L).toJobParameters());
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
assertEquals(2, stepExecution.getWriteSkipCount());
|
||||
}
|
||||
}
|
||||
package org.springframework.batch.integration.chunk;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameter;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@DirtiesContext
|
||||
public class RemoteChunkFaultTolerantStepJmsIntegrationTests {
|
||||
|
||||
@BeforeClass
|
||||
public static void clear() {
|
||||
FileSystemUtils.deleteRecursively(new File("activemq-data"));
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
@Test
|
||||
public void testFailedStep() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParameters(Collections.singletonMap("item.three", new JobParameter("unsupported"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedStepOnError() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParameters(Collections.singletonMap("item.three", new JobParameter("error"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSunnyDayFaultTolerant() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParameters(Collections.singletonMap("item.three", new JobParameter("3"))));
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(9, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipsInWriter() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParametersBuilder().addString("item.three", "fail").addLong("run.id", 1L).toJobParameters());
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
assertEquals(2, stepExecution.getWriteSkipCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
package org.springframework.batch.integration.chunk;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameter;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class RemoteChunkStepIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
@Test
|
||||
public void testSunnyDaySimpleStep() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters(Collections.singletonMap("item.three",
|
||||
new JobParameter("3"))));
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(9, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedStep() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters(Collections.singletonMap("item.three",
|
||||
new JobParameter("fail"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
}
|
||||
package org.springframework.batch.integration.chunk;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameter;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class RemoteChunkStepIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
@Test
|
||||
public void testSunnyDaySimpleStep() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParameters(Collections.singletonMap("item.three", new JobParameter("3"))));
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
assertEquals(9, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedStep() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParameters(Collections.singletonMap("item.three", new JobParameter("fail"))));
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
|
||||
assertEquals(9, stepExecution.getReadCount());
|
||||
// In principle the write count could be more than 2 and less than 9...
|
||||
assertEquals(7, stepExecution.getWriteCount());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -76,16 +76,19 @@ import static org.mockito.Mockito.when;
|
||||
* @author Mahmoud Ben Hassine
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {RemoteChunkingManagerStepBuilderTests.BatchConfiguration.class})
|
||||
@ContextConfiguration(classes = { RemoteChunkingManagerStepBuilderTests.BatchConfiguration.class })
|
||||
public class RemoteChunkingManagerStepBuilderTests {
|
||||
|
||||
@Autowired
|
||||
private JobRepository jobRepository;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
private PollableChannel inputChannel = new QueueChannel();
|
||||
|
||||
private DirectChannel outputChannel = new DirectChannel();
|
||||
|
||||
private ItemReader<String> itemReader = new ListItemReader<>(Arrays.asList("a", "b", "c"));
|
||||
|
||||
@Test
|
||||
@@ -153,48 +156,40 @@ public class RemoteChunkingManagerStepBuilderTests {
|
||||
@Test
|
||||
public void eitherOutputChannelOrMessagingTemplateMustBeProvided() {
|
||||
// given
|
||||
RemoteChunkingManagerStepBuilder<String, String> builder = new RemoteChunkingManagerStepBuilder<String, String>("step")
|
||||
.inputChannel(this.inputChannel)
|
||||
.outputChannel(new DirectChannel())
|
||||
RemoteChunkingManagerStepBuilder<String, String> builder = new RemoteChunkingManagerStepBuilder<String, String>(
|
||||
"step").inputChannel(this.inputChannel).outputChannel(new DirectChannel())
|
||||
.messagingTemplate(new MessagingTemplate());
|
||||
|
||||
// when
|
||||
final Exception expectedException = Assert.assertThrows(IllegalStateException.class, builder::build);
|
||||
|
||||
// then
|
||||
assertThat(expectedException).hasMessage("You must specify either an outputChannel or a messagingTemplate but not both.");
|
||||
assertThat(expectedException)
|
||||
.hasMessage("You must specify either an outputChannel or a messagingTemplate but not both.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsupportedOperationExceptionWhenSpecifyingAnItemWriter() {
|
||||
// when
|
||||
final Exception expectedException = Assert.assertThrows(UnsupportedOperationException.class,
|
||||
() -> new RemoteChunkingManagerStepBuilder<String, String>("step")
|
||||
.reader(this.itemReader)
|
||||
.writer(items -> { })
|
||||
.repository(this.jobRepository)
|
||||
.transactionManager(this.transactionManager)
|
||||
.inputChannel(this.inputChannel)
|
||||
.outputChannel(this.outputChannel)
|
||||
.build());
|
||||
() -> new RemoteChunkingManagerStepBuilder<String, String>("step").reader(this.itemReader)
|
||||
.writer(items -> {
|
||||
}).repository(this.jobRepository).transactionManager(this.transactionManager)
|
||||
.inputChannel(this.inputChannel).outputChannel(this.outputChannel).build());
|
||||
|
||||
// then
|
||||
assertThat(expectedException).hasMessage("When configuring a manager " +
|
||||
"step for remote chunking, the item writer will be automatically " +
|
||||
"set to an instance of ChunkMessageChannelItemWriter. " +
|
||||
"The item writer must not be provided in this case.");
|
||||
assertThat(expectedException).hasMessage(
|
||||
"When configuring a manager " + "step for remote chunking, the item writer will be automatically "
|
||||
+ "set to an instance of ChunkMessageChannelItemWriter. "
|
||||
+ "The item writer must not be provided in this case.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManagerStepCreation() {
|
||||
// when
|
||||
TaskletStep taskletStep = new RemoteChunkingManagerStepBuilder<String, String>("step")
|
||||
.reader(this.itemReader)
|
||||
.repository(this.jobRepository)
|
||||
.transactionManager(this.transactionManager)
|
||||
.inputChannel(this.inputChannel)
|
||||
.outputChannel(this.outputChannel)
|
||||
.build();
|
||||
TaskletStep taskletStep = new RemoteChunkingManagerStepBuilder<String, String>("step").reader(this.itemReader)
|
||||
.repository(this.jobRepository).transactionManager(this.transactionManager)
|
||||
.inputChannel(this.inputChannel).outputChannel(this.outputChannel).build();
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(taskletStep);
|
||||
@@ -204,7 +199,7 @@ public class RemoteChunkingManagerStepBuilderTests {
|
||||
* The following test is to cover setters that override those from parent builders.
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void testSetters() throws Exception {
|
||||
// when
|
||||
DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute();
|
||||
@@ -226,7 +221,7 @@ public class RemoteChunkingManagerStepBuilderTests {
|
||||
|
||||
ItemProcessor<String, String> itemProcessor = item -> {
|
||||
System.out.println("processing item " + item);
|
||||
if(item.equals("b")) {
|
||||
if (item.equals("b")) {
|
||||
throw new Exception("b was found");
|
||||
}
|
||||
else {
|
||||
@@ -237,21 +232,22 @@ public class RemoteChunkingManagerStepBuilderTests {
|
||||
ItemReader<String> itemReader = new ItemReader<String>() {
|
||||
|
||||
int count = 0;
|
||||
|
||||
List<String> items = Arrays.asList("a", "b", "c", "d", "d", "e", "f", "g", "h", "i");
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String read() throws Exception {
|
||||
System.out.println(">> count == " + count);
|
||||
if(count == 6) {
|
||||
if (count == 6) {
|
||||
count++;
|
||||
throw new IOException("6th item");
|
||||
}
|
||||
else if(count == 7) {
|
||||
else if (count == 7) {
|
||||
count++;
|
||||
throw new RuntimeException("7th item");
|
||||
}
|
||||
else if(count < items.size()){
|
||||
else if (count < items.size()) {
|
||||
String item = items.get(count++);
|
||||
System.out.println(">> item read was " + item);
|
||||
return item;
|
||||
@@ -262,38 +258,16 @@ public class RemoteChunkingManagerStepBuilderTests {
|
||||
}
|
||||
};
|
||||
|
||||
TaskletStep taskletStep = new RemoteChunkingManagerStepBuilder<String, String>("step")
|
||||
.reader(itemReader)
|
||||
.readerIsTransactionalQueue()
|
||||
.processor(itemProcessor)
|
||||
.repository(this.jobRepository)
|
||||
.transactionManager(this.transactionManager)
|
||||
.transactionAttribute(transactionAttribute)
|
||||
.inputChannel(this.inputChannel)
|
||||
.outputChannel(this.outputChannel)
|
||||
.listener(annotatedListener)
|
||||
.listener(skipListener)
|
||||
.listener(chunkListener)
|
||||
.listener(stepExecutionListener)
|
||||
.listener(itemReadListener)
|
||||
.listener(itemWriteListener)
|
||||
.listener(retryListener)
|
||||
.skip(Exception.class)
|
||||
.noSkip(RuntimeException.class)
|
||||
.skipLimit(10)
|
||||
.retry(IOException.class)
|
||||
.noRetry(RuntimeException.class)
|
||||
.retryLimit(10)
|
||||
.retryContextCache(retryCache)
|
||||
.noRollback(Exception.class)
|
||||
.startLimit(3)
|
||||
.allowStartIfComplete(true)
|
||||
.stepOperations(stepOperations)
|
||||
.chunk(3)
|
||||
.backOffPolicy(backOffPolicy)
|
||||
.stream(stream)
|
||||
.keyGenerator(Object::hashCode)
|
||||
.build();
|
||||
TaskletStep taskletStep = new RemoteChunkingManagerStepBuilder<String, String>("step").reader(itemReader)
|
||||
.readerIsTransactionalQueue().processor(itemProcessor).repository(this.jobRepository)
|
||||
.transactionManager(this.transactionManager).transactionAttribute(transactionAttribute)
|
||||
.inputChannel(this.inputChannel).outputChannel(this.outputChannel).listener(annotatedListener)
|
||||
.listener(skipListener).listener(chunkListener).listener(stepExecutionListener)
|
||||
.listener(itemReadListener).listener(itemWriteListener).listener(retryListener).skip(Exception.class)
|
||||
.noSkip(RuntimeException.class).skipLimit(10).retry(IOException.class).noRetry(RuntimeException.class)
|
||||
.retryLimit(10).retryContextCache(retryCache).noRollback(Exception.class).startLimit(3)
|
||||
.allowStartIfComplete(true).stepOperations(stepOperations).chunk(3).backOffPolicy(backOffPolicy)
|
||||
.stream(stream).keyGenerator(Object::hashCode).build();
|
||||
|
||||
JobExecution jobExecution = this.jobRepository.createJobExecution("job1", new JobParameters());
|
||||
StepExecution stepExecution = new StepExecution("step1", jobExecution);
|
||||
@@ -307,8 +281,10 @@ public class RemoteChunkingManagerStepBuilderTests {
|
||||
SimpleChunkProvider provider = (SimpleChunkProvider) ReflectionTestUtils.getField(tasklet, "chunkProvider");
|
||||
SimpleChunkProcessor processor = (SimpleChunkProcessor) ReflectionTestUtils.getField(tasklet, "chunkProcessor");
|
||||
ItemWriter itemWriter = (ItemWriter) ReflectionTestUtils.getField(processor, "itemWriter");
|
||||
MessagingTemplate messagingTemplate = (MessagingTemplate) ReflectionTestUtils.getField(itemWriter, "messagingGateway");
|
||||
CompositeItemStream compositeItemStream = (CompositeItemStream) ReflectionTestUtils.getField(taskletStep, "stream");
|
||||
MessagingTemplate messagingTemplate = (MessagingTemplate) ReflectionTestUtils.getField(itemWriter,
|
||||
"messagingGateway");
|
||||
CompositeItemStream compositeItemStream = (CompositeItemStream) ReflectionTestUtils.getField(taskletStep,
|
||||
"stream");
|
||||
|
||||
Assert.assertEquals(ReflectionTestUtils.getField(provider, "itemReader"), itemReader);
|
||||
Assert.assertFalse((Boolean) ReflectionTestUtils.getField(tasklet, "buffering"));
|
||||
@@ -324,7 +300,7 @@ public class RemoteChunkingManagerStepBuilderTests {
|
||||
Object stepOperationsUsed = ReflectionTestUtils.getField(taskletStep, "stepOperations");
|
||||
Assert.assertEquals(stepOperationsUsed, stepOperations);
|
||||
|
||||
Assert.assertEquals(((List)ReflectionTestUtils.getField(compositeItemStream, "streams")).size(), 2);
|
||||
Assert.assertEquals(((List) ReflectionTestUtils.getField(compositeItemStream, "streams")).size(), 2);
|
||||
Assert.assertNotNull(ReflectionTestUtils.getField(processor, "keyGenerator"));
|
||||
|
||||
verify(skipListener, atLeastOnce()).onSkipInProcess(any(), any());
|
||||
@@ -344,11 +320,8 @@ public class RemoteChunkingManagerStepBuilderTests {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
|
||||
.addScript("/org/springframework/batch/core/schema-hsqldb.sql")
|
||||
.generateUniqueName(true)
|
||||
.build();
|
||||
return new EmbeddedDatabaseBuilder().addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
|
||||
.addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -357,4 +330,5 @@ public class RemoteChunkingManagerStepBuilderTests {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
public class RemoteChunkingWorkerBuilderTests {
|
||||
|
||||
private ItemProcessor<String, String> itemProcessor = new PassThroughItemProcessor<>();
|
||||
private ItemWriter<String> itemWriter = items -> { };
|
||||
|
||||
private ItemWriter<String> itemWriter = items -> {
|
||||
};
|
||||
|
||||
@Test
|
||||
public void itemProcessorMustNotBeNull() {
|
||||
@@ -90,7 +92,8 @@ public class RemoteChunkingWorkerBuilderTests {
|
||||
public void testMandatoryInputChannel() {
|
||||
// given
|
||||
RemoteChunkingWorkerBuilder<String, String> builder = new RemoteChunkingWorkerBuilder<String, String>()
|
||||
.itemWriter(items -> { });
|
||||
.itemWriter(items -> {
|
||||
});
|
||||
|
||||
// when
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class, builder::build);
|
||||
@@ -103,9 +106,8 @@ public class RemoteChunkingWorkerBuilderTests {
|
||||
public void testMandatoryOutputChannel() {
|
||||
// given
|
||||
RemoteChunkingWorkerBuilder<String, String> builder = new RemoteChunkingWorkerBuilder<String, String>()
|
||||
.itemWriter(items -> { })
|
||||
.inputChannel(new DirectChannel());
|
||||
|
||||
.itemWriter(items -> {
|
||||
}).inputChannel(new DirectChannel());
|
||||
|
||||
// when
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class, builder::build);
|
||||
@@ -120,9 +122,7 @@ public class RemoteChunkingWorkerBuilderTests {
|
||||
DirectChannel inputChannel = new DirectChannel();
|
||||
DirectChannel outputChannel = new DirectChannel();
|
||||
RemoteChunkingWorkerBuilder<String, String> builder = new RemoteChunkingWorkerBuilder<String, String>()
|
||||
.itemProcessor(this.itemProcessor)
|
||||
.itemWriter(this.itemWriter)
|
||||
.inputChannel(inputChannel)
|
||||
.itemProcessor(this.itemProcessor).itemWriter(this.itemWriter).inputChannel(inputChannel)
|
||||
.outputChannel(outputChannel);
|
||||
|
||||
// when
|
||||
|
||||
@@ -1,72 +1,72 @@
|
||||
package org.springframework.batch.integration.chunk;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ParseException;
|
||||
import org.springframework.batch.item.UnexpectedInputException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class TestItemReader<T> implements ItemReader<T> {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TestItemReader.class);
|
||||
|
||||
/**
|
||||
* Counts the number of chunks processed in the handler.
|
||||
*/
|
||||
public volatile int count = 0;
|
||||
|
||||
/**
|
||||
* Item that causes failure in handler.
|
||||
*/
|
||||
public final static String FAIL_ON = "bad";
|
||||
|
||||
/**
|
||||
* Item that causes handler to wait to simulate delayed processing.
|
||||
*/
|
||||
public static final String WAIT_ON = "wait";
|
||||
|
||||
private List<T> items = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* @param items the items to set
|
||||
*/
|
||||
public void setItems(List<T> items) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public T read() throws Exception, UnexpectedInputException, ParseException {
|
||||
|
||||
if (count>=items.size()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
T item = items.get(count++);
|
||||
|
||||
logger.debug("Reading "+item);
|
||||
|
||||
if (item.equals(WAIT_ON)) {
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Unexpected interruption.", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.equals(FAIL_ON)) {
|
||||
throw new IllegalStateException("Planned failure on: " + FAIL_ON);
|
||||
}
|
||||
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
package org.springframework.batch.integration.chunk;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ParseException;
|
||||
import org.springframework.batch.item.UnexpectedInputException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class TestItemReader<T> implements ItemReader<T> {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TestItemReader.class);
|
||||
|
||||
/**
|
||||
* Counts the number of chunks processed in the handler.
|
||||
*/
|
||||
public volatile int count = 0;
|
||||
|
||||
/**
|
||||
* Item that causes failure in handler.
|
||||
*/
|
||||
public final static String FAIL_ON = "bad";
|
||||
|
||||
/**
|
||||
* Item that causes handler to wait to simulate delayed processing.
|
||||
*/
|
||||
public static final String WAIT_ON = "wait";
|
||||
|
||||
private List<T> items = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* @param items the items to set
|
||||
*/
|
||||
public void setItems(List<T> items) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public T read() throws Exception, UnexpectedInputException, ParseException {
|
||||
|
||||
if (count >= items.size()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
T item = items.get(count++);
|
||||
|
||||
logger.debug("Reading " + item);
|
||||
|
||||
if (item.equals(WAIT_ON)) {
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Unexpected interruption.", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.equals(FAIL_ON)) {
|
||||
throw new IllegalStateException("Planned failure on: " + FAIL_ON);
|
||||
}
|
||||
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,74 +1,74 @@
|
||||
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.item.ItemWriter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class TestItemWriter<T> implements ItemWriter<T> {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TestItemWriter.class);
|
||||
|
||||
/**
|
||||
* Counts the number of chunks processed in the handler.
|
||||
*/
|
||||
public volatile static int count = 0;
|
||||
|
||||
/**
|
||||
* Item that causes failure in handler.
|
||||
*/
|
||||
public final static String FAIL_ON = "fail";
|
||||
|
||||
/**
|
||||
* Item that causes error in handler.
|
||||
*/
|
||||
public final static String UNSUPPORTED_ON = "unsupported";
|
||||
|
||||
/**
|
||||
* Item that causes error in handler.
|
||||
*/
|
||||
public final static String ERROR_ON = "error";
|
||||
|
||||
/**
|
||||
* Item that causes handler to wait to simulate delayed processing.
|
||||
*/
|
||||
public static final String WAIT_ON = "wait";
|
||||
|
||||
public void write(List<? extends T> items) throws Exception {
|
||||
|
||||
for (T item : items) {
|
||||
|
||||
count++;
|
||||
|
||||
logger.debug("Writing: " + item);
|
||||
|
||||
if (item.equals(WAIT_ON)) {
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Unexpected interruption.", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.equals(FAIL_ON)) {
|
||||
throw new IllegalStateException("Planned failure on: " + FAIL_ON);
|
||||
}
|
||||
|
||||
if (item.equals(UNSUPPORTED_ON)) {
|
||||
throw new UnsupportedOperationException("Planned failure on: " + UNSUPPORTED_ON);
|
||||
}
|
||||
|
||||
if (item.equals(ERROR_ON)) {
|
||||
throw new Error("Planned failure on: " + ERROR_ON);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
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.item.ItemWriter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class TestItemWriter<T> implements ItemWriter<T> {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(TestItemWriter.class);
|
||||
|
||||
/**
|
||||
* Counts the number of chunks processed in the handler.
|
||||
*/
|
||||
public volatile static int count = 0;
|
||||
|
||||
/**
|
||||
* Item that causes failure in handler.
|
||||
*/
|
||||
public final static String FAIL_ON = "fail";
|
||||
|
||||
/**
|
||||
* Item that causes error in handler.
|
||||
*/
|
||||
public final static String UNSUPPORTED_ON = "unsupported";
|
||||
|
||||
/**
|
||||
* Item that causes error in handler.
|
||||
*/
|
||||
public final static String ERROR_ON = "error";
|
||||
|
||||
/**
|
||||
* Item that causes handler to wait to simulate delayed processing.
|
||||
*/
|
||||
public static final String WAIT_ON = "wait";
|
||||
|
||||
public void write(List<? extends T> items) throws Exception {
|
||||
|
||||
for (T item : items) {
|
||||
|
||||
count++;
|
||||
|
||||
logger.debug("Writing: " + item);
|
||||
|
||||
if (item.equals(WAIT_ON)) {
|
||||
try {
|
||||
Thread.sleep(200);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Unexpected interruption.", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.equals(FAIL_ON)) {
|
||||
throw new IllegalStateException("Planned failure on: " + FAIL_ON);
|
||||
}
|
||||
|
||||
if (item.equals(UNSUPPORTED_ON)) {
|
||||
throw new UnsupportedOperationException("Planned failure on: " + UNSUPPORTED_ON);
|
||||
}
|
||||
|
||||
if (item.equals(ERROR_ON)) {
|
||||
throw new Error("Planned failure on: " + ERROR_ON);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
* @author Mahmoud Ben Hassine
|
||||
* @since 1.3
|
||||
@@ -30,13 +29,10 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
@EnableBatchProcessing
|
||||
public class JobLauncherParserTestsConfiguration {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
|
||||
.addScript("/org/springframework/batch/core/schema-hsqldb.sql")
|
||||
.generateUniqueName(true)
|
||||
.build();
|
||||
}
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder().addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
|
||||
.addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
* @since 1.3
|
||||
*
|
||||
@@ -47,17 +46,20 @@ public class JobLaunchingGatewayParserTests {
|
||||
public void testGatewayParser() throws Exception {
|
||||
setUp("JobLaunchingGatewayParserTests-context.xml", getClass());
|
||||
|
||||
final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(this.consumer, "inputChannel", AbstractMessageChannel.class);
|
||||
final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(this.consumer, "inputChannel",
|
||||
AbstractMessageChannel.class);
|
||||
assertEquals("requestChannel", inputChannel.getComponentName());
|
||||
|
||||
final JobLaunchingMessageHandler jobLaunchingMessageHandler = TestUtils.getPropertyValue(this.consumer, "handler.jobLaunchingMessageHandler", JobLaunchingMessageHandler.class);
|
||||
final JobLaunchingMessageHandler jobLaunchingMessageHandler = TestUtils.getPropertyValue(this.consumer,
|
||||
"handler.jobLaunchingMessageHandler", JobLaunchingMessageHandler.class);
|
||||
|
||||
assertNotNull(jobLaunchingMessageHandler);
|
||||
|
||||
final MessagingTemplate messagingTemplate = TestUtils.getPropertyValue(this.consumer, "handler.messagingTemplate", MessagingTemplate.class);
|
||||
final MessagingTemplate messagingTemplate = TestUtils.getPropertyValue(this.consumer,
|
||||
"handler.messagingTemplate", MessagingTemplate.class);
|
||||
final Long sendTimeout = TestUtils.getPropertyValue(messagingTemplate, "sendTimeout", Long.class);
|
||||
|
||||
assertEquals("Wrong sendTimeout", Long.valueOf(123L), sendTimeout);
|
||||
assertEquals("Wrong sendTimeout", Long.valueOf(123L), sendTimeout);
|
||||
assertFalse(this.consumer.isRunning());
|
||||
}
|
||||
|
||||
@@ -66,10 +68,11 @@ public class JobLaunchingGatewayParserTests {
|
||||
setUp("JobLaunchingGatewayParserTestsRunning-context.xml", getClass());
|
||||
assertTrue(this.consumer.isRunning());
|
||||
|
||||
final MessagingTemplate messagingTemplate = TestUtils.getPropertyValue(this.consumer, "handler.messagingTemplate", MessagingTemplate.class);
|
||||
final MessagingTemplate messagingTemplate = TestUtils.getPropertyValue(this.consumer,
|
||||
"handler.messagingTemplate", MessagingTemplate.class);
|
||||
final Long sendTimeout = TestUtils.getPropertyValue(messagingTemplate, "sendTimeout", Long.class);
|
||||
|
||||
assertEquals("Wrong sendTimeout", Long.valueOf(-1L), sendTimeout);
|
||||
assertEquals("Wrong sendTimeout", Long.valueOf(-1L), sendTimeout);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -77,7 +80,7 @@ public class JobLaunchingGatewayParserTests {
|
||||
try {
|
||||
setUp("JobLaunchingGatewayParserTestsNoJobLauncher-context.xml", getClass());
|
||||
}
|
||||
catch(BeanCreationException e) {
|
||||
catch (BeanCreationException e) {
|
||||
assertEquals("No bean named 'jobLauncher' available", e.getCause().getMessage());
|
||||
return;
|
||||
}
|
||||
@@ -88,24 +91,26 @@ public class JobLaunchingGatewayParserTests {
|
||||
public void testJobLaunchingGatewayWithEnableBatchProcessing() throws Exception {
|
||||
|
||||
setUp("JobLaunchingGatewayParserTestsWithEnableBatchProcessing-context.xml", getClass());
|
||||
final JobLaunchingMessageHandler jobLaunchingMessageHandler = TestUtils.getPropertyValue(this.consumer, "handler.jobLaunchingMessageHandler", JobLaunchingMessageHandler.class);
|
||||
final JobLaunchingMessageHandler jobLaunchingMessageHandler = TestUtils.getPropertyValue(this.consumer,
|
||||
"handler.jobLaunchingMessageHandler", JobLaunchingMessageHandler.class);
|
||||
assertNotNull(jobLaunchingMessageHandler);
|
||||
|
||||
final JobLauncher jobLauncher = TestUtils.getPropertyValue(jobLaunchingMessageHandler, "jobLauncher", JobLauncher.class);
|
||||
final JobLauncher jobLauncher = TestUtils.getPropertyValue(jobLaunchingMessageHandler, "jobLauncher",
|
||||
JobLauncher.class);
|
||||
assertNotNull(jobLauncher);
|
||||
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown(){
|
||||
if(context != null){
|
||||
public void tearDown() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void setUp(String name, Class<?> cls){
|
||||
context = new ClassPathXmlApplicationContext(name, cls);
|
||||
consumer = this.context.getBean("batchjobExecutor", EventDrivenConsumer.class);
|
||||
public void setUp(String name, Class<?> cls) {
|
||||
context = new ClassPathXmlApplicationContext(name, cls);
|
||||
consumer = this.context.getBean("batchjobExecutor", EventDrivenConsumer.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test cases for the {@link RemoteChunkingWorkerParser}
|
||||
* and {@link RemoteChunkingManagerParser}.
|
||||
* Test cases for the {@link RemoteChunkingWorkerParser} and
|
||||
* {@link RemoteChunkingManagerParser}.
|
||||
* </p>
|
||||
*
|
||||
* @author Chris Schaefer
|
||||
@@ -57,47 +57,54 @@ public class RemoteChunkingParserTests {
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test
|
||||
public void testRemoteChunkingWorkerParserWithProcessorDefined() {
|
||||
ApplicationContext applicationContext =
|
||||
new ClassPathXmlApplicationContext("/org/springframework/batch/integration/config/xml/RemoteChunkingWorkerParserTests.xml");
|
||||
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
|
||||
"/org/springframework/batch/integration/config/xml/RemoteChunkingWorkerParserTests.xml");
|
||||
|
||||
ChunkHandler chunkHandler = applicationContext.getBean(ChunkProcessorChunkHandler.class);
|
||||
ChunkProcessor chunkProcessor = (SimpleChunkProcessor) TestUtils.getPropertyValue(chunkHandler, "chunkProcessor");
|
||||
ChunkProcessor chunkProcessor = (SimpleChunkProcessor) TestUtils.getPropertyValue(chunkHandler,
|
||||
"chunkProcessor");
|
||||
assertNotNull("ChunkProcessor must not be null", chunkProcessor);
|
||||
|
||||
ItemWriter<String> itemWriter = (ItemWriter<String>) TestUtils.getPropertyValue(chunkProcessor, "itemWriter");
|
||||
assertNotNull("ChunkProcessor ItemWriter must not be null", itemWriter);
|
||||
assertTrue("Got wrong instance of ItemWriter", itemWriter instanceof Writer);
|
||||
|
||||
ItemProcessor<String, String> itemProcessor = (ItemProcessor<String, String>) TestUtils.getPropertyValue(chunkProcessor, "itemProcessor");
|
||||
ItemProcessor<String, String> itemProcessor = (ItemProcessor<String, String>) TestUtils
|
||||
.getPropertyValue(chunkProcessor, "itemProcessor");
|
||||
assertNotNull("ChunkProcessor ItemWriter must not be null", itemProcessor);
|
||||
assertTrue("Got wrong instance of ItemProcessor", itemProcessor instanceof Processor);
|
||||
|
||||
FactoryBean serviceActivatorFactoryBean = applicationContext.getBean(ServiceActivatorFactoryBean.class);
|
||||
assertNotNull("ServiceActivatorFactoryBean must not be null", serviceActivatorFactoryBean);
|
||||
assertNotNull("Output channel name must not be null", TestUtils.getPropertyValue(serviceActivatorFactoryBean, "outputChannelName"));
|
||||
assertNotNull("Output channel name must not be null",
|
||||
TestUtils.getPropertyValue(serviceActivatorFactoryBean, "outputChannelName"));
|
||||
|
||||
MessageChannel inputChannel = applicationContext.getBean("requests", MessageChannel.class);
|
||||
assertNotNull("Input channel must not be null", inputChannel);
|
||||
|
||||
String targetMethodName = (String) TestUtils.getPropertyValue(serviceActivatorFactoryBean, "targetMethodName");
|
||||
assertNotNull("Target method name must not be null", targetMethodName);
|
||||
assertTrue("Target method name must be handleChunk, got: " + targetMethodName, "handleChunk".equals(targetMethodName));
|
||||
assertTrue("Target method name must be handleChunk, got: " + targetMethodName,
|
||||
"handleChunk".equals(targetMethodName));
|
||||
|
||||
ChunkHandler targetObject = (ChunkHandler) TestUtils.getPropertyValue(serviceActivatorFactoryBean, "targetObject");
|
||||
ChunkHandler targetObject = (ChunkHandler) TestUtils.getPropertyValue(serviceActivatorFactoryBean,
|
||||
"targetObject");
|
||||
assertNotNull("Target object must not be null", targetObject);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test
|
||||
public void testRemoteChunkingWorkerParserWithProcessorNotDefined() {
|
||||
ApplicationContext applicationContext =
|
||||
new ClassPathXmlApplicationContext("/org/springframework/batch/integration/config/xml/RemoteChunkingWorkerParserNoProcessorTests.xml");
|
||||
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
|
||||
"/org/springframework/batch/integration/config/xml/RemoteChunkingWorkerParserNoProcessorTests.xml");
|
||||
|
||||
ChunkHandler chunkHandler = applicationContext.getBean(ChunkProcessorChunkHandler.class);
|
||||
ChunkProcessor chunkProcessor = (SimpleChunkProcessor) TestUtils.getPropertyValue(chunkHandler, "chunkProcessor");
|
||||
ChunkProcessor chunkProcessor = (SimpleChunkProcessor) TestUtils.getPropertyValue(chunkHandler,
|
||||
"chunkProcessor");
|
||||
assertNotNull("ChunkProcessor must not be null", chunkProcessor);
|
||||
|
||||
ItemProcessor<String, String> itemProcessor = (ItemProcessor<String, String>) TestUtils.getPropertyValue(chunkProcessor, "itemProcessor");
|
||||
ItemProcessor<String, String> itemProcessor = (ItemProcessor<String, String>) TestUtils
|
||||
.getPropertyValue(chunkProcessor, "itemProcessor");
|
||||
assertNotNull("ChunkProcessor ItemWriter must not be null", itemProcessor);
|
||||
assertTrue("Got wrong instance of ItemProcessor", itemProcessor instanceof PassThroughItemProcessor);
|
||||
}
|
||||
@@ -105,15 +112,18 @@ public class RemoteChunkingParserTests {
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test
|
||||
public void testRemoteChunkingManagerParser() {
|
||||
ApplicationContext applicationContext =
|
||||
new ClassPathXmlApplicationContext("/org/springframework/batch/integration/config/xml/RemoteChunkingManagerParserTests.xml");
|
||||
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
|
||||
"/org/springframework/batch/integration/config/xml/RemoteChunkingManagerParserTests.xml");
|
||||
|
||||
ItemWriter itemWriter = applicationContext.getBean("itemWriter", ChunkMessageChannelItemWriter.class);
|
||||
assertNotNull("Messaging template must not be null", TestUtils.getPropertyValue(itemWriter, "messagingGateway"));
|
||||
assertNotNull("Messaging template must not be null",
|
||||
TestUtils.getPropertyValue(itemWriter, "messagingGateway"));
|
||||
assertNotNull("Reply channel must not be null", TestUtils.getPropertyValue(itemWriter, "replyChannel"));
|
||||
|
||||
FactoryBean<ChunkHandler> remoteChunkingHandlerFactoryBean = applicationContext.getBean(RemoteChunkHandlerFactoryBean.class);
|
||||
assertNotNull("Chunk writer must not be null", TestUtils.getPropertyValue(remoteChunkingHandlerFactoryBean, "chunkWriter"));
|
||||
FactoryBean<ChunkHandler> remoteChunkingHandlerFactoryBean = applicationContext
|
||||
.getBean(RemoteChunkHandlerFactoryBean.class);
|
||||
assertNotNull("Chunk writer must not be null",
|
||||
TestUtils.getPropertyValue(remoteChunkingHandlerFactoryBean, "chunkWriter"));
|
||||
assertNotNull("Step must not be null", TestUtils.getPropertyValue(remoteChunkingHandlerFactoryBean, "step"));
|
||||
}
|
||||
|
||||
@@ -121,13 +131,16 @@ public class RemoteChunkingParserTests {
|
||||
public void testRemoteChunkingManagerIdAttrAssert() throws Exception {
|
||||
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
|
||||
applicationContext.setValidating(false);
|
||||
applicationContext.setConfigLocation("/org/springframework/batch/integration/config/xml/RemoteChunkingManagerParserMissingIdAttrTests.xml");
|
||||
applicationContext.setConfigLocation(
|
||||
"/org/springframework/batch/integration/config/xml/RemoteChunkingManagerParserMissingIdAttrTests.xml");
|
||||
|
||||
try {
|
||||
applicationContext.refresh();
|
||||
fail();
|
||||
} catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException", e.getCause() instanceof IllegalArgumentException);
|
||||
}
|
||||
catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException",
|
||||
e.getCause() instanceof IllegalArgumentException);
|
||||
|
||||
IllegalArgumentException iae = (IllegalArgumentException) e.getCause();
|
||||
|
||||
@@ -140,17 +153,21 @@ public class RemoteChunkingParserTests {
|
||||
public void testRemoteChunkingManagerMessageTemplateAttrAssert() throws Exception {
|
||||
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
|
||||
applicationContext.setValidating(false);
|
||||
applicationContext.setConfigLocation("/org/springframework/batch/integration/config/xml/RemoteChunkingManagerParserMissingMessageTemplateAttrTests.xml");
|
||||
applicationContext.setConfigLocation(
|
||||
"/org/springframework/batch/integration/config/xml/RemoteChunkingManagerParserMissingMessageTemplateAttrTests.xml");
|
||||
|
||||
try {
|
||||
applicationContext.refresh();
|
||||
fail();
|
||||
} catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException", e.getCause() instanceof IllegalArgumentException);
|
||||
}
|
||||
catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException",
|
||||
e.getCause() instanceof IllegalArgumentException);
|
||||
|
||||
IllegalArgumentException iae = (IllegalArgumentException) e.getCause();
|
||||
|
||||
assertTrue("Expected: " + "The message-template attribute must be specified" + " but got: " + iae.getMessage(),
|
||||
assertTrue(
|
||||
"Expected: " + "The message-template attribute must be specified" + " but got: " + iae.getMessage(),
|
||||
"The message-template attribute must be specified".equals(iae.getMessage()));
|
||||
}
|
||||
}
|
||||
@@ -159,13 +176,16 @@ public class RemoteChunkingParserTests {
|
||||
public void testRemoteChunkingManagerStepAttrAssert() throws Exception {
|
||||
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
|
||||
applicationContext.setValidating(false);
|
||||
applicationContext.setConfigLocation("/org/springframework/batch/integration/config/xml/RemoteChunkingManagerParserMissingStepAttrTests.xml");
|
||||
applicationContext.setConfigLocation(
|
||||
"/org/springframework/batch/integration/config/xml/RemoteChunkingManagerParserMissingStepAttrTests.xml");
|
||||
|
||||
try {
|
||||
applicationContext.refresh();
|
||||
fail();
|
||||
} catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException", e.getCause() instanceof IllegalArgumentException);
|
||||
}
|
||||
catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException",
|
||||
e.getCause() instanceof IllegalArgumentException);
|
||||
|
||||
IllegalArgumentException iae = (IllegalArgumentException) e.getCause();
|
||||
|
||||
@@ -178,13 +198,16 @@ public class RemoteChunkingParserTests {
|
||||
public void testRemoteChunkingManagerReplyChannelAttrAssert() throws Exception {
|
||||
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
|
||||
applicationContext.setValidating(false);
|
||||
applicationContext.setConfigLocation("/org/springframework/batch/integration/config/xml/RemoteChunkingManagerParserMissingReplyChannelAttrTests.xml");
|
||||
applicationContext.setConfigLocation(
|
||||
"/org/springframework/batch/integration/config/xml/RemoteChunkingManagerParserMissingReplyChannelAttrTests.xml");
|
||||
|
||||
try {
|
||||
applicationContext.refresh();
|
||||
fail();
|
||||
} catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException", e.getCause() instanceof IllegalArgumentException);
|
||||
}
|
||||
catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException",
|
||||
e.getCause() instanceof IllegalArgumentException);
|
||||
|
||||
IllegalArgumentException iae = (IllegalArgumentException) e.getCause();
|
||||
|
||||
@@ -197,13 +220,16 @@ public class RemoteChunkingParserTests {
|
||||
public void testRemoteChunkingWorkerIdAttrAssert() throws Exception {
|
||||
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
|
||||
applicationContext.setValidating(false);
|
||||
applicationContext.setConfigLocation("/org/springframework/batch/integration/config/xml/RemoteChunkingWorkerParserMissingIdAttrTests.xml");
|
||||
applicationContext.setConfigLocation(
|
||||
"/org/springframework/batch/integration/config/xml/RemoteChunkingWorkerParserMissingIdAttrTests.xml");
|
||||
|
||||
try {
|
||||
applicationContext.refresh();
|
||||
fail();
|
||||
} catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException", e.getCause() instanceof IllegalArgumentException);
|
||||
}
|
||||
catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException",
|
||||
e.getCause() instanceof IllegalArgumentException);
|
||||
|
||||
IllegalArgumentException iae = (IllegalArgumentException) e.getCause();
|
||||
|
||||
@@ -216,13 +242,16 @@ public class RemoteChunkingParserTests {
|
||||
public void testRemoteChunkingWorkerInputChannelAttrAssert() throws Exception {
|
||||
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
|
||||
applicationContext.setValidating(false);
|
||||
applicationContext.setConfigLocation("/org/springframework/batch/integration/config/xml/RemoteChunkingWorkerParserMissingInputChannelAttrTests.xml");
|
||||
applicationContext.setConfigLocation(
|
||||
"/org/springframework/batch/integration/config/xml/RemoteChunkingWorkerParserMissingInputChannelAttrTests.xml");
|
||||
|
||||
try {
|
||||
applicationContext.refresh();
|
||||
fail();
|
||||
} catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException", e.getCause() instanceof IllegalArgumentException);
|
||||
}
|
||||
catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException",
|
||||
e.getCause() instanceof IllegalArgumentException);
|
||||
|
||||
IllegalArgumentException iae = (IllegalArgumentException) e.getCause();
|
||||
|
||||
@@ -235,13 +264,16 @@ public class RemoteChunkingParserTests {
|
||||
public void testRemoteChunkingWorkerItemWriterAttrAssert() throws Exception {
|
||||
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
|
||||
applicationContext.setValidating(false);
|
||||
applicationContext.setConfigLocation("/org/springframework/batch/integration/config/xml/RemoteChunkingWorkerParserMissingItemWriterAttrTests.xml");
|
||||
applicationContext.setConfigLocation(
|
||||
"/org/springframework/batch/integration/config/xml/RemoteChunkingWorkerParserMissingItemWriterAttrTests.xml");
|
||||
|
||||
try {
|
||||
applicationContext.refresh();
|
||||
fail();
|
||||
} catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException", e.getCause() instanceof IllegalArgumentException);
|
||||
}
|
||||
catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException",
|
||||
e.getCause() instanceof IllegalArgumentException);
|
||||
|
||||
IllegalArgumentException iae = (IllegalArgumentException) e.getCause();
|
||||
|
||||
@@ -254,33 +286,42 @@ public class RemoteChunkingParserTests {
|
||||
public void testRemoteChunkingWorkerOutputChannelAttrAssert() throws Exception {
|
||||
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
|
||||
applicationContext.setValidating(false);
|
||||
applicationContext.setConfigLocation("/org/springframework/batch/integration/config/xml/RemoteChunkingWorkerParserMissingOutputChannelAttrTests.xml");
|
||||
applicationContext.setConfigLocation(
|
||||
"/org/springframework/batch/integration/config/xml/RemoteChunkingWorkerParserMissingOutputChannelAttrTests.xml");
|
||||
|
||||
try {
|
||||
applicationContext.refresh();
|
||||
fail();
|
||||
} catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException", e.getCause() instanceof IllegalArgumentException);
|
||||
}
|
||||
catch (BeanDefinitionStoreException e) {
|
||||
assertTrue("Nested exception must be of type IllegalArgumentException",
|
||||
e.getCause() instanceof IllegalArgumentException);
|
||||
|
||||
IllegalArgumentException iae = (IllegalArgumentException) e.getCause();
|
||||
|
||||
assertTrue("Expected: " + "The output-channel attribute must be specified" + " but got: " + iae.getMessage(),
|
||||
assertTrue(
|
||||
"Expected: " + "The output-channel attribute must be specified" + " but got: " + iae.getMessage(),
|
||||
"The output-channel attribute must be specified".equals(iae.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private static class Writer implements ItemWriter<String> {
|
||||
|
||||
@Override
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
//
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Processor implements ItemProcessor<String, String> {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String process(String item) throws Exception {
|
||||
return item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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 static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration()
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class FileToMessagesJobIntegrationTests implements MessageHandler {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("requests")
|
||||
private SubscribableChannel requests;
|
||||
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
int count = 0;
|
||||
|
||||
public void handleMessage(Message<?> message) {
|
||||
count++;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
requests.subscribe(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileSent() throws Exception {
|
||||
|
||||
JobExecution execution = jobLauncher.run(job, new JobParametersBuilder().addLong("time.stamp",
|
||||
System.currentTimeMillis()).toJobParameters());
|
||||
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
|
||||
// 2 chunks sent to channel (5 items and commit-interval=3)
|
||||
assertEquals(2, count);
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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 static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration()
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class FileToMessagesJobIntegrationTests implements MessageHandler {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("requests")
|
||||
private SubscribableChannel requests;
|
||||
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
int count = 0;
|
||||
|
||||
public void handleMessage(Message<?> message) {
|
||||
count++;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
requests.subscribe(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileSent() throws Exception {
|
||||
|
||||
JobExecution execution = jobLauncher.run(job,
|
||||
new JobParametersBuilder().addLong("time.stamp", System.currentTimeMillis()).toJobParameters());
|
||||
assertEquals(BatchStatus.COMPLETED, execution.getStatus());
|
||||
// 2 chunks sent to channel (5 items and commit-interval=3)
|
||||
assertEquals(2, count);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,80 +1,80 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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 static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.Splitter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration()
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@MessageEndpoint
|
||||
public class ResourceSplitterIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("resources")
|
||||
private MessageChannel resources;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("requests")
|
||||
private PollableChannel requests;
|
||||
|
||||
/*
|
||||
* This is so cool (but see INT-190)...
|
||||
*
|
||||
* The incoming message is a Resource pattern, and it is converted to the
|
||||
* correct payload type with Spring's default strategy
|
||||
*/
|
||||
@Splitter(inputChannel = "resources", outputChannel = "requests")
|
||||
public Resource[] handle(Resource[] message) {
|
||||
List<Resource> list = Arrays.asList(message);
|
||||
System.err.println(list);
|
||||
return message;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
@Ignore //FIXME
|
||||
// This broke with Integration 2.0 in a milestone, so watch out when upgrading...
|
||||
public void testVanillaConversion() throws Exception {
|
||||
resources.send(new GenericMessage<>("classpath:*-context.xml"));
|
||||
Message<Resource> message = (Message<Resource>) requests.receive(200L);
|
||||
assertNotNull(message);
|
||||
message = (Message<Resource>) requests.receive(100L);
|
||||
assertNotNull(message);
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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 static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.Splitter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration()
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@MessageEndpoint
|
||||
public class ResourceSplitterIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("resources")
|
||||
private MessageChannel resources;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("requests")
|
||||
private PollableChannel requests;
|
||||
|
||||
/*
|
||||
* This is so cool (but see INT-190)...
|
||||
*
|
||||
* The incoming message is a Resource pattern, and it is converted to the correct
|
||||
* payload type with Spring's default strategy
|
||||
*/
|
||||
@Splitter(inputChannel = "resources", outputChannel = "requests")
|
||||
public Resource[] handle(Resource[] message) {
|
||||
List<Resource> list = Arrays.asList(message);
|
||||
System.err.println(list);
|
||||
return message;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
@Ignore // FIXME
|
||||
// This broke with Integration 2.0 in a milestone, so watch out when upgrading...
|
||||
public void testVanillaConversion() throws Exception {
|
||||
resources.send(new GenericMessage<>("classpath:*-context.xml"));
|
||||
Message<Resource> message = (Message<Resource>) requests.receive(200L);
|
||||
assertNotNull(message);
|
||||
message = (Message<Resource>) requests.receive(100L);
|
||||
assertNotNull(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,143 +1,147 @@
|
||||
/*
|
||||
* Copyright 2006-2021 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
|
||||
*
|
||||
* https://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.item;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.integration.annotation.Splitter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* Test case showing the use of a MessagingGateway to provide an ItemWriter or
|
||||
* ItemProcessor to Spring Batch that is hooked directly into a Spring
|
||||
* Integration MessageChannel.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Mahmoud Ben Hassine
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class MessagingGatewayIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private ItemProcessor<String, String> processor;
|
||||
|
||||
@Autowired
|
||||
private ItemWriter<String> writer;
|
||||
|
||||
/**
|
||||
* Just for the sake of being able to make assertions.
|
||||
*/
|
||||
@Autowired
|
||||
private EndService service;
|
||||
|
||||
/**
|
||||
* Just for the sake of being able to make assertions.
|
||||
*/
|
||||
@Autowired
|
||||
private SplitService splitter;
|
||||
|
||||
@Test
|
||||
public void testProcessor() throws Exception {
|
||||
String result = processor.process("foo");
|
||||
assertEquals("foo: 0: 1", result);
|
||||
assertNull(processor.process("filter"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriter() throws Exception {
|
||||
writer.write(Arrays.asList("foo", "bar", "spam"));
|
||||
assertEquals(3, splitter.count);
|
||||
assertEquals(3, service.count);
|
||||
}
|
||||
|
||||
/**
|
||||
* This service is wrapped into an ItemProcessor and used to transform
|
||||
* items. This is where the main business processing could take place in a
|
||||
* real application. To suppress output the service activator can just
|
||||
* return null (same as an ItemProcessor) but remember to set the reply
|
||||
* timeout in the gateway.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@MessageEndpoint
|
||||
public static class Activator {
|
||||
private int count;
|
||||
|
||||
@ServiceActivator
|
||||
public String transform(String input) {
|
||||
if (input.equals("filter")) {
|
||||
return null;
|
||||
}
|
||||
return input + ": " + (count++);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Splitter is wrapped into an ItemWriter and used to relay items to its
|
||||
* output channel. This one is completely trivial, it just passes the items
|
||||
* on as they are. More complex splitters might filter or enhance the items
|
||||
* before passing them on.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@MessageEndpoint
|
||||
public static class SplitService {
|
||||
// Just for assertions in the test case
|
||||
private int count;
|
||||
|
||||
@Splitter
|
||||
public List<String> split(List<String> input) {
|
||||
count += input.size();
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is just used to trap the messages sent by the ItemWriter and make an
|
||||
* assertion about them in the test case. In a real application this
|
||||
* would be the output stage and/or business processing.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@MessageEndpoint
|
||||
public static class EndService {
|
||||
private int count;
|
||||
|
||||
@ServiceActivator
|
||||
public void service(String input) {
|
||||
count++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2006-2021 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
|
||||
*
|
||||
* https://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.item;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.annotation.MessageEndpoint;
|
||||
import org.springframework.integration.annotation.ServiceActivator;
|
||||
import org.springframework.integration.annotation.Splitter;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* Test case showing the use of a MessagingGateway to provide an ItemWriter or
|
||||
* ItemProcessor to Spring Batch that is hooked directly into a Spring Integration
|
||||
* MessageChannel.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Mahmoud Ben Hassine
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class MessagingGatewayIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private ItemProcessor<String, String> processor;
|
||||
|
||||
@Autowired
|
||||
private ItemWriter<String> writer;
|
||||
|
||||
/**
|
||||
* Just for the sake of being able to make assertions.
|
||||
*/
|
||||
@Autowired
|
||||
private EndService service;
|
||||
|
||||
/**
|
||||
* Just for the sake of being able to make assertions.
|
||||
*/
|
||||
@Autowired
|
||||
private SplitService splitter;
|
||||
|
||||
@Test
|
||||
public void testProcessor() throws Exception {
|
||||
String result = processor.process("foo");
|
||||
assertEquals("foo: 0: 1", result);
|
||||
assertNull(processor.process("filter"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriter() throws Exception {
|
||||
writer.write(Arrays.asList("foo", "bar", "spam"));
|
||||
assertEquals(3, splitter.count);
|
||||
assertEquals(3, service.count);
|
||||
}
|
||||
|
||||
/**
|
||||
* This service is wrapped into an ItemProcessor and used to transform items. This is
|
||||
* where the main business processing could take place in a real application. To
|
||||
* suppress output the service activator can just return null (same as an
|
||||
* ItemProcessor) but remember to set the reply timeout in the gateway.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@MessageEndpoint
|
||||
public static class Activator {
|
||||
|
||||
private int count;
|
||||
|
||||
@ServiceActivator
|
||||
public String transform(String input) {
|
||||
if (input.equals("filter")) {
|
||||
return null;
|
||||
}
|
||||
return input + ": " + (count++);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The Splitter is wrapped into an ItemWriter and used to relay items to its output
|
||||
* channel. This one is completely trivial, it just passes the items on as they are.
|
||||
* More complex splitters might filter or enhance the items before passing them on.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@MessageEndpoint
|
||||
public static class SplitService {
|
||||
|
||||
// Just for assertions in the test case
|
||||
private int count;
|
||||
|
||||
@Splitter
|
||||
public List<String> split(List<String> input) {
|
||||
count += input.size();
|
||||
return input;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This is just used to trap the messages sent by the ItemWriter and make an assertion
|
||||
* about them in the test case. In a real application this would be the output stage
|
||||
* and/or business processing.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@MessageEndpoint
|
||||
public static class EndService {
|
||||
|
||||
private int count;
|
||||
|
||||
@ServiceActivator
|
||||
public void service(String input) {
|
||||
count++;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
* @since 1.3
|
||||
*
|
||||
@@ -77,7 +76,7 @@ public class JobLaunchingGatewayIntegrationTests {
|
||||
@Before
|
||||
public void setUp() {
|
||||
Object message = "";
|
||||
while (message!=null) {
|
||||
while (message != null) {
|
||||
message = responseChannel.receive(10L);
|
||||
}
|
||||
}
|
||||
@@ -86,8 +85,7 @@ public class JobLaunchingGatewayIntegrationTests {
|
||||
@DirtiesContext
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testNoReply() {
|
||||
GenericMessage<JobLaunchRequest> trigger = new GenericMessage<>(new JobLaunchRequest(job,
|
||||
new JobParameters()));
|
||||
GenericMessage<JobLaunchRequest> trigger = new GenericMessage<>(new JobLaunchRequest(job, new JobParameters()));
|
||||
try {
|
||||
requestChannel.send(trigger);
|
||||
fail();
|
||||
@@ -110,8 +108,8 @@ public class JobLaunchingGatewayIntegrationTests {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put(MessageHeaders.REPLY_CHANNEL, "response");
|
||||
MessageHeaders headers = new MessageHeaders(map);
|
||||
GenericMessage<JobLaunchRequest> trigger = new GenericMessage<>(new JobLaunchRequest(job,
|
||||
builder.toJobParameters()), headers);
|
||||
GenericMessage<JobLaunchRequest> trigger = new GenericMessage<>(
|
||||
new JobLaunchRequest(job, builder.toJobParameters()), headers);
|
||||
requestChannel.send(trigger);
|
||||
Message<JobExecution> executionMessage = (Message<JobExecution>) responseChannel.receive(1000);
|
||||
|
||||
@@ -152,8 +150,8 @@ public class JobLaunchingGatewayIntegrationTests {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put(MessageHeaders.REPLY_CHANNEL, "response");
|
||||
MessageHeaders headers = new MessageHeaders(map);
|
||||
GenericMessage<JobLaunchRequest> trigger = new GenericMessage<>(new JobLaunchRequest(testJob,
|
||||
builder.toJobParameters()), headers);
|
||||
GenericMessage<JobLaunchRequest> trigger = new GenericMessage<>(
|
||||
new JobLaunchRequest(testJob, builder.toJobParameters()), headers);
|
||||
requestChannel.send(trigger);
|
||||
|
||||
Message<JobExecution> executionMessage = (Message<JobExecution>) responseChannel.receive(1000);
|
||||
@@ -166,4 +164,5 @@ public class JobLaunchingGatewayIntegrationTests {
|
||||
assertEquals(ExitStatus.FAILED.getExitCode(), execution.getExitStatus().getExitCode());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
* @since 1.3
|
||||
*
|
||||
@@ -42,12 +41,12 @@ public class JobLaunchingGatewayTests {
|
||||
@Test
|
||||
public void testExceptionRaised() throws Exception {
|
||||
|
||||
final Message<JobLaunchRequest> message = MessageBuilder.withPayload(new JobLaunchRequest(new JobSupport("testJob"),
|
||||
new JobParameters())).build();
|
||||
final Message<JobLaunchRequest> message = MessageBuilder
|
||||
.withPayload(new JobLaunchRequest(new JobSupport("testJob"), new JobParameters())).build();
|
||||
|
||||
final JobLauncher jobLauncher = mock(JobLauncher.class);
|
||||
when(jobLauncher.run(any(Job.class), any(JobParameters.class)))
|
||||
.thenThrow(new JobParametersInvalidException("This is a JobExecutionException."));
|
||||
.thenThrow(new JobParametersInvalidException("This is a JobExecutionException."));
|
||||
|
||||
JobLaunchingGateway jobLaunchingGateway = new JobLaunchingGateway(jobLauncher);
|
||||
|
||||
@@ -62,4 +61,5 @@ public class JobLaunchingGatewayTests {
|
||||
fail("Expecting a MessageHandlingException to be thrown.");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class JobLaunchingMessageHandlerIntegrationTests {
|
||||
@Before
|
||||
public void setUp() {
|
||||
Object message = "";
|
||||
while (message!=null) {
|
||||
while (message != null) {
|
||||
message = responseChannel.receive(10L);
|
||||
}
|
||||
}
|
||||
@@ -52,8 +52,7 @@ public class JobLaunchingMessageHandlerIntegrationTests {
|
||||
@DirtiesContext
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testNoReply() {
|
||||
GenericMessage<JobLaunchRequest> trigger = new GenericMessage<>(new JobLaunchRequest(job,
|
||||
new JobParameters()));
|
||||
GenericMessage<JobLaunchRequest> trigger = new GenericMessage<>(new JobLaunchRequest(job, new JobParameters()));
|
||||
try {
|
||||
requestChannel.send(trigger);
|
||||
}
|
||||
@@ -75,8 +74,8 @@ public class JobLaunchingMessageHandlerIntegrationTests {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put(MessageHeaders.REPLY_CHANNEL, "response");
|
||||
MessageHeaders headers = new MessageHeaders(map);
|
||||
GenericMessage<JobLaunchRequest> trigger = new GenericMessage<>(new JobLaunchRequest(job,
|
||||
builder.toJobParameters()), headers);
|
||||
GenericMessage<JobLaunchRequest> trigger = new GenericMessage<>(
|
||||
new JobLaunchRequest(job, builder.toJobParameters()), headers);
|
||||
requestChannel.send(trigger);
|
||||
Message<JobExecution> executionMessage = (Message<JobExecution>) responseChannel.receive(1000);
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public class JobLaunchingMessageHandlerTests extends AbstractJUnit4SpringContext
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleDelivery() throws Exception{
|
||||
public void testSimpleDelivery() throws Exception {
|
||||
messageHandler.launch(new JobLaunchRequest(new JobSupport("testjob"), null));
|
||||
|
||||
assertEquals("Wrong job count", 1, jobLauncher.jobs.size());
|
||||
@@ -47,7 +47,7 @@ public class JobLaunchingMessageHandlerTests extends AbstractJUnit4SpringContext
|
||||
|
||||
AtomicLong jobId = new AtomicLong();
|
||||
|
||||
public JobExecution run(Job job, JobParameters jobParameters){
|
||||
public JobExecution run(Job job, JobParameters jobParameters) {
|
||||
jobs.add(job);
|
||||
parameters.add(jobParameters);
|
||||
return new JobExecution(new JobInstance(jobId.getAndIncrement(), job.getName()), jobParameters);
|
||||
|
||||
@@ -30,7 +30,8 @@ public class JobRequestConverter {
|
||||
@ServiceActivator
|
||||
public JobLaunchRequest convert(String jobName) {
|
||||
Properties properties = new Properties();
|
||||
return new JobLaunchRequest(new JobSupport(jobName), new DefaultJobParametersConverter().getJobParameters(properties));
|
||||
return new JobLaunchRequest(new JobSupport(jobName),
|
||||
new DefaultJobParametersConverter().getJobParameters(properties));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,57 +1,58 @@
|
||||
package org.springframework.batch.integration.partition;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
|
||||
|
||||
public class BeanFactoryStepLocatorTests {
|
||||
|
||||
private BeanFactoryStepLocator stepLocator = new BeanFactoryStepLocator();
|
||||
private DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
@Test
|
||||
public void testGetStep() throws Exception {
|
||||
beanFactory.registerSingleton("foo", new StubStep("foo"));
|
||||
stepLocator.setBeanFactory(beanFactory);
|
||||
assertNotNull(stepLocator.getStep("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStepNames() throws Exception {
|
||||
beanFactory.registerSingleton("foo", new StubStep("foo"));
|
||||
beanFactory.registerSingleton("bar", new StubStep("bar"));
|
||||
stepLocator.setBeanFactory(beanFactory);
|
||||
assertEquals(2, stepLocator.getStepNames().size());
|
||||
}
|
||||
|
||||
private static final class StubStep implements Step {
|
||||
|
||||
private String name;
|
||||
|
||||
public StubStep(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void execute(StepExecution stepExecution) throws JobInterruptedException {
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getStartLimit() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean isAllowStartIfComplete() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
package org.springframework.batch.integration.partition;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.core.JobInterruptedException;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.StepExecution;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
|
||||
public class BeanFactoryStepLocatorTests {
|
||||
|
||||
private BeanFactoryStepLocator stepLocator = new BeanFactoryStepLocator();
|
||||
|
||||
private DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
||||
|
||||
@Test
|
||||
public void testGetStep() throws Exception {
|
||||
beanFactory.registerSingleton("foo", new StubStep("foo"));
|
||||
stepLocator.setBeanFactory(beanFactory);
|
||||
assertNotNull(stepLocator.getStep("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStepNames() throws Exception {
|
||||
beanFactory.registerSingleton("foo", new StubStep("foo"));
|
||||
beanFactory.registerSingleton("bar", new StubStep("bar"));
|
||||
stepLocator.setBeanFactory(beanFactory);
|
||||
assertEquals(2, stepLocator.getStepNames().size());
|
||||
}
|
||||
|
||||
private static final class StubStep implements Step {
|
||||
|
||||
private String name;
|
||||
|
||||
public StubStep(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void execute(StepExecution stepExecution) throws JobInterruptedException {
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getStartLimit() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public boolean isAllowStartIfComplete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,59 +1,59 @@
|
||||
package org.springframework.batch.integration.partition;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.ItemStreamException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link ItemReader} with hard-coded input data.
|
||||
*/
|
||||
public class ExampleItemReader implements ItemReader<String>, ItemStream {
|
||||
|
||||
private Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private String[] input = { "Hello", "world!", "Go", "on", "punk", "make", "my", "day!" };
|
||||
|
||||
private int index = 0;
|
||||
|
||||
public static volatile boolean fail = false;
|
||||
|
||||
/**
|
||||
* Reads next record from input
|
||||
*/
|
||||
@Nullable
|
||||
public String read() throws Exception {
|
||||
if (index >= input.length) {
|
||||
return null;
|
||||
}
|
||||
logger.info(String.format("Processing input index=%s, item=%s, in (%s)", index, input[index], this));
|
||||
if (fail && index == 4) {
|
||||
synchronized (ExampleItemReader.class) {
|
||||
if (fail) {
|
||||
// Only fail once per flag setting...
|
||||
fail = false;
|
||||
logger.info(String.format("Throwing exception index=%s, item=%s, in (%s)", index, input[index],
|
||||
this));
|
||||
index++;
|
||||
throw new RuntimeException("Planned failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
return input[index++];
|
||||
}
|
||||
|
||||
public void close() throws ItemStreamException {
|
||||
}
|
||||
|
||||
public void open(ExecutionContext executionContext) throws ItemStreamException {
|
||||
index = (int) executionContext.getLong("POSITION", 0);
|
||||
}
|
||||
|
||||
public void update(ExecutionContext executionContext) throws ItemStreamException {
|
||||
executionContext.putLong("POSITION", index);
|
||||
}
|
||||
|
||||
}
|
||||
package org.springframework.batch.integration.partition;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
import org.springframework.batch.item.ItemReader;
|
||||
import org.springframework.batch.item.ItemStream;
|
||||
import org.springframework.batch.item.ItemStreamException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link ItemReader} with hard-coded input data.
|
||||
*/
|
||||
public class ExampleItemReader implements ItemReader<String>, ItemStream {
|
||||
|
||||
private Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private String[] input = { "Hello", "world!", "Go", "on", "punk", "make", "my", "day!" };
|
||||
|
||||
private int index = 0;
|
||||
|
||||
public static volatile boolean fail = false;
|
||||
|
||||
/**
|
||||
* Reads next record from input
|
||||
*/
|
||||
@Nullable
|
||||
public String read() throws Exception {
|
||||
if (index >= input.length) {
|
||||
return null;
|
||||
}
|
||||
logger.info(String.format("Processing input index=%s, item=%s, in (%s)", index, input[index], this));
|
||||
if (fail && index == 4) {
|
||||
synchronized (ExampleItemReader.class) {
|
||||
if (fail) {
|
||||
// Only fail once per flag setting...
|
||||
fail = false;
|
||||
logger.info(
|
||||
String.format("Throwing exception index=%s, item=%s, in (%s)", index, input[index], this));
|
||||
index++;
|
||||
throw new RuntimeException("Planned failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
return input[index++];
|
||||
}
|
||||
|
||||
public void close() throws ItemStreamException {
|
||||
}
|
||||
|
||||
public void open(ExecutionContext executionContext) throws ItemStreamException {
|
||||
index = (int) executionContext.getLong("POSITION", 0);
|
||||
}
|
||||
|
||||
public void update(ExecutionContext executionContext) throws ItemStreamException {
|
||||
executionContext.putLong("POSITION", index);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,70 +1,70 @@
|
||||
package org.springframework.batch.integration.partition;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
|
||||
public class ExampleItemReaderTests {
|
||||
|
||||
private ExampleItemReader reader = new ExampleItemReader();
|
||||
|
||||
@Before
|
||||
@After
|
||||
public void ensureFailFlagUnset() {
|
||||
ExampleItemReader.fail = false;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead() throws Exception {
|
||||
int count = 0;
|
||||
while (reader.read()!=null) {
|
||||
count++;
|
||||
}
|
||||
assertEquals(8, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpen() throws Exception {
|
||||
ExecutionContext context = new ExecutionContext();
|
||||
for (int i=0; i<4; i++) {
|
||||
reader.read();
|
||||
}
|
||||
reader.update(context);
|
||||
reader.open(context);
|
||||
int count = 0;
|
||||
while (reader.read()!=null) {
|
||||
count++;
|
||||
}
|
||||
assertEquals(4, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailAndRestart() throws Exception {
|
||||
ExecutionContext context = new ExecutionContext();
|
||||
ExampleItemReader.fail = true;
|
||||
for (int i=0; i<4; i++) {
|
||||
reader.read();
|
||||
reader.update(context);
|
||||
}
|
||||
try {
|
||||
reader.read();
|
||||
reader.update(context);
|
||||
fail("Expected Exception");
|
||||
}
|
||||
catch (Exception e) {
|
||||
// expected
|
||||
assertEquals("Planned failure", e.getMessage());
|
||||
}
|
||||
assertFalse(ExampleItemReader.fail);
|
||||
reader.open(context);
|
||||
int count = 0;
|
||||
while (reader.read()!=null) {
|
||||
count++;
|
||||
}
|
||||
assertEquals(4, count);
|
||||
}
|
||||
|
||||
}
|
||||
package org.springframework.batch.integration.partition;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.batch.item.ExecutionContext;
|
||||
|
||||
public class ExampleItemReaderTests {
|
||||
|
||||
private ExampleItemReader reader = new ExampleItemReader();
|
||||
|
||||
@Before
|
||||
@After
|
||||
public void ensureFailFlagUnset() {
|
||||
ExampleItemReader.fail = false;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead() throws Exception {
|
||||
int count = 0;
|
||||
while (reader.read() != null) {
|
||||
count++;
|
||||
}
|
||||
assertEquals(8, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpen() throws Exception {
|
||||
ExecutionContext context = new ExecutionContext();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
reader.read();
|
||||
}
|
||||
reader.update(context);
|
||||
reader.open(context);
|
||||
int count = 0;
|
||||
while (reader.read() != null) {
|
||||
count++;
|
||||
}
|
||||
assertEquals(4, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailAndRestart() throws Exception {
|
||||
ExecutionContext context = new ExecutionContext();
|
||||
ExampleItemReader.fail = true;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
reader.read();
|
||||
reader.update(context);
|
||||
}
|
||||
try {
|
||||
reader.read();
|
||||
reader.update(context);
|
||||
fail("Expected Exception");
|
||||
}
|
||||
catch (Exception e) {
|
||||
// expected
|
||||
assertEquals("Planned failure", e.getMessage());
|
||||
}
|
||||
assertFalse(ExampleItemReader.fail);
|
||||
reader.open(context);
|
||||
int count = 0;
|
||||
while (reader.read() != null) {
|
||||
count++;
|
||||
}
|
||||
assertEquals(4, count);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
package org.springframework.batch.integration.partition;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
|
||||
/**
|
||||
* Dummy {@link ItemWriter} which only logs data it receives.
|
||||
*/
|
||||
public class ExampleItemWriter implements ItemWriter<Object> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ExampleItemWriter.class);
|
||||
|
||||
/**
|
||||
* @see ItemWriter#write(List)
|
||||
*/
|
||||
public void write(List<? extends Object> data) throws Exception {
|
||||
log.info(data);
|
||||
}
|
||||
|
||||
}
|
||||
package org.springframework.batch.integration.partition;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
|
||||
/**
|
||||
* Dummy {@link ItemWriter} which only logs data it receives.
|
||||
*/
|
||||
public class ExampleItemWriter implements ItemWriter<Object> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(ExampleItemWriter.class);
|
||||
|
||||
/**
|
||||
* @see ItemWriter#write(List)
|
||||
*/
|
||||
public void write(List<? extends Object> data) throws Exception {
|
||||
log.info(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*
|
||||
* https://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.
|
||||
@@ -37,7 +37,7 @@ import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@@ -68,14 +68,17 @@ public class JmsIntegrationTests {
|
||||
int after = jobInstances.size();
|
||||
assertEquals(1, after - before);
|
||||
JobExecution jobExecution = jobExplorer.getJobExecutions(jobInstances.get(jobInstances.size() - 1)).get(0);
|
||||
assertEquals(jobExecution.getExitStatus().getExitDescription(), BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
assertEquals(jobExecution.getExitStatus().getExitDescription(), BatchStatus.COMPLETED,
|
||||
jobExecution.getStatus());
|
||||
assertEquals(3, jobExecution.getStepExecutions().size());
|
||||
for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
|
||||
// BATCH-1703: we are using a map dao so the step executions in the job execution are old and we need to
|
||||
// BATCH-1703: we are using a map dao so the step executions in the job
|
||||
// execution are old and we need to
|
||||
// pull them back out of the repository...
|
||||
stepExecution = jobExplorer.getStepExecution(jobExecution.getId(), stepExecution.getId());
|
||||
logger.debug("" + stepExecution);
|
||||
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Will Schipp
|
||||
* @author Michael Minella
|
||||
* @author Mahmoud Ben Hassine
|
||||
@@ -57,68 +56,71 @@ public class MessageChannelPartitionHandlerTests {
|
||||
|
||||
@Test
|
||||
public void testNoPartitions() throws Exception {
|
||||
//execute with no default set
|
||||
// execute with no default set
|
||||
messageChannelPartitionHandler = new MessageChannelPartitionHandler();
|
||||
//mock
|
||||
// mock
|
||||
StepExecution managerStepExecution = mock(StepExecution.class);
|
||||
StepExecutionSplitter stepExecutionSplitter = mock(StepExecutionSplitter.class);
|
||||
|
||||
//execute
|
||||
Collection<StepExecution> executions = messageChannelPartitionHandler.handle(stepExecutionSplitter, managerStepExecution);
|
||||
//verify
|
||||
// execute
|
||||
Collection<StepExecution> executions = messageChannelPartitionHandler.handle(stepExecutionSplitter,
|
||||
managerStepExecution);
|
||||
// verify
|
||||
assertTrue(executions.isEmpty());
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Test
|
||||
public void testHandleNoReply() throws Exception {
|
||||
//execute with no default set
|
||||
// execute with no default set
|
||||
messageChannelPartitionHandler = new MessageChannelPartitionHandler();
|
||||
//mock
|
||||
// mock
|
||||
StepExecution managerStepExecution = mock(StepExecution.class);
|
||||
StepExecutionSplitter stepExecutionSplitter = mock(StepExecutionSplitter.class);
|
||||
MessagingTemplate operations = mock(MessagingTemplate.class);
|
||||
Message message = mock(Message.class);
|
||||
//when
|
||||
// when
|
||||
HashSet<StepExecution> stepExecutions = new HashSet<>();
|
||||
stepExecutions.add(new StepExecution("step1", new JobExecution(5L)));
|
||||
when(stepExecutionSplitter.split(any(StepExecution.class), eq(1))).thenReturn(stepExecutions);
|
||||
when(message.getPayload()).thenReturn(Collections.emptyList());
|
||||
when(operations.receive((PollableChannel) any())).thenReturn(message);
|
||||
//set
|
||||
// set
|
||||
messageChannelPartitionHandler.setMessagingOperations(operations);
|
||||
|
||||
//execute
|
||||
Collection<StepExecution> executions = messageChannelPartitionHandler.handle(stepExecutionSplitter, managerStepExecution);
|
||||
//verify
|
||||
// execute
|
||||
Collection<StepExecution> executions = messageChannelPartitionHandler.handle(stepExecutionSplitter,
|
||||
managerStepExecution);
|
||||
// verify
|
||||
assertNotNull(executions);
|
||||
assertTrue(executions.isEmpty());
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Test
|
||||
public void testHandleWithReplyChannel() throws Exception {
|
||||
//execute with no default set
|
||||
// execute with no default set
|
||||
messageChannelPartitionHandler = new MessageChannelPartitionHandler();
|
||||
//mock
|
||||
// mock
|
||||
StepExecution managerStepExecution = mock(StepExecution.class);
|
||||
StepExecutionSplitter stepExecutionSplitter = mock(StepExecutionSplitter.class);
|
||||
MessagingTemplate operations = mock(MessagingTemplate.class);
|
||||
Message message = mock(Message.class);
|
||||
PollableChannel replyChannel = mock(PollableChannel.class);
|
||||
//when
|
||||
// when
|
||||
HashSet<StepExecution> stepExecutions = new HashSet<>();
|
||||
stepExecutions.add(new StepExecution("step1", new JobExecution(5L)));
|
||||
when(stepExecutionSplitter.split(any(StepExecution.class), eq(1))).thenReturn(stepExecutions);
|
||||
when(message.getPayload()).thenReturn(Collections.emptyList());
|
||||
when(operations.receive(replyChannel)).thenReturn(message);
|
||||
//set
|
||||
// set
|
||||
messageChannelPartitionHandler.setMessagingOperations(operations);
|
||||
messageChannelPartitionHandler.setReplyChannel(replyChannel);
|
||||
|
||||
//execute
|
||||
Collection<StepExecution> executions = messageChannelPartitionHandler.handle(stepExecutionSplitter, managerStepExecution);
|
||||
//verify
|
||||
// execute
|
||||
Collection<StepExecution> executions = messageChannelPartitionHandler.handle(stepExecutionSplitter,
|
||||
managerStepExecution);
|
||||
// verify
|
||||
assertNotNull(executions);
|
||||
assertTrue(executions.isEmpty());
|
||||
|
||||
@@ -127,36 +129,36 @@ public class MessageChannelPartitionHandlerTests {
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test(expected = MessageTimeoutException.class)
|
||||
public void messageReceiveTimeout() throws Exception {
|
||||
//execute with no default set
|
||||
// execute with no default set
|
||||
messageChannelPartitionHandler = new MessageChannelPartitionHandler();
|
||||
//mock
|
||||
// mock
|
||||
StepExecution managerStepExecution = mock(StepExecution.class);
|
||||
StepExecutionSplitter stepExecutionSplitter = mock(StepExecutionSplitter.class);
|
||||
MessagingTemplate operations = mock(MessagingTemplate.class);
|
||||
Message message = mock(Message.class);
|
||||
//when
|
||||
// when
|
||||
HashSet<StepExecution> stepExecutions = new HashSet<>();
|
||||
stepExecutions.add(new StepExecution("step1", new JobExecution(5L)));
|
||||
when(stepExecutionSplitter.split(any(StepExecution.class), eq(1))).thenReturn(stepExecutions);
|
||||
when(message.getPayload()).thenReturn(Collections.emptyList());
|
||||
//set
|
||||
// set
|
||||
messageChannelPartitionHandler.setMessagingOperations(operations);
|
||||
|
||||
//execute
|
||||
// execute
|
||||
messageChannelPartitionHandler.handle(stepExecutionSplitter, managerStepExecution);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleWithJobRepositoryPolling() throws Exception {
|
||||
//execute with no default set
|
||||
// execute with no default set
|
||||
messageChannelPartitionHandler = new MessageChannelPartitionHandler();
|
||||
//mock
|
||||
// mock
|
||||
JobExecution jobExecution = new JobExecution(5L, new JobParameters());
|
||||
StepExecution managerStepExecution = new StepExecution("step1", jobExecution, 1L);
|
||||
StepExecutionSplitter stepExecutionSplitter = mock(StepExecutionSplitter.class);
|
||||
MessagingTemplate operations = mock(MessagingTemplate.class);
|
||||
JobExplorer jobExplorer = mock(JobExplorer.class);
|
||||
//when
|
||||
// when
|
||||
HashSet<StepExecution> stepExecutions = new HashSet<>();
|
||||
StepExecution partition1 = new StepExecution("step1:partition1", jobExecution, 2L);
|
||||
StepExecution partition2 = new StepExecution("step1:partition2", jobExecution, 3L);
|
||||
@@ -170,39 +172,41 @@ public class MessageChannelPartitionHandlerTests {
|
||||
stepExecutions.add(partition2);
|
||||
stepExecutions.add(partition3);
|
||||
when(stepExecutionSplitter.split(any(StepExecution.class), eq(1))).thenReturn(stepExecutions);
|
||||
when(jobExplorer.getStepExecution(eq(5L), any(Long.class))).thenReturn(partition2, partition1, partition3, partition3, partition3, partition3, partition4);
|
||||
when(jobExplorer.getStepExecution(eq(5L), any(Long.class))).thenReturn(partition2, partition1, partition3,
|
||||
partition3, partition3, partition3, partition4);
|
||||
|
||||
//set
|
||||
// set
|
||||
messageChannelPartitionHandler.setMessagingOperations(operations);
|
||||
messageChannelPartitionHandler.setJobExplorer(jobExplorer);
|
||||
messageChannelPartitionHandler.setStepName("step1");
|
||||
messageChannelPartitionHandler.setPollInterval(500L);
|
||||
messageChannelPartitionHandler.afterPropertiesSet();
|
||||
|
||||
//execute
|
||||
Collection<StepExecution> executions = messageChannelPartitionHandler.handle(stepExecutionSplitter, managerStepExecution);
|
||||
//verify
|
||||
// execute
|
||||
Collection<StepExecution> executions = messageChannelPartitionHandler.handle(stepExecutionSplitter,
|
||||
managerStepExecution);
|
||||
// verify
|
||||
assertNotNull(executions);
|
||||
assertEquals(3, executions.size());
|
||||
assertTrue(executions.contains(partition1));
|
||||
assertTrue(executions.contains(partition2));
|
||||
assertTrue(executions.contains(partition4));
|
||||
|
||||
//verify
|
||||
// verify
|
||||
verify(operations, times(3)).send(any(Message.class));
|
||||
}
|
||||
|
||||
@Test(expected = TimeoutException.class)
|
||||
public void testHandleWithJobRepositoryPollingTimeout() throws Exception {
|
||||
//execute with no default set
|
||||
// execute with no default set
|
||||
messageChannelPartitionHandler = new MessageChannelPartitionHandler();
|
||||
//mock
|
||||
// mock
|
||||
JobExecution jobExecution = new JobExecution(5L, new JobParameters());
|
||||
StepExecution managerStepExecution = new StepExecution("step1", jobExecution, 1L);
|
||||
StepExecutionSplitter stepExecutionSplitter = mock(StepExecutionSplitter.class);
|
||||
MessagingTemplate operations = mock(MessagingTemplate.class);
|
||||
JobExplorer jobExplorer = mock(JobExplorer.class);
|
||||
//when
|
||||
// when
|
||||
HashSet<StepExecution> stepExecutions = new HashSet<>();
|
||||
StepExecution partition1 = new StepExecution("step1:partition1", jobExecution, 2L);
|
||||
StepExecution partition2 = new StepExecution("step1:partition2", jobExecution, 3L);
|
||||
@@ -216,14 +220,15 @@ public class MessageChannelPartitionHandlerTests {
|
||||
when(stepExecutionSplitter.split(any(StepExecution.class), eq(1))).thenReturn(stepExecutions);
|
||||
when(jobExplorer.getStepExecution(eq(5L), any(Long.class))).thenReturn(partition2, partition1, partition3);
|
||||
|
||||
//set
|
||||
// set
|
||||
messageChannelPartitionHandler.setMessagingOperations(operations);
|
||||
messageChannelPartitionHandler.setJobExplorer(jobExplorer);
|
||||
messageChannelPartitionHandler.setStepName("step1");
|
||||
messageChannelPartitionHandler.setTimeout(1000L);
|
||||
messageChannelPartitionHandler.afterPropertiesSet();
|
||||
|
||||
//execute
|
||||
// execute
|
||||
messageChannelPartitionHandler.handle(stepExecutionSplitter, managerStepExecution);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@@ -47,7 +47,7 @@ public class PollingIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
|
||||
@Autowired
|
||||
private JobExplorer jobExplorer;
|
||||
|
||||
@@ -62,8 +62,8 @@ public class PollingIntegrationTests {
|
||||
assertNotNull(jobLauncher.run(job, new JobParameters()));
|
||||
List<JobInstance> jobInstances = jobExplorer.getJobInstances(job.getName(), 0, 100);
|
||||
int after = jobInstances.size();
|
||||
assertEquals(1, after-before);
|
||||
JobExecution jobExecution = jobExplorer.getJobExecutions(jobInstances.get(jobInstances.size()-1)).get(0);
|
||||
assertEquals(1, after - before);
|
||||
JobExecution jobExecution = jobExplorer.getJobExecutions(jobInstances.get(jobInstances.size() - 1)).get(0);
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
assertEquals(3, jobExecution.getStepExecutions().size());
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ import static org.springframework.test.util.ReflectionTestUtils.getField;
|
||||
* @author Mahmoud Ben Hassine
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration(classes = {RemotePartitioningManagerStepBuilderTests.BatchConfiguration.class})
|
||||
@ContextConfiguration(classes = { RemotePartitioningManagerStepBuilderTests.BatchConfiguration.class })
|
||||
public class RemotePartitioningManagerStepBuilderTests {
|
||||
|
||||
@Autowired
|
||||
@@ -121,15 +121,14 @@ public class RemotePartitioningManagerStepBuilderTests {
|
||||
public void eitherOutputChannelOrMessagingTemplateMustBeProvided() {
|
||||
// given
|
||||
RemotePartitioningManagerStepBuilder builder = new RemotePartitioningManagerStepBuilder("step")
|
||||
.outputChannel(new DirectChannel())
|
||||
.messagingTemplate(new MessagingTemplate());
|
||||
.outputChannel(new DirectChannel()).messagingTemplate(new MessagingTemplate());
|
||||
|
||||
// when
|
||||
final Exception expectedException = Assert.assertThrows(IllegalStateException.class,
|
||||
builder::build);
|
||||
final Exception expectedException = Assert.assertThrows(IllegalStateException.class, builder::build);
|
||||
|
||||
// then
|
||||
assertThat(expectedException).hasMessage("You must specify either an outputChannel or a messagingTemplate but not both.");
|
||||
assertThat(expectedException)
|
||||
.hasMessage("You must specify either an outputChannel or a messagingTemplate but not both.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -143,11 +142,10 @@ public class RemotePartitioningManagerStepBuilderTests {
|
||||
() -> builder.partitionHandler(partitionHandler));
|
||||
|
||||
// then
|
||||
assertThat(expectedException).hasMessage("When configuring a manager step " +
|
||||
"for remote partitioning using the RemotePartitioningManagerStepBuilder, " +
|
||||
"the partition handler will be automatically set to an instance " +
|
||||
"of MessageChannelPartitionHandler. The partition handler must " +
|
||||
"not be provided in this case.");
|
||||
assertThat(expectedException).hasMessage("When configuring a manager step "
|
||||
+ "for remote partitioning using the RemotePartitioningManagerStepBuilder, "
|
||||
+ "the partition handler will be automatically set to an instance "
|
||||
+ "of MessageChannelPartitionHandler. The partition handler must " + "not be provided in this case.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -159,20 +157,14 @@ public class RemotePartitioningManagerStepBuilderTests {
|
||||
long pollInterval = 5000L;
|
||||
DirectChannel outputChannel = new DirectChannel();
|
||||
Partitioner partitioner = Mockito.mock(Partitioner.class);
|
||||
StepExecutionAggregator stepExecutionAggregator = (result, executions) -> { };
|
||||
StepExecutionAggregator stepExecutionAggregator = (result, executions) -> {
|
||||
};
|
||||
|
||||
// when
|
||||
Step step = new RemotePartitioningManagerStepBuilder("managerStep")
|
||||
.repository(jobRepository)
|
||||
.outputChannel(outputChannel)
|
||||
.partitioner("workerStep", partitioner)
|
||||
.gridSize(gridSize)
|
||||
.pollInterval(pollInterval)
|
||||
.timeout(timeout)
|
||||
.startLimit(startLimit)
|
||||
.aggregator(stepExecutionAggregator)
|
||||
.allowStartIfComplete(true)
|
||||
.build();
|
||||
Step step = new RemotePartitioningManagerStepBuilder("managerStep").repository(jobRepository)
|
||||
.outputChannel(outputChannel).partitioner("workerStep", partitioner).gridSize(gridSize)
|
||||
.pollInterval(pollInterval).timeout(timeout).startLimit(startLimit).aggregator(stepExecutionAggregator)
|
||||
.allowStartIfComplete(true).build();
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(step);
|
||||
@@ -202,18 +194,13 @@ public class RemotePartitioningManagerStepBuilderTests {
|
||||
int startLimit = 3;
|
||||
DirectChannel outputChannel = new DirectChannel();
|
||||
Partitioner partitioner = Mockito.mock(Partitioner.class);
|
||||
StepExecutionAggregator stepExecutionAggregator = (result, executions) -> { };
|
||||
StepExecutionAggregator stepExecutionAggregator = (result, executions) -> {
|
||||
};
|
||||
|
||||
// when
|
||||
Step step = new RemotePartitioningManagerStepBuilder("managerStep")
|
||||
.repository(jobRepository)
|
||||
.outputChannel(outputChannel)
|
||||
.partitioner("workerStep", partitioner)
|
||||
.gridSize(gridSize)
|
||||
.startLimit(startLimit)
|
||||
.aggregator(stepExecutionAggregator)
|
||||
.allowStartIfComplete(true)
|
||||
.build();
|
||||
Step step = new RemotePartitioningManagerStepBuilder("managerStep").repository(jobRepository)
|
||||
.outputChannel(outputChannel).partitioner("workerStep", partitioner).gridSize(gridSize)
|
||||
.startLimit(startLimit).aggregator(stepExecutionAggregator).allowStartIfComplete(true).build();
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(step);
|
||||
@@ -244,12 +231,10 @@ public class RemotePartitioningManagerStepBuilderTests {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
|
||||
.addScript("/org/springframework/batch/core/schema-hsqldb.sql")
|
||||
.generateUniqueName(true)
|
||||
.build();
|
||||
return new EmbeddedDatabaseBuilder().addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql")
|
||||
.addScript("/org/springframework/batch/core/schema-hsqldb.sql").generateUniqueName(true).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,8 @@ public class RemotePartitioningWorkerStepBuilderTests {
|
||||
final RemotePartitioningWorkerStepBuilder builder = new RemotePartitioningWorkerStepBuilder("step");
|
||||
|
||||
// when
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class, () -> builder.inputChannel(null));
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class,
|
||||
() -> builder.inputChannel(null));
|
||||
|
||||
// then
|
||||
assertThat(expectedException).hasMessage("inputChannel must not be null");
|
||||
@@ -51,7 +52,8 @@ public class RemotePartitioningWorkerStepBuilderTests {
|
||||
final RemotePartitioningWorkerStepBuilder builder = new RemotePartitioningWorkerStepBuilder("step");
|
||||
|
||||
// when
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class, () -> builder.outputChannel(null));
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class,
|
||||
() -> builder.outputChannel(null));
|
||||
|
||||
// then
|
||||
assertThat(expectedException).hasMessage("outputChannel must not be null");
|
||||
@@ -63,7 +65,8 @@ public class RemotePartitioningWorkerStepBuilderTests {
|
||||
final RemotePartitioningWorkerStepBuilder builder = new RemotePartitioningWorkerStepBuilder("step");
|
||||
|
||||
// when
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class, () -> builder.jobExplorer(null));
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class,
|
||||
() -> builder.jobExplorer(null));
|
||||
|
||||
// then
|
||||
assertThat(expectedException).hasMessage("jobExplorer must not be null");
|
||||
@@ -75,7 +78,8 @@ public class RemotePartitioningWorkerStepBuilderTests {
|
||||
final RemotePartitioningWorkerStepBuilder builder = new RemotePartitioningWorkerStepBuilder("step");
|
||||
|
||||
// when
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class, () -> builder.stepLocator(null));
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class,
|
||||
() -> builder.stepLocator(null));
|
||||
|
||||
// then
|
||||
assertThat(expectedException).hasMessage("stepLocator must not be null");
|
||||
@@ -87,7 +91,8 @@ public class RemotePartitioningWorkerStepBuilderTests {
|
||||
final RemotePartitioningWorkerStepBuilder builder = new RemotePartitioningWorkerStepBuilder("step");
|
||||
|
||||
// when
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class, () -> builder.beanFactory(null));
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class,
|
||||
() -> builder.beanFactory(null));
|
||||
|
||||
// then
|
||||
assertThat(expectedException).hasMessage("beanFactory must not be null");
|
||||
@@ -99,7 +104,8 @@ public class RemotePartitioningWorkerStepBuilderTests {
|
||||
final RemotePartitioningWorkerStepBuilder builder = new RemotePartitioningWorkerStepBuilder("step");
|
||||
|
||||
// when
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class, () -> builder.tasklet(this.tasklet));
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class,
|
||||
() -> builder.tasklet(this.tasklet));
|
||||
|
||||
// then
|
||||
assertThat(expectedException).hasMessage("An InputChannel must be provided");
|
||||
@@ -113,7 +119,8 @@ public class RemotePartitioningWorkerStepBuilderTests {
|
||||
.inputChannel(inputChannel);
|
||||
|
||||
// when
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class, () -> builder.tasklet(this.tasklet));
|
||||
final Exception expectedException = Assert.assertThrows(IllegalArgumentException.class,
|
||||
() -> builder.tasklet(this.tasklet));
|
||||
|
||||
// then
|
||||
assertThat(expectedException).hasMessage("A JobExplorer must be provided");
|
||||
|
||||
@@ -46,7 +46,8 @@ public class StepExecutionRequestTests {
|
||||
@Test
|
||||
public void stepExecutionRequestShouldBeDeserializableWithJackson() throws IOException {
|
||||
// when
|
||||
StepExecutionRequest deserializedRequest = this.objectMapper.readValue(SERIALIZED_REQUEST, StepExecutionRequest.class);
|
||||
StepExecutionRequest deserializedRequest = this.objectMapper.readValue(SERIALIZED_REQUEST,
|
||||
StepExecutionRequest.class);
|
||||
|
||||
// then
|
||||
Assert.assertNotNull(deserializedRequest);
|
||||
@@ -54,4 +55,5 @@ public class StepExecutionRequestTests {
|
||||
Assert.assertEquals(1L, deserializedRequest.getJobExecutionId().longValue());
|
||||
Assert.assertEquals(1L, deserializedRequest.getStepExecutionId().longValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@@ -46,7 +46,7 @@ public class VanillaIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
|
||||
@Autowired
|
||||
private JobExplorer jobExplorer;
|
||||
|
||||
@@ -61,8 +61,8 @@ public class VanillaIntegrationTests {
|
||||
assertNotNull(jobLauncher.run(job, new JobParameters()));
|
||||
List<JobInstance> jobInstances = jobExplorer.getJobInstances(job.getName(), 0, 100);
|
||||
int after = jobInstances.size();
|
||||
assertEquals(1, after-before);
|
||||
JobExecution jobExecution = jobExplorer.getJobExecutions(jobInstances.get(jobInstances.size()-1)).get(0);
|
||||
assertEquals(1, after - before);
|
||||
JobExecution jobExecution = jobExplorer.getJobExecutions(jobInstances.get(jobInstances.size() - 1)).get(0);
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
assertEquals(3, jobExecution.getStepExecutions().size());
|
||||
}
|
||||
|
||||
@@ -30,17 +30,17 @@ public class RepeatTransactionalPollingIntegrationTests implements ApplicationCo
|
||||
private Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private static List<String> processed = new ArrayList<>();
|
||||
|
||||
|
||||
private static List<String> expected;
|
||||
|
||||
private static List<String> handled = new ArrayList<>();
|
||||
|
||||
|
||||
private static List<String> list = new ArrayList<>();
|
||||
|
||||
private Lifecycle bus;
|
||||
|
||||
private volatile static int count = 0;
|
||||
|
||||
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
bus = (Lifecycle) applicationContext;
|
||||
}
|
||||
@@ -48,8 +48,8 @@ public class RepeatTransactionalPollingIntegrationTests implements ApplicationCo
|
||||
public String process(String message) {
|
||||
String result = message + ": " + count;
|
||||
logger.debug("Handling: " + message);
|
||||
if (count<expected.size()) {
|
||||
processed.add(message);
|
||||
if (count < expected.size()) {
|
||||
processed.add(message);
|
||||
count++;
|
||||
}
|
||||
if ("fail".equals(message)) {
|
||||
@@ -57,7 +57,7 @@ public class RepeatTransactionalPollingIntegrationTests implements ApplicationCo
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public String input() {
|
||||
logger.debug("Polling: " + count);
|
||||
if (list.isEmpty()) {
|
||||
@@ -66,9 +66,9 @@ public class RepeatTransactionalPollingIntegrationTests implements ApplicationCo
|
||||
return list.remove(0);
|
||||
}
|
||||
|
||||
public void output(String message) {
|
||||
public void output(String message) {
|
||||
handled.add(message);
|
||||
logger.debug("Handled: " + message);
|
||||
logger.debug("Handled: " + message);
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -82,23 +82,21 @@ public class RepeatTransactionalPollingIntegrationTests implements ApplicationCo
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testSunnyDay() throws Exception {
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("a,b,c,d,e,f,g,h,j,k")));
|
||||
expected = Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("a,b,c,d"));
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(
|
||||
Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,c,d,e,f,g,h,j,k")));
|
||||
expected = Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,c,d"));
|
||||
waitForResults(bus, expected.size(), 60);
|
||||
assertEquals(expected,processed);
|
||||
assertEquals(expected, processed);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testRollback() throws Exception {
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("a,b,fail,d,e,f,g,h,j,k")));
|
||||
expected = Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("a,b,fail,fail"));
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(
|
||||
Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,fail,d,e,f,g,h,j,k")));
|
||||
expected = Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,fail,fail"));
|
||||
waitForResults(bus, expected.size(), 60);
|
||||
assertEquals(expected,processed);
|
||||
assertEquals(expected, processed);
|
||||
assertEquals(2, handled.size()); // a,b
|
||||
}
|
||||
|
||||
|
||||
@@ -68,8 +68,8 @@ public class RetryRepeatTransactionalPollingIntegrationTests implements Applicat
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testSunnyDay() throws Exception {
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("a,b,c,d,e,f,g,h,j,k")));
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(
|
||||
Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,c,d,e,f,g,h,j,k")));
|
||||
List<String> expected = Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,c,d"));
|
||||
service.setExpected(expected);
|
||||
waitForResults(lifecycle, expected.size(), 60);
|
||||
@@ -80,11 +80,12 @@ public class RetryRepeatTransactionalPollingIntegrationTests implements Applicat
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testRollback() throws Exception {
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("a,b,fail,d,e,f,g,h,j,k")));
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(
|
||||
Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,fail,d,e,f,g,h,j,k")));
|
||||
List<String> expected = Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,fail,fail,d,e,f"));
|
||||
service.setExpected(expected);
|
||||
waitForResults(lifecycle, expected.size(), 60); // (a,b), (fail), (fail), ([fail],d), (e,f)
|
||||
waitForResults(lifecycle, expected.size(), 60); // (a,b), (fail), (fail),
|
||||
// ([fail],d), (e,f)
|
||||
System.err.println(service.getProcessed());
|
||||
assertEquals(7, service.getProcessed().size()); // a,b,fail,fail,d,e,f
|
||||
assertEquals(1, recoverer.getRecovered().size()); // fail
|
||||
|
||||
@@ -56,7 +56,8 @@ public class RetryTransactionalPollingIntegrationTests implements ApplicationCon
|
||||
if (list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// This happens in a transaction and the list is transactional so if it rolls back the same item comes back
|
||||
// This happens in a transaction and the list is transactional so if it rolls back
|
||||
// the same item comes back
|
||||
// again at the head of the list
|
||||
return list.remove(0);
|
||||
}
|
||||
@@ -69,8 +70,8 @@ public class RetryTransactionalPollingIntegrationTests implements ApplicationCon
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testSunnyDay() throws Exception {
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("a,b,c,d,e,f,g,h,j,k")));
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(
|
||||
Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,c,d,e,f,g,h,j,k")));
|
||||
List<String> expected = Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,c,d"));
|
||||
service.setExpected(expected);
|
||||
waitForResults(bus, expected.size(), 60);
|
||||
@@ -81,8 +82,8 @@ public class RetryTransactionalPollingIntegrationTests implements ApplicationCon
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testRollback() throws Exception {
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("a,b,fail,d,e,f,g,h,j,k")));
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(
|
||||
Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,fail,d,e,f,g,h,j,k")));
|
||||
|
||||
List<String> expected = Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,fail,fail,d,e"));
|
||||
service.setExpected(expected);
|
||||
|
||||
@@ -34,4 +34,5 @@ public final class SimpleRecoverer implements MethodInvocationRecoverer<String>
|
||||
recovered.add(payload);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -87,12 +87,13 @@ public class TransactionalPollingIntegrationTests implements ApplicationContextA
|
||||
@DirtiesContext
|
||||
public void testSunnyDay() throws Exception {
|
||||
try {
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("a,b,c,d,e,f,g,h,j,k")));
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(
|
||||
Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,c,d,e,f,g,h,j,k")));
|
||||
expected = Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,c,d"));
|
||||
waitForResults(bus, 4, 60);
|
||||
assertEquals(expected, processed);
|
||||
} catch (Throwable t) {
|
||||
}
|
||||
catch (Throwable t) {
|
||||
System.out.println(t.getMessage());
|
||||
t.printStackTrace();
|
||||
}
|
||||
@@ -101,8 +102,8 @@ public class TransactionalPollingIntegrationTests implements ApplicationContextA
|
||||
@Test
|
||||
@DirtiesContext
|
||||
public void testRollback() throws Exception {
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(Arrays.asList(StringUtils
|
||||
.commaDelimitedListToStringArray("a,b,fail,d,e,f,g,h,j,k")));
|
||||
list = TransactionAwareProxyFactory.createTransactionalList(
|
||||
Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,fail,d,e,f,g,h,j,k")));
|
||||
expected = Arrays.asList(StringUtils.commaDelimitedListToStringArray("a,b,fail,fail"));
|
||||
waitForResults(bus, 4, 30);
|
||||
assertEquals(expected, processed);
|
||||
|
||||
@@ -1,73 +1,74 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.step;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.ExitStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration()
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class StepGatewayIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("job")
|
||||
private Job job;
|
||||
|
||||
@Autowired
|
||||
private TestTasklet tasklet;
|
||||
|
||||
@After
|
||||
public void clear() {
|
||||
tasklet.setFail(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchJob() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchFailedJob() throws Exception {
|
||||
tasklet.setFail(true);
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParametersBuilder().addLong("run.id", 2L).toJobParameters());
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
assertEquals(ExitStatus.FAILED, jobExecution.getExitStatus());
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* https://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.step;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.ExitStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobExecution;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
@ContextConfiguration()
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class StepGatewayIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("job")
|
||||
private Job job;
|
||||
|
||||
@Autowired
|
||||
private TestTasklet tasklet;
|
||||
|
||||
@After
|
||||
public void clear() {
|
||||
tasklet.setFail(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchJob() throws Exception {
|
||||
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
|
||||
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
|
||||
assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchFailedJob() throws Exception {
|
||||
tasklet.setFail(true);
|
||||
JobExecution jobExecution = jobLauncher.run(job,
|
||||
new JobParametersBuilder().addLong("run.id", 2L).toJobParameters());
|
||||
assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
|
||||
assertEquals(ExitStatus.FAILED, jobExecution.getExitStatus());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
/*
|
||||
* Copyright 2006-2019 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
|
||||
*
|
||||
* https://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.step;
|
||||
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||
import org.springframework.batch.repeat.RepeatStatus;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class TestTasklet implements Tasklet {
|
||||
|
||||
private boolean fail = false;
|
||||
|
||||
public void setFail(boolean fail) {
|
||||
this.fail = fail;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||
if (fail) {
|
||||
throw new IllegalStateException("Planned Tasklet failure");
|
||||
}
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Copyright 2006-2019 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
|
||||
*
|
||||
* https://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.step;
|
||||
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||
import org.springframework.batch.repeat.RepeatStatus;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class TestTasklet implements Tasklet {
|
||||
|
||||
private boolean fail = false;
|
||||
|
||||
public void setFail(boolean fail) {
|
||||
this.fail = fail;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||
if (fail) {
|
||||
throw new IllegalStateException("Planned Tasklet failure");
|
||||
}
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user