BATCH-1466, BATCHADM-16: move integration project

This commit is contained in:
David Syer
2009-12-29 09:18:06 +00:00
committed by Michael Minella
parent eb3660c972
commit a0bc87c3e4
87 changed files with 6273 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry kind="src" output="target/classes" path="src/main/resources"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>spring-batch-integration</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.wst.common.project.facet.core.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.springframework.ide.eclipse.core.springbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
<nature>org.springframework.ide.eclipse.core.springnature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.maven.ide.eclipse.maven2Nature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,83 @@
#Wed Aug 20 13:59:46 BST 2008
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.5
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.doc.comment.support=enabled
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
org.eclipse.jdt.core.compiler.problem.deprecation=warning
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=enabled
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error
org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=default
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public
org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag
org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
org.eclipse.jdt.core.compiler.problem.nullReference=warning
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.source=1.5

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="spring-batch-integration">
<wb-resource deploy-path="/" source-path="/src/main/java"/>
<wb-resource deploy-path="/" source-path="/src/main/resources"/>
<wb-resource deploy-path="/" source-path="/src/test/java"/>
<wb-resource deploy-path="/" source-path="/src/test/resources"/>
</wb-module>
</project-modules>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<faceted-project>
<installed facet="jst.java" version="5.0"/>
<installed facet="jst.utility" version="1.0"/>
</faceted-project>

View File

@@ -0,0 +1,9 @@
#Sat Jun 20 14:28:55 BST 2009
activeProfiles=
eclipse.preferences.version=1
fullBuildGoals=process-test-resources
includeModules=false
resolveWorkspaceProjects=true
resourceFilterGoals=process-resources resources\:testResources
skipCompilerPlugin=true
version=1

View File

@@ -0,0 +1,3 @@
#Wed May 06 08:20:22 BST 2009
eclipse.preferences.version=1
org.springframework.ide.eclipse.beans.core.ignoreMissingNamespaceHandler=false

View File

@@ -0,0 +1,49 @@
#Mon Jun 02 08:59:31 BST 2008
eclipse.preferences.version=1
org.springframework.ide.eclipse.core.builders.enable.aopreferencemodelbuilder=true
org.springframework.ide.eclipse.core.builders.enable.beanmetadatabuilder=false
org.springframework.ide.eclipse.core.builders.enable.osgibundleupdater=true
org.springframework.ide.eclipse.core.enable.project.preferences=false
org.springframework.ide.eclipse.core.validator.enable.com.springsource.sts.bestpractices.beansvalidator=true
org.springframework.ide.eclipse.core.validator.enable.org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.enable.org.springframework.ide.eclipse.core.springvalidator=true
org.springframework.ide.eclipse.core.validator.enable.org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.com.springsource.sts.bestpractices.AvoidDriverManagerDataSource-com.springsource.sts.bestpractices.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.com.springsource.sts.bestpractices.ImportElementsAtTopRulee-com.springsource.sts.bestpractices.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.com.springsource.sts.bestpractices.ParentBeanSpecifiesAbstractClassRule-com.springsource.sts.bestpractices.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.com.springsource.sts.bestpractices.RefElementRule-com.springsource.sts.bestpractices.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.com.springsource.sts.bestpractices.TooManyBeansInFileRule-com.springsource.sts.bestpractices.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.com.springsource.sts.bestpractices.UnnecessaryValueElementRule-com.springsource.sts.bestpractices.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.com.springsource.sts.bestpractices.UseBeanInheritance-com.springsource.sts.bestpractices.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.com.springsource.sts.bestpractices.legacyxmlusage.jndiobjectfactory-com.springsource.sts.bestpractices.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanAlias-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanClass-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanConstructorArgument-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanDefinition-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanDefinitionHolder-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanFactory-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanInitDestroyMethod-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanProperty-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.beanReference-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.methodOverride-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.parsingProblems-org.springframework.ide.eclipse.beans.core.beansvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.beans.core.requiredProperty-org.springframework.ide.eclipse.beans.core.beansvalidator=false
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.core.springClasspath-org.springframework.ide.eclipse.core.springvalidator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.action-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.actionstate-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.attribute-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.attributemapper-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.beanaction-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.evaluationaction-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.evaluationresult-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.exceptionhandler-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.import-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.inputattribute-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.mapping-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.outputattribute-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.set-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.state-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.subflowstate-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.transition-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.variable-org.springframework.ide.eclipse.webflow.core.validator=true
org.springframework.ide.eclipse.core.validator.rule.enable.org.springframework.ide.eclipse.webflow.core.validation.webflowstate-org.springframework.ide.eclipse.webflow.core.validator=true

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<beansProjectDescription>
<version>1</version>
<pluginVersion><![CDATA[2.2.4.RELEASE]]></pluginVersion>
<configSuffixes>
<configSuffix><![CDATA[xml]]></configSuffix>
</configSuffixes>
<enableImports><![CDATA[true]]></enableImports>
<configs>
<config>src/test/resources/job-execution-context.xml</config>
<config>src/test/resources/simple-job-launcher-context.xml</config>
<config>src/test/resources/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/integration/job/MessageOrientedStepIntegrationTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/integration/file/ResourceSplitterIntegrationTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/integration/retry/TransactionalPollingIntegrationTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/integration/retry/RepeatTransactionalPollingIntegrationTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/integration/retry/RetryRepeatTransactionalPollingIntegrationTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/integration/SmokeTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/integration/retry/RetryTransactionalPollingIntegrationTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/integration/partition/VanillaIntegrationTests-context.xml</config>
<config>src/test/resources/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests-context.xml</config>
</configs>
<configSets>
</configSets>
</beansProjectDescription>

View File

@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-batch-integration</artifactId>
<name>Spring Batch Integration</name>
<url>http://static.springframework.org/spring-batch/${project.artifactId}</url>
<parent>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-admin-parent</artifactId>
<version>1.0.0.CI-SNAPSHOT</version>
<relativePath>../spring-batch-admin-parent</relativePath>
</parent>
<profiles>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>test</id>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-core</artifactId>
<version>${spring.batch.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-test</artifactId>
<version>${spring.batch.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jms_1.1_spec</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-core</artifactId>
<version>${spring.integration.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-adapter</artifactId>
<version>${spring.integration.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-jms</artifactId>
<version>${spring.integration.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<junitArtifactName>junit:junit</junitArtifactName>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Class-Path:

View File

@@ -0,0 +1,77 @@
package org.springframework.batch.integration.async;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.util.Assert;
/**
* An {@link ItemProcessor} that delegates to a nested processor and in the
* background. To allow for background processing the return value from the
* processor is a {@link Future} which needs to be unpacked before the item can
* be used by a client.
*
* @author Dave Syer
*
* @param <I> the input object type
* @param <O> the output object type (will be wrapped in a Future)
*/
public class AsyncItemProcessor<I, O> implements ItemProcessor<I, Future<O>>, InitializingBean {
private ItemProcessor<I, O> delegate;
private TaskExecutor taskExecutor = new SyncTaskExecutor();
/**
* Check mandatory properties (the {@link #setDelegate(ItemProcessor)}).
*
* @see InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(delegate, "The delegate must be set.");
}
/**
* The {@link ItemProcessor} to use to delegate processing to in a
* background thread.
*
* @param delegate the {@link ItemProcessor} to use as a delegate
*/
public void setDelegate(ItemProcessor<I, O> delegate) {
this.delegate = delegate;
}
/**
* The {@link TaskExecutor} to use to allow the item processing to proceed
* in the background. Defaults to a {@link SyncTaskExecutor} so no threads
* are created unless this is overridden.
*
* @param taskExecutor a {@link TaskExecutor}
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
/**
* Transform the input by delegating to the provided item processor. The
* return value is wrapped in a {@link Future} so that clients can unpack it
* later.
*
* @see ItemProcessor#process(Object)
*/
public Future<O> process(final I item) throws Exception {
FutureTask<O> task = new FutureTask<O>(new Callable<O>() {
public O call() throws Exception {
return delegate.process(item);
}
});
taskExecutor.execute(task);
return task;
}
}

View File

@@ -0,0 +1,34 @@
package org.springframework.batch.integration.async;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import org.springframework.batch.item.ItemWriter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
public class AsyncItemWriter<T> implements ItemWriter<Future<T>>, InitializingBean {
private ItemWriter<T> delegate;
public void afterPropertiesSet() throws Exception {
Assert.notNull(delegate, "A delegate ItemWriter must be provided.");
}
/**
* @param delegate
*/
public void setDelegate(ItemWriter<T> delegate) {
this.delegate = delegate;
}
public void write(List<? extends Future<T>> items) throws Exception {
List<T> list = new ArrayList<T>();
for (Future<T> future : items) {
list.add(future.get());
}
delegate.write(list);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.chunk;
import org.springframework.batch.item.ItemWriterException;
/**
* Exception indicating that a failure or early completion condition was
* detected in a remote worker.
*
* @author Dave Syer
*
*/
public class AsynchronousFailureException extends ItemWriterException {
/**
* Create a new {@link AsynchronousFailureException} based on a message and
* another exception.
*
* @param message
* the message for this exception
* @param cause
* the other exception
*/
public AsynchronousFailureException(String message, Throwable cause) {
super(message, cause);
}
/**
* Create a new {@link AsynchronousFailureException} based on a message.
*
* @param message
* the message for this exception
*/
public AsynchronousFailureException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.chunk;
public interface ChunkHandler<T> {
ChunkResponse handleChunk(ChunkRequest<T> chunk);
}

View File

@@ -0,0 +1,202 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.chunk;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.ItemWriter;
import org.springframework.integration.gateway.MessagingGateway;
import org.springframework.util.Assert;
public class ChunkMessageChannelItemWriter<T> extends StepExecutionListenerSupport implements ItemWriter<T>, ItemStream {
private static final Log logger = LogFactory.getLog(ChunkMessageChannelItemWriter.class);
static final String ACTUAL = ChunkMessageChannelItemWriter.class.getName() + ".ACTUAL";
static final String EXPECTED = ChunkMessageChannelItemWriter.class.getName() + ".EXPECTED";
private static final long DEFAULT_THROTTLE_LIMIT = 6;
private MessagingGateway messagingGateway;
private LocalState localState = new LocalState();
private long throttleLimit = DEFAULT_THROTTLE_LIMIT;
/**
* Public setter for the throttle limit. This limits the number of pending
* requests for chunk processing to avoid overwhelming the receivers.
* @param throttleLimit the throttle limit to set
*/
public void setThrottleLimit(long throttleLimit) {
this.throttleLimit = throttleLimit;
}
public void setMessagingGateway(MessagingGateway messagingGateway) {
this.messagingGateway = messagingGateway;
}
public void write(List<? extends T> items) throws Exception {
// Block until expecting <= throttle limit
while (localState.getExpecting() > throttleLimit) {
getNextResult();
}
if (!items.isEmpty()) {
logger.debug("Dispatching chunk: " + items);
ChunkRequest<T> request = new ChunkRequest<T>(items, localState.getJobId(), localState
.createStepContribution());
messagingGateway.send(request);
localState.expected++;
}
}
@Override
public void beforeStep(StepExecution stepExecution) {
localState.setStepExecution(stepExecution);
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
if (!(stepExecution.getStatus() == BatchStatus.COMPLETED)) {
return ExitStatus.EXECUTING;
}
long expecting = localState.getExpecting();
boolean timedOut;
try {
logger.debug("Waiting for results in step listener...");
timedOut = !waitForResults();
logger.debug("Finished waiting for results in step listener.");
}
catch (RuntimeException e) {
logger.debug("Detected failure waiting for results in step listener.", e);
stepExecution.setStatus(BatchStatus.FAILED);
return ExitStatus.FAILED.addExitDescription(e.getClass().getName() + ": " + e.getMessage());
}
if (timedOut) {
stepExecution.setStatus(BatchStatus.FAILED);
throw new ItemStreamException("Timed out waiting for back log at end of step");
}
return ExitStatus.COMPLETED.addExitDescription("Waited for " + expecting + " results.");
}
public void close() throws ItemStreamException {
localState.reset();
}
public void open(ExecutionContext executionContext) throws ItemStreamException {
if (executionContext.containsKey(EXPECTED)) {
localState.expected = executionContext.getLong(EXPECTED);
localState.actual = executionContext.getLong(ACTUAL);
if (!waitForResults()) {
throw new ItemStreamException("Timed out waiting for back log on open");
}
}
}
public void update(ExecutionContext executionContext) throws ItemStreamException {
executionContext.putLong(EXPECTED, localState.expected);
executionContext.putLong(ACTUAL, localState.actual);
}
/**
* Wait until all the results that are in the pipeline come back to the
* reply channel.
*
* @return true if successfully received a result, false if timed out
*/
private boolean waitForResults() {
// TODO: cumulative timeout, or throw an exception?
int count = 0;
int maxCount = 40;
while (localState.getExpecting() > 0 && count++ < maxCount) {
getNextResult();
}
return count < maxCount;
}
/**
* Get the next result if it is available (within the timeout specified in
* the gateway), otherwise do nothing.
*
* @throws AsynchronousFailureException If there is a response and it
* contains a failed chunk response.
*
* @throws IllegalStateException if the result contains the wrong job
* instance id (maybe we are sharing a channel and we shouldn't be)
*/
private void getNextResult() {
// TODO: make sure this is transactional (should be if single threaded)
ChunkResponse payload = (ChunkResponse) messagingGateway.receive();
if (payload != null) {
Long jobInstanceId = payload.getJobId();
Assert.state(jobInstanceId != null, "Message did not contain job instance id.");
Assert.state(jobInstanceId.equals(localState.getJobId()), "Message contained wrong job instance id ["
+ jobInstanceId + "] should have been [" + localState.getJobId() + "].");
localState.actual++;
// TODO: apply the skip count
if (!payload.isSuccessful()) {
throw new AsynchronousFailureException("Failure or interrupt detected in handler: "
+ payload.getMessage());
}
}
}
private static class LocalState {
private long actual;
private long expected;
private StepExecution stepExecution;
public long getExpecting() {
return expected - actual;
}
public StepContribution createStepContribution() {
return stepExecution.createStepContribution();
}
public Long getJobId() {
return stepExecution.getJobExecution().getJobId();
}
public void setStepExecution(StepExecution stepExecution) {
this.stepExecution = stepExecution;
}
public void reset() {
expected = actual = 0;
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.chunk;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.step.item.Chunk;
import org.springframework.batch.core.step.item.ChunkProcessor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.util.Assert;
@MessageEndpoint
public class ChunkProcessorChunkHandler<S> implements ChunkHandler<S>, InitializingBean {
private static final Log logger = LogFactory.getLog(ChunkProcessorChunkHandler.class);
private ChunkProcessor<S> chunkProcessor;
/*
* (non-Javadoc)
*
* @see
* org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(chunkProcessor, "A ChunkProcessor must be provided");
}
/**
* Public setter for the {@link ChunkProcessor}.
*
* @param chunkProcessor the chunkProcessor to set
*/
public void setChunkProcessor(ChunkProcessor<S> chunkProcessor) {
this.chunkProcessor = chunkProcessor;
}
/**
*
* @see ChunkHandler#handleChunk(ChunkRequest)
*/
@ServiceActivator
public ChunkResponse handleChunk(ChunkRequest<S> chunkRequest) {
logger.debug("Handling chunk: " + chunkRequest);
StepContribution stepContribution = chunkRequest.getStepContribution();
try {
chunkProcessor.process(stepContribution, new Chunk<S>(chunkRequest.getItems()));
}
catch (Exception e) {
logger.debug("Failed chunk", e);
return new ChunkResponse(false, chunkRequest.getJobId(), stepContribution, e.getClass().getName() + ": "
+ e.getMessage());
}
logger.debug("Completed chunk handling with " + stepContribution);
return new ChunkResponse(true, chunkRequest.getJobId(), stepContribution);
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.chunk;
import java.io.Serializable;
import java.util.Collection;
import org.springframework.batch.core.StepContribution;
public class ChunkRequest<T> implements Serializable {
private final Long jobId;
private final Collection<? extends T> items;
private final StepContribution stepContribution;
public ChunkRequest(Collection<? extends T> items, Long jobId, StepContribution stepContribution) {
this.items = items;
this.jobId = jobId;
this.stepContribution = stepContribution;
}
public Long getJobId() {
return jobId;
}
public Collection<? extends T> getItems() {
return items;
}
/**
* @return the {@link StepContribution} for this chunk
*/
public StepContribution getStepContribution() {
return stepContribution;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return getClass().getSimpleName()+": jobId="+jobId+", contribution="+stepContribution+", item count="+items.size();
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.chunk;
import java.io.Serializable;
import org.springframework.batch.core.StepContribution;
public class ChunkResponse implements Serializable {
private final StepContribution stepContribution;
private final Long jobId;
private final boolean status;
private final String message;
public ChunkResponse(Long jobId, StepContribution stepContribution) {
this(true, jobId, stepContribution, null);
}
public ChunkResponse(boolean status, Long jobId, StepContribution stepContribution) {
this(status, jobId, stepContribution, null);
}
public ChunkResponse(boolean status, Long jobId, StepContribution stepContribution, String message) {
this.status = status;
this.jobId = jobId;
this.stepContribution = stepContribution;
this.message = message;
}
public StepContribution getStepContribution() {
return stepContribution;
}
public Long getJobId() {
return jobId;
}
public boolean isSuccessful() {
return status;
}
public String getMessage() {
return message;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return getClass().getSimpleName()+": jobId="+jobId+", stepContribution="+stepContribution+", successful="+status;
}
}

View File

@@ -0,0 +1,389 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.file;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.List;
import java.util.Properties;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
import org.springframework.batch.core.converter.JobParametersConverter;
import org.springframework.batch.core.job.SimpleJob;
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.item.SimpleStepFactoryBean;
import org.springframework.batch.integration.launch.JobLaunchingMessageHandler;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.xml.StaxEventItemReader;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;
/**
* A FactoryBean for a {@link Job} with a single step which just pumps messages
* from a file into a channel. The channel has to be a {@link DirectChannel} to
* ensure that failures propagate up to the step and fail the job execution.
* Normally this job will be used in conjunction with a
* {@link JobLaunchingMessageHandler} and a
* {@link ResourcePayloadAsJobParameterStrategy}, so that the user can just send
* a message to a request channel listing the files to be processed, and
* everything else just happens by magic. After a failure the job will be
* restarted just by sending it the same message.
*
* @author Dave Syer
*
*/
public class FileToMessagesJobFactoryBean<T> implements FactoryBean, BeanNameAware {
private String name = "fileToMessageJob";
private ItemReader<? extends T> itemReader;
private MessageChannel channel;
private PlatformTransactionManager transactionManager;
private JobRepository jobRepository;
/*
* (non-Javadoc)
*
* @see
* org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang
* .String)
*/
public void setBeanName(String name) {
this.name = name;
}
/**
* Public setter for the {@link ItemReader}. Must be either a
* {@link FlatFileItemReader} or a {@link StaxEventItemReader}. In either
* case there is no need to set the resource property as it will be set by
* this factory.
*
* @param itemReader the itemReader to set
*/
@Required
public void setItemReader(ItemReader<? extends T> itemReader) {
this.itemReader = itemReader;
}
/**
* Public setter for the channel. Each item from the item reader will be
* sent to this channel.
*
* @param channel the channel to set
*/
@Required
public void setChannel(MessageChannel channel) {
this.channel = channel;
}
/**
* Public setter for the {@link JobRepository}.
*
* @param jobRepository the job repository to set
*/
@Required
public void setJobRepository(JobRepository jobRepository) {
this.jobRepository = jobRepository;
}
/**
* Public setter for the {@link PlatformTransactionManager}.
*
* @param transactionManager the transaction manager to set
*/
@Required
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* Creates a {@link Job} that can process a flat file or XML file into
* messages. To launch the job will require only a {@link JobParameters}
* instance with a resource location as a URL.
*
* @see org.springframework.beans.factory.FactoryBean#getObject()
*/
public Object getObject() throws Exception {
SimpleJob job = new SimpleJob();
job.setName(name);
job.setJobRepository(jobRepository);
SimpleStepFactoryBean<T, T> stepFactory = new SimpleStepFactoryBean<T, T>();
stepFactory.setBeanName("step");
Assert.state((itemReader instanceof FlatFileItemReader) || (itemReader instanceof StaxEventItemReader),
"ItemReader must be either a FlatFileItemReader or a StaxEventItemReader");
JobParameterResourceProxy resourceProxy = new JobParameterResourceProxy();
resourceProxy.setKey(ResourcePayloadAsJobParameterStrategy.FILE_INPUT_PATH);
stepFactory.setListeners(new StepExecutionListener[] { resourceProxy });
setResource(itemReader, resourceProxy);
stepFactory.setItemReader(itemReader);
Assert.notNull(channel, "A channel must be provided");
Assert.state(channel instanceof DirectChannel,
"The channel must be a DirectChannel (otherwise failures can not be recovered from)");
MessageChannelItemWriter<? super T> itemWriter = new MessageChannelItemWriter<T>(channel);
stepFactory.setItemWriter(itemWriter);
Assert.notNull(transactionManager, "A transaction manager must be provided");
stepFactory.setTransactionManager(transactionManager);
Assert.notNull(jobRepository, "A job repository must be provided");
stepFactory.setJobRepository(jobRepository);
job.addStep((Step) stepFactory.getObject());
return job;
}
/**
* @param itemReader
* @param resource
*/
private void setResource(ItemReader<? extends T> itemReader, Resource resource) {
if (itemReader instanceof FlatFileItemReader) {
((FlatFileItemReader<? extends T>) itemReader).setResource(resource);
}
else {
((StaxEventItemReader<? extends T>) itemReader).setResource(resource);
}
}
/**
* Always returns {@link Job}.
*
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
public Class<Job> getObjectType() {
return Job.class;
}
/**
* Always true. TODO: should it be false?
*
* @see org.springframework.beans.factory.FactoryBean#isSingleton()
*/
public boolean isSingleton() {
return true;
}
/**
* Strategy for resolving a filename just prior to step execution. The proxy
* is given a key that will correspond to a key in the job parameters. Just
* before the step is executed, the resource will be created with its
* filename as the value found in the job parameters.
*
* To use this resource it must be initialised with a {@link StepExecution}.
* The best way to do that is to register it as a listener in the step that
* is going to need it. For this reason the resource implements
* {@link StepExecutionListener}.
*
* @see Resource
*/
private class JobParameterResourceProxy extends StepExecutionListenerSupport implements Resource,
ResourceLoaderAware, StepExecutionListener {
private JobParametersConverter jobParametersConverter = new DefaultJobParametersConverter();
private ResourceLoader resourceLoader = new FileSystemResourceLoader();
private Resource delegate;
private String key = null;
private static final String NOT_INITIALISED = "The delegate resource has not been initialised. "
+ "Remember to register this object as a StepListener.";
/**
* @param relativePath
* @throws IOException
* @see org.springframework.core.io.Resource#createRelative(java.lang.String)
*/
public Resource createRelative(String relativePath) throws IOException {
Assert.state(delegate != null, NOT_INITIALISED);
return delegate.createRelative(relativePath);
}
/**
* @see org.springframework.core.io.Resource#exists()
*/
public boolean exists() {
Assert.state(delegate != null, NOT_INITIALISED);
return delegate.exists();
}
/**
* @see org.springframework.core.io.Resource#getDescription()
*/
public String getDescription() {
Assert.state(delegate != null, NOT_INITIALISED);
return delegate.getDescription();
}
/**
* @throws IOException
* @see org.springframework.core.io.Resource#getFile()
*/
public File getFile() throws IOException {
Assert.state(delegate != null, NOT_INITIALISED);
return delegate.getFile();
}
/**
* @see org.springframework.core.io.Resource#getFilename()
*/
public String getFilename() {
Assert.state(delegate != null, NOT_INITIALISED);
return delegate.getFilename();
}
/**
* @throws IOException
* @see org.springframework.core.io.InputStreamSource#getInputStream()
*/
public InputStream getInputStream() throws IOException {
Assert.state(delegate != null, NOT_INITIALISED);
return delegate.getInputStream();
}
/**
* @throws IOException
* @see org.springframework.core.io.Resource#getURI()
*/
public URI getURI() throws IOException {
Assert.state(delegate != null, NOT_INITIALISED);
return delegate.getURI();
}
/**
* @throws IOException
* @see org.springframework.core.io.Resource#getURL()
*/
public URL getURL() throws IOException {
Assert.state(delegate != null, NOT_INITIALISED);
return delegate.getURL();
}
/**
* @see org.springframework.core.io.Resource#isOpen()
*/
public boolean isOpen() {
Assert.state(delegate != null, NOT_INITIALISED);
return delegate.isOpen();
}
/**
* @see org.springframework.core.io.Resource#isReadable()
*/
public boolean isReadable() {
Assert.state(delegate != null, NOT_INITIALISED);
return delegate.isReadable();
}
/**
* @see org.springframework.core.io.Resource#lastModified()
*/
public long lastModified() throws IOException {
Assert.state(delegate != null, NOT_INITIALISED);
return delegate.lastModified();
}
/**
* Public setter for the {@link JobParametersConverter} used to
* translate {@link JobParameters} into {@link Properties}. Defaults to
* a {@link DefaultJobParametersConverter}.
*
* @param jobParametersConverter the {@link JobParametersConverter} to
* set
*/
public void setJobParametersFactory(JobParametersConverter jobParametersConverter) {
this.jobParametersConverter = jobParametersConverter;
}
/**
* Always false because we are expecting to be step scoped.
*
* @see org.springframework.beans.factory.config.AbstractFactoryBean#isSingleton()
*/
public boolean isSingleton() {
return false;
}
/**
* @see org.springframework.context.ResourceLoaderAware#setResourceLoader(org.springframework.core.io.ResourceLoader)
*/
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
public void setKey(String key) {
this.key = key;
}
/**
* Collect the properties of the enclosing {@link StepExecution} that
* will be needed to create a file name.
*
* @see org.springframework.batch.core.StepExecutionListener#beforeStep(org.springframework.batch.core.StepExecution)
*/
public void beforeStep(StepExecution execution) {
Properties properties = jobParametersConverter.getProperties(execution.getJobExecution().getJobInstance()
.getJobParameters());
delegate = resourceLoader.getResource(properties.getProperty(this.key));
}
}
private static class MessageChannelItemWriter<T> implements ItemWriter<T> {
private MessageChannel channel;
public MessageChannelItemWriter(MessageChannel channel) {
super();
this.channel = channel;
}
public void write(List<? extends T> items) throws Exception {
for (T item : items) {
channel.send(new GenericMessage<T>(item));
}
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.file;
import org.springframework.batch.core.JobParameters;
import org.springframework.integration.core.Message;
/**
*
* @author Jonas Partner
*
*/
public interface MessageToJobParametersStrategy {
public JobParameters getJobParameters(Message<?> message);
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.file;
import java.io.IOException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.core.io.Resource;
import org.springframework.integration.core.Message;
/**
* @author Dave Syer
*
*/
public class ResourcePayloadAsJobParameterStrategy implements MessageToJobParametersStrategy {
/**
* The key name for the job parameter that will be a URL for the input file
*/
public static final String FILE_INPUT_PATH = "input.file.path";
/**
* Convert a message payload which is a {@link Resource} to its URL
* representation and load that into a job parameter.
*
* @see MessageToJobParametersStrategy#getJobParameters(Message)
*/
public JobParameters getJobParameters(Message<?> message) {
JobParametersBuilder builder = new JobParametersBuilder();
Resource resource = (Resource) message.getPayload();
try {
builder.addString(FILE_INPUT_PATH, resource.getURL().toExternalForm());
}
catch (IOException e) {
throw new ItemStreamException("Could not create URL for resource: [" + resource + "]", e);
}
return builder.toJobParameters();
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.job;
/**
* @author dsyer
*
*/
public class JobExecutionReply {
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.job;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
/**
* Encapsulation of a request to execute a job execution through a message flow
* consisting of step handlers. A handler should pass the message on as it is,
* modifying the request properties as necessary. Generally a handler will
* execute a step as part of the {@link JobExecution} passed in, and should
* change the status to {@link BatchStatus#COMPLETED} if the step is successful
* (generally a handler cannot determine if the whole job execution is complete,
* so this is just information about the step).<br/>
*
* If the incoming status is {@link BatchStatus#FAILED},
* {@link BatchStatus#ABANDONED} or {@link BatchStatus#STOPPING} the request
* should be ignored by handlers (passed on without modification).
*
* @author Dave Syer
*
*/
public class JobExecutionRequest {
private JobExecution jobExecution;
private BatchStatus status;
private Throwable throwable;
/**
* @param jobExecution
*/
public JobExecutionRequest(JobExecution jobExecution) {
this.jobExecution = jobExecution;
status = jobExecution.getStatus();
}
/**
* @return the current job execution id
*/
public Long getJobId() {
return this.jobExecution.getJobId();
}
/**
* @return the current {@link BatchStatus}
*/
public BatchStatus getStatus() {
return status;
}
/**
* Public setter for the status.
* @param status the status to set
*/
public void setStatus(BatchStatus status) {
this.status = status;
}
/**
* @return true if there are errors
*/
public boolean hasErrors() {
return throwable != null;
}
/**
* Public getter for the throwable.
* @return the throwable
*/
public Throwable getLastThrowable() {
return throwable;
}
/**
* Public setter for the throwable.
* @param throwable the throwable to set
*/
public void registerThrowable(Throwable throwable) {
this.throwable = throwable;
}
/**
* Public getter for the jobExecution.
* @return the jobExecution
*/
public JobExecution getJobExecution() {
return jobExecution;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return getClass().getSimpleName()+": "+jobExecution;
}
}

View File

@@ -0,0 +1,179 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.job;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.integration.channel.PollableChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.util.Assert;
/**
* @author Dave Syer
*
*/
public class MessageOrientedStep extends AbstractStep {
/**
* Key in execution context for flag to say we are waiting.
*/
public static final String WAITING = MessageOrientedStep.class.getName() + ".WAITING";
private MessageChannel outputChannel;
private PollableChannel source;
private static long MINUTE = 1000 * 60;
private long executionTimeout = 30*MINUTE ;
private long pollingInterval = 5;
/**
* Public setter for the execution timeout in minutes. Defaults to 30.
* @param executionTimeoutMinutes the timeout to set
*/
public void setExecutionTimeoutMinutes(int executionTimeoutMinutes) {
this.executionTimeout = executionTimeoutMinutes * MINUTE;
}
/**
* Public setter for the execution timeout in milliseconds. Defaults to 30 minutes.
* @param executionTimeout
*/
public void setExecutionTimeout(long executionTimeout) {
this.executionTimeout = executionTimeout;
}
/**
* Public setter for the polling interval in milliseconds while waiting for
* replies signalling the end of the step. Defaults to 5.
* @param pollingInterval the polling interval to set
*/
public void setPollingInterval(long pollingInterval) {
this.pollingInterval = pollingInterval;
}
/**
* Public setter for the target.
* @param outputChannel the target to set
*/
@Required
public void setOutputChannel(MessageChannel outputChannel) {
this.outputChannel = outputChannel;
}
/**
* Public setter for the source.
* @param source the source to set
*/
@Required
public void setInputChannel(PollableChannel source) {
this.source = source;
}
/**
* @see AbstractStep#execute(StepExecution)
*/
@Override
protected void doExecute(StepExecution stepExecution) throws JobInterruptedException,
UnexpectedJobExecutionException {
JobExecutionRequest request = new JobExecutionRequest(stepExecution.getJobExecution());
ExecutionContext executionContext = stepExecution.getExecutionContext();
if (executionContext.containsKey(WAITING)) {
// restart scenario: we are still waiting for a response
waitForReply(request.getJobId());
}
else {
executionContext.putString(WAITING, "true");
// TODO: need these two lines to be atomic
getJobRepository().update(stepExecution);
outputChannel.send(new GenericMessage<JobExecutionRequest>(request));
waitForReply(request.getJobId());
}
stepExecution.setExitStatus(ExitStatus.COMPLETED);
}
/**
* @param expectedJobId
*/
private void waitForReply(Long expectedJobId) {
long timeout = pollingInterval;
long maxCount = executionTimeout / timeout;
long count = 0;
while (count++ < maxCount) {
// TODO: timeout?
@SuppressWarnings("unchecked")
Message<JobExecutionRequest> message = (Message<JobExecutionRequest>) source.receive(timeout);
if (message != null) {
JobExecutionRequest payload = message.getPayload();
Long jobInstanceId = payload.getJobId();
Assert.state(jobInstanceId != null, "Message did not contain job instance id.");
Assert.state(jobInstanceId.equals(expectedJobId), "Message contained wrong job instance id ["
+ jobInstanceId + "] should have been [" + expectedJobId + "].");
if (payload.getStatus() == BatchStatus.COMPLETED) {
// One of the steps decided we were finished
// TODO: wait for all the other steps that might be
// executing concurrently?
// TODO: maybe *any* reply on this channel should
// mean the end of the step?
break;
}
if (payload.hasErrors()) {
rethrow(payload.getLastThrowable());
}
}
}
if (count >= maxCount) {
throw new StepExecutionTimeoutException("Timed out waiting for steps to execute.");
}
}
/**
* @param lastThrowable
*/
private static void rethrow(Throwable t) throws RuntimeException {
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
if (t instanceof Exception) {
throw new UnexpectedJobExecutionException("Unexpected checked exception thrown by step.", t);
}
throw (Error) t;
}
}

View File

@@ -0,0 +1,193 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.job;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.StartLimitExceededException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
/**
* @author Dave Syer
*
*/
@MessageEndpoint
public class StepExecutionMessageHandler {
private Step step;
private JobRepository jobRepository;
/**
* Public setter for the {@link Step}.
* @param step the step to set
*/
@Required
public void setStep(Step step) {
this.step = step;
}
/**
* Public setter for the {@link JobRepository} that is needed to manage the
* state of the batch meta domain (jobs, steps, executions) during the life
* of a job.
*
* @param jobRepository
*/
@Required
public void setJobRepository(JobRepository jobRepository) {
this.jobRepository = jobRepository;
}
@ServiceActivator
public JobExecutionRequest handle(JobExecutionRequest request) {
// Hand off immediately if the job has already failed
if (isComplete(request)) {
return request;
}
JobExecution jobExecution = request.getJobExecution();
JobInstance jobInstance = jobExecution.getJobInstance();
StepExecution stepExecution = jobExecution.createStepExecution(step.getName());
try {
StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, step.getName());
// Even if it completed successfully we want to pass on the output
// attributes, so set up the execution context here if it is
// available.
if (lastStepExecution != null) {
stepExecution.setExecutionContext(lastStepExecution.getExecutionContext());
}
// If it is already complete and not restartable it will simply be
// skipped
if (shouldStart(lastStepExecution, step)) {
if (!isRestart(jobInstance, lastStepExecution)) {
stepExecution.setExecutionContext(new ExecutionContext());
}
jobRepository.add(stepExecution);
step.execute(stepExecution);
}
else if (lastStepExecution != null) {
/*
* We only set these if the step is not going to execute. They
* might be needed by the next step to receive the request, but
* they won't be persisted because the step is not executed.
*/
stepExecution.setStatus(lastStepExecution.getStatus());
stepExecution.setExitStatus(lastStepExecution.getExitStatus());
}
// (the job might actually not be complete, but the stage is).
request.setStatus(BatchStatus.COMPLETED);
}
catch (Exception e) {
handleFailure(request, e);
}
catch (Error e) {
handleFailure(request, e);
throw e;
}
return request;
}
/**
* @param jobInstance
* @param lastStepExecution
* @return
*/
private boolean isRestart(JobInstance jobInstance, StepExecution lastStepExecution) {
return (lastStepExecution != null && !lastStepExecution.getStatus().equals(BatchStatus.COMPLETED));
}
/**
* @param request
* @return
*/
private boolean isComplete(JobExecutionRequest request) {
return request.getStatus() == BatchStatus.FAILED || request.getStatus() == BatchStatus.ABANDONED
|| request.getStatus() == BatchStatus.STOPPING;
}
/**
* @param request
* @param e
*/
private void handleFailure(JobExecutionRequest request, Throwable e) {
request.registerThrowable(e);
request.setStatus(BatchStatus.FAILED);
}
/*
* TODO: merge this with SimpleJob implementation.
*
* Given a step and configuration, return true if the step should start,
* false if it should not, and throw an exception if the job should finish.
*/
private boolean shouldStart(StepExecution lastStepExecution, Step step) throws JobExecutionException {
BatchStatus stepStatus;
// if the last execution is null, the step has never been executed.
if (lastStepExecution == null) {
return true;
}
else {
stepStatus = lastStepExecution.getStatus();
}
if (stepStatus == BatchStatus.UNKNOWN) {
throw new JobExecutionException("Cannot restart step from UNKNOWN status. "
+ "The last execution may have ended with a failure that could not be rolled back, "
+ "so it may be dangerous to proceed. " + "Manual intervention is probably necessary.");
}
if (stepStatus == BatchStatus.COMPLETED && step.isAllowStartIfComplete() == false) {
// step is complete, false should be returned, indicating that the
// step should not be started
return false;
}
if (jobRepository.getStepExecutionCount(lastStepExecution.getJobExecution().getJobInstance(), step.getName()) < step
.getStartLimit()) {
// step start count is less than start max, return true
return true;
}
else {
// start max has been exceeded, throw an exception.
throw new StartLimitExceededException("Maximum start limit exceeded for step: " + step.getName()
+ "StartMax: " + step.getStartLimit());
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.job;
import org.springframework.batch.core.UnexpectedJobExecutionException;
/**
* @author Dave Syer
*
*/
public class StepExecutionTimeoutException extends UnexpectedJobExecutionException {
/**
* Constructs a new instance.
*
* @param msg the exception message.
*
*/
public StepExecutionTimeoutException(String msg) {
super(msg);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.launch;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
/**
* Encapsulation of a {@link Job} and its {@link JobParameters} forming a
* request for a job to be launched.
*
* @author Dave Syer
*
*/
public class JobLaunchRequest {
private final Job job;
private final JobParameters jobParameters;
/**
* @param job
* @param jobParameters
*/
public JobLaunchRequest(Job job, JobParameters jobParameters) {
super();
this.job = job;
this.jobParameters = jobParameters;
}
/**
* @return the {@link Job} to be executed
*/
public Job getJob() {
return this.job;
}
/**
* @return the {@link JobParameters} for this request
*/
public JobParameters getJobParameters() {
return this.jobParameters;
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.launch;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.integration.annotation.ServiceActivator;
/**
* Message handler which uses strategies to convert a Message into a job and a
* set of job parameters
* @author Jonas Partner
* @author Dave Syer
*
*/
public class JobLaunchingMessageHandler {
private final JobLauncher jobLauncher;
/**
* @param jobLauncher
*/
public JobLaunchingMessageHandler(JobLauncher jobLauncher) {
super();
this.jobLauncher = jobLauncher;
}
@ServiceActivator
public JobExecution launch(JobLaunchRequest request) {
Job job = request.getJob();
JobParameters jobParameters = request.getJobParameters();
try {
JobExecution execution = jobLauncher.run(job, jobParameters);
return execution;
}
catch (JobExecutionException e) {
throw new UnexpectedJobExecutionException("Exception executing job: ["+request+"]", e);
}
}
}

View File

@@ -0,0 +1,47 @@
package org.springframework.batch.integration.partition;
import java.util.Arrays;
import java.util.Collection;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.step.StepLocator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.util.Assert;
/**
* A {@link StepLocator} implementation that just looks in its enclosing bean
* factory for components of type {@link Step}.
*
* @author Dave Syer
*
*/
public class BeanFactoryStepLocator implements StepLocator, BeanFactoryAware {
private BeanFactory beanFactory;
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
/**
* Look up a bean with the provided name of type {@link Step}.
* @see StepLocator#getStep(String)
*/
public Step getStep(String stepName) {
return (Step) beanFactory.getBean(stepName, Step.class);
}
/**
* Look in the bean factory for all beans of type {@link Step}.
* @throws IllegalStateException if the {@link BeanFactory} is not listable
* @see StepLocator#getStepNames()
*/
public Collection<String> getStepNames() {
Assert.state(beanFactory instanceof ListableBeanFactory, "BeanFactory is not listable.");
return Arrays.asList(((ListableBeanFactory) beanFactory).getBeanNamesForType(Step.class));
}
}

View File

@@ -0,0 +1,138 @@
package org.springframework.batch.integration.partition;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.partition.PartitionHandler;
import org.springframework.batch.core.partition.StepExecutionSplitter;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.integration.annotation.Aggregator;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.gateway.MessagingGateway;
import org.springframework.integration.message.MessageBuilder;
import org.springframework.util.Assert;
/**
* A {@link PartitionHandler} that uses {@link MessageChannel} instances to send
* instructions to remote workers and receive their responses. The
* {@link MessageChannel} provides a nice abstraction so that the location of
* the workers and the transport used to communicate with them can be changed at
* run time. The communication with the remote workers does not need to be
* transactional or have guaranteed delivery, so a local thread pool based
* implementation works as well as a remote web service or JMS implementation.
* If a remote worker fails or doesn't send a reply message, the job will fail
* and can be restarted to pick up missing messages and processing. The remote
* workers need access to the Spring Batch {@link JobRepository} so that the
* shared state across those restarts can be managed centrally.
*
* @author Dave Syer
*
*/
@MessageEndpoint
public class MessageChannelPartitionHandler implements PartitionHandler {
private int gridSize = 1;
private MessagingGateway messagingGateway;
private String stepName;
public void afterPropertiesSet() throws Exception {
Assert.notNull(stepName, "A step name must be provided for the remote workers.");
Assert.state(messagingGateway != null, "The MessagingGateway must be set");
}
/**
* A pre-configured gateway for sending and receiving messages to the remote
* workers. Using this property allows a large degree of control over the
* timeouts and other properties of the send. It should have channels set up
* internally:
* <ul>
* <li>request channel capable of accepting {@link StepExecutionRequest}
* payloads</li>
* <li>reply channel that returns a list of {@link StepExecution} results</li>
* </ul>
* The timeout for the repoy should be set sufficiently long that the remote
* steps have time to complete.
*
* @param messagingGateway the {@link MessagingGateway} to set
*/
public void setMessagingGateway(MessagingGateway messagingGateway) {
this.messagingGateway = messagingGateway;
}
/**
* Passed to the {@link StepExecutionSplitter} in the
* {@link #handle(StepExecutionSplitter, StepExecution)} method, instructing
* it how many {@link StepExecution} instances are required, ideally. The
* {@link StepExecutionSplitter} is allowed to ignore the grid size in the
* case of a restart, since the input data partitions must be preserved.
*
* @param gridSize the number of step executions that will be created
*/
public void setGridSize(int gridSize) {
this.gridSize = gridSize;
}
/**
* The name of the {@link Step} that will be used to execute the partitioned
* {@link StepExecution}. This is a regular Spring Batch step, with all the
* business logic required to complete an execution based on the input
* parameters in its {@link StepExecution} context. The name will be
* translated into a {@link Step} instance by the remote worker.
*
* @param stepName the name of the {@link Step} instance to execute business
* logic
*/
public void setStepName(String stepName) {
this.stepName = stepName;
}
/**
* @param messages the messages to be aggregated
* @return the list as it was passed in
*/
@Aggregator(sendPartialResultsOnTimeout = true)
public List<?> aggregate(List<?> messages) {
return messages;
}
/**
* Sends {@link StepExecutionRequest} objects to the request channel of the
* {@link MessagingGateway}, and then receives the result back as a list of
* {@link StepExecution} on a reply channel. Use the
* {@link #aggregate(List)} method as an aggregator of the individual remote
* replies. The receive timeout needs to be set realistically in the
* {@link MessagingGateway} <b>and</b> the aggregator, so that there is a
* good chance of all work being done.
*
* @see PartitionHandler#handle(StepExecutionSplitter, StepExecution)
*/
public Collection<StepExecution> handle(StepExecutionSplitter stepExecutionSplitter,
StepExecution masterStepExecution) throws Exception {
Set<StepExecution> split = stepExecutionSplitter.split(masterStepExecution, gridSize);
int count = 0;
for (StepExecution stepExecution : split) {
messagingGateway.send(createMessage(count++, split.size(), new StepExecutionRequest(stepName, stepExecution
.getJobExecutionId(), stepExecution.getId())));
}
@SuppressWarnings("unchecked")
Collection<StepExecution> result = (Collection<StepExecution>) messagingGateway.receive();
return result;
}
private Message<StepExecutionRequest> createMessage(int sequenceNumber, int sequenceSize,
StepExecutionRequest stepExecutionRequest) {
return MessageBuilder.withPayload(stepExecutionRequest).setSequenceNumber(sequenceNumber).setSequenceSize(
sequenceSize).setCorrelationId(
stepExecutionRequest.getJobExecutionId() + ":" + stepExecutionRequest.getStepName()).build();
}
}

View File

@@ -0,0 +1,37 @@
package org.springframework.batch.integration.partition;
import java.io.Serializable;
public class StepExecutionRequest implements Serializable {
private final Long stepExecutionId;
private final String stepName;
private final Long jobExecutionId;
public StepExecutionRequest(String stepName, Long jobExecutionId, Long stepExecutionId) {
this.stepName = stepName;
this.jobExecutionId = jobExecutionId;
this.stepExecutionId = stepExecutionId;
}
public Long getJobExecutionId() {
return jobExecutionId;
}
public Long getStepExecutionId() {
return stepExecutionId;
}
public String getStepName() {
return stepName;
}
@Override
public String toString() {
return String.format("StepExecutionRequest: [jobExecutionId=%d, stepExecutionId=%d, stepName=%s]",
jobExecutionId, stepExecutionId, stepName);
}
}

View File

@@ -0,0 +1,81 @@
package org.springframework.batch.integration.partition;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.step.StepLocator;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
/**
* A {@link MessageEndpoint} that can handle a {@link StepExecutionRequest} and
* return a {@link StepExecution} as the result. Typically these need to be
* aggregated into a response to a partition handler.
*
* @author Dave Syer
*
*/
@MessageEndpoint
public class StepExecutionRequestHandler {
private JobExplorer jobExplorer;
private StepLocator stepLocator;
/**
* Used to locate a {@link Step} to execute for each request.
* @param stepLocator a {@link StepLocator}
*/
public void setStepLocator(StepLocator stepLocator) {
this.stepLocator = stepLocator;
}
/**
* An explorer that should be used to check for {@link StepExecution}
* completion.
*
* @param jobExplorer a {@link JobExplorer} that is linked to the shared
* repository used by all remote workers.
*/
public void setJobExplorer(JobExplorer jobExplorer) {
this.jobExplorer = jobExplorer;
}
@ServiceActivator
public StepExecution handle(StepExecutionRequest request) {
Long jobExecutionId = request.getJobExecutionId();
Long stepExecutionId = request.getStepExecutionId();
StepExecution stepExecution = jobExplorer.getStepExecution(jobExecutionId, stepExecutionId);
if (stepExecution == null) {
// TODO: create new Exception
throw new IllegalStateException("No StepExecution could be located for this request: " + request);
}
String stepName = request.getStepName();
Step step = stepLocator.getStep(stepName);
if (step == null) {
// TODO: create new Exception
throw new IllegalStateException(String.format("No Step with name [%s] could be located.", stepName));
}
try {
step.execute(stepExecution);
}
catch (JobInterruptedException e) {
stepExecution.setStatus(BatchStatus.STOPPED);
// TODO: maybe update stepExecution in repository
}
catch (Throwable e) {
stepExecution.addFailureException(e);
stepExecution.setStatus(BatchStatus.FAILED);
// TODO: maybe update stepExecution in repository
}
return stepExecution;
}
}

View File

@@ -0,0 +1,187 @@
------
Spring Integration Batch
------
Dave Syer
------
March 2008
Overview of the Spring Integration Batch Module
Many use cases in Spring Batch look like they might be efficiently and concisely implemented in Spring Integration. Here is a list. These are features that can extend Spring Batch, or use Spring batch features in the context of Spring Integration. Work in progress waiting for community feedback. Many issues to do with transactionality and synchronous execution have been raised and fixed in Spring Integration as a result of these use cases being prototyped.
*---+---+---+---+---+
|<<ID>>|<<Description>>|<<Status>>|<<Sub-package>>|<<Comments>>|
*----
|1|{{{Triggers}Message triggers job}}|Complete|launch|Complete. Also lots of opportunities with monitoring progress.|
*----
|2|{{{Chunking}Chunking and multi-VM job execution}}|Complete|chunk|Failures might need some analysis. Use of stateful StepExecutionListener requires use of step scope.|
*----
|3|{{{Aggregator}Asynchronous Aggregator}}|Unstarted| | |
*----
|4|{{{jobs}Stateful and non-linear jobs}} -> job = flow|Complete|job|Simple use cases work well with Spring Batch 2.0 and no Integration features.|
*----
|5|{{{Flexible}Flexible item processing model}} (as message flow) -> step = flow|Complete|item|Complete (v. simple using MessagingGateway). Unit tests only.|
*----
|6|{{{repeat}Automatic repeat / retry}}|Complete|retry (unit test)|Unit tests only, since it just uses existing features.|
*----
|7|{{{files}Restartable file processing}}|Complete|file|Seems to hang together. Not tested thoroughly, but apparently someone is using it.|
*----
|8|{{{async}Asynchronous item processing}}|Complete|async|A general purpose ItemProcesor that returns a Future.|
*----
Numbers 2, 4, 5 have also been identified as high level Spring Batch 2.0 Features or themes. If we implement 1, then we also don't need to do any more scheduling and triggering in Spring Batch.
Number 6 from the list (repeat/retry) is more of a Spring Integration pattern than a Spring Batch one. We implemented it in Spring Batch first, with an eye to seeing about pushing it out into Spring Integration later (with probably a split of repeat/retry out of Batch at that time).
* Message {Triggers} Job
Description:
[[1]] User sends message to channel (maybe through a scheduler)
[[1]] System interprets message payload as parameters for JobLauncher
[[1]] System launches job execution
[[1]] If message had a replyTo, System acknowledges with JobExecution
[[1]] User accepts response and uses it to monitor progress
Variation:
[[1]] System waits for job to finish and replies when it is over
[[1]] User polls for replies and gets notification about end of execution
Variation:
[[1]] User wants to block on send and only receive response when job is done
* {Chunking} and Multi-VM
Description:
[[1]] Step flushes chunk as message to outgoing channel (repeat up to throttle limit)
[[1]] Worker thread picks up chunk and processes it
[[1]] Worker thread replies to response channel
[[1]] Step picks up reply and aggregates the counts
[[1]] Step blocks until all the requests are satisfied
TODO: failure modes
* Asynchronous {Aggregator}
Job is executed over long period. Many jobs can be executing concurrently.
Description:
[[1]] Input stage for each job: System reads all items and marks with the job instance id in a durable repository (staging table)
[[1]] System sends each item (or chunks of items that can be processed together as appropriate) to a channel
[[1]] Items flow through message pipeline, occasionally pausing until certain conditions are met, possibly for days at a time
[[1]] Aggregator sits and waits for all items in a job to be finished and then wraps up
* Stateful and non-linear {jobs}
Dependencies beyween steps and conditional flow between steps. Each handler node in a message flow is a step execution, with all the robustness guarantees from the Spring Batch meta data.
Description:
[[1]] User launches job
[[1]] System sends message to channel containing job execution
[[1]] Handler accepts message and executes a step
[[1]] Handler translates result of step execution into the same form that it accepted the original request
[[1]] System routes message to next handler, possibly dynamically based on data in the message
[[1]] Next handler does the same... until one of the routing decisions leads to a reply channel
[[1]] System receives reply and transfers information to job execution (e.g. status) as necessary
Variation: failure in one of the handlers
Variation: restart after failure
* {Flexible} item processing model
Description:
[[1]] Step hands item to ItemWriter
[[1]] Item is converted to message and sent to synchronous flow
[[1]] Handler accepts message and does something with item
[[1]] System routes result to next handler, possibly dynamically
Variation: failure
[[1]] Handler throws exception
[[1]] System propagates exception up to ItemWriter (forces rollback under normal circs - hence synchronous flow)
* Automatic repeat / retry
Description ({repeat}):
[[1]] User sends message to channel
[[1]] System start a transaction and reseives message, then processes it
[[1]] User sends another message
[[1]] System receives and processes it in the same transaction
[[1]] ... repeat ...
[[1]] System determines that batch is complete and commits transaction
* Restartable file processing
Large {files} need to be processed, so message payload of file contents is not practical. One line or XML event per message with failover and restartability from Spring Batch.
Description:
[[1]] User triggers file processing (sends message, copies file to directory, etc.)
[[1]] System starts new job
[[1]] System processes file line by line (or even by event), wrapping each one as a message and sending it to a synchronous flow
[[1]] System commits periodically (as determined by Spring Batch step configuration)
Variation: failure and restart
[[1]] Item processing fails
[[1]] System aborts job and sends message to failure channel (or failure message to normal reply channel)
[[1]] Operator fixes problem and triggers restart (another message channel?)
[[1]] System restarts job for same file at point where it left off
[[1]] System completes processing
[[1]] System sends sucess message to reply channel
Variation: send to asynchronous flow. Same as main use case but item message is sent to asynchronous flow. Not as robust because if the lights go out then meesages will be lost, but at least a large file can be split into smaller chunks.
* Asynchronous item processing
This is actually a variation on {{{Flexible}flexible item processing model}}.
Description ({async}):
[[1]] ItemProcessor executes in background (non-transactionally)
[[1]] ItemWriter collects outputs from futures before phyically writing data

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<project name="Spring Batch: ${project.name}">
<body>
<links>
<item name="${project.name}" href="index.html"/>
</links>
<menu name="Documentation">
<item name="${project.name}" href="index.html"/>
</menu>
<menu ref="reports"/>
</body>
</project>

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration;
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;
/**
* @author Dave Syer
*
*/
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, jobParameters, jobName));
}
/* (non-Javadoc)
* @see org.springframework.batch.core.repository.JobRepository#getLastStepExecution(org.springframework.batch.core.JobInstance, org.springframework.batch.core.Step)
*/
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)
*/
public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) {
return null;
}
}

View File

@@ -0,0 +1,30 @@
package org.springframework.batch.integration;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersIncrementer;
public class JobSupport implements Job {
String name;
public JobSupport(String name){
this.name = name;
}
public void execute(JobExecution execution) {
}
public String getName() {
return name;
}
public boolean isRestartable() {
return false;
}
public JobParametersIncrementer getJobParametersIncrementer() {
return null;
}
}

View File

@@ -0,0 +1,59 @@
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.beans.factory.annotation.Qualifier;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.PollableChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@MessageEndpoint
public class SmokeTests {
@Autowired
@Qualifier("smokein")
private MessageChannel smokein;
@Autowired
@Qualifier("smokeout")
private PollableChannel smokeout;
// This has to be static because the MessageBus 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;
}
@Test
public void testDummyWithSimpleAssert() throws Exception {
assertTrue(true);
}
@Test
public void testVanillaSendAndReceive() throws Exception {
smokein.send(new GenericMessage<String>("foo"));
@SuppressWarnings("unchecked")
Message<String> message = (Message<String>) smokeout.receive(100);
String result = (String) (message == null ? null : message.getPayload());
assertEquals("foo: 1", result);
assertEquals(1, count);
}
}

View File

@@ -0,0 +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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration;
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;
/**
* @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;
}
}

View File

@@ -0,0 +1,59 @@
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.Future;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.item.ItemProcessor;
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 AsyncItemProcessor<String, String> processor = new AsyncItemProcessor<String, String>();
@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<Future<String>>();
for (int count = 0; count < 10; count++) {
list.add(processor.process("foo" + count));
}
for (Future<String> future : list) {
String value = future.get();
/**
* TODO: 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. Maybe we should have an option 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) {
return value + value;
}
}
}

View File

@@ -0,0 +1,49 @@
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.Future;
import org.junit.Test;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
public class AsyncItemProcessorTests {
private AsyncItemProcessor<String, String> processor = new AsyncItemProcessor<String, String>();
private ItemProcessor<String, String> delegate = new ItemProcessor<String, String>() {
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 testMultiExecution() throws Exception {
processor.setDelegate(delegate);
processor.setTaskExecutor(new SimpleAsyncTaskExecutor());
List<Future<String>> list = new ArrayList<Future<String>>();
for (int count = 0; count < 10; count++) {
list.add(processor.process("foo" + count));
}
for (Future<String> future : list) {
assertTrue(future.get().matches("foo.*foo.*"));
}
}
}

View File

@@ -0,0 +1,315 @@
package org.springframework.batch.integration.chunk;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
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.JobRestartException;
import org.springframework.batch.core.repository.dao.MapExecutionContextDao;
import org.springframework.batch.core.repository.dao.MapJobExecutionDao;
import org.springframework.batch.core.repository.dao.MapJobInstanceDao;
import org.springframework.batch.core.repository.dao.MapStepExecutionDao;
import org.springframework.batch.core.repository.support.SimpleJobRepository;
import org.springframework.batch.core.step.item.SimpleStepFactoryBean;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.integration.channel.PollableChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.gateway.SimpleMessagingGateway;
import org.springframework.integration.message.GenericMessage;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StringUtils;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class ChunkMessageItemWriterIntegrationTests {
private ChunkMessageChannelItemWriter<Object> writer = new ChunkMessageChannelItemWriter<Object>();
@Autowired
@Qualifier("requests")
private MessageChannel requests;
@Autowired
@Qualifier("replies")
private PollableChannel replies;
private SimpleStepFactoryBean<Object, Object> factory = new SimpleStepFactoryBean<Object, Object>();
private SimpleJobRepository jobRepository;
private static long jobCounter;
@Before
public void setUp() {
jobRepository = new SimpleJobRepository(new MapJobInstanceDao(), new MapJobExecutionDao(),
new MapStepExecutionDao(), new MapExecutionContextDao());
factory.setJobRepository(jobRepository);
factory.setTransactionManager(new ResourcelessTransactionManager());
factory.setBeanName("step");
factory.setItemWriter(writer);
factory.setCommitInterval(4);
SimpleMessagingGateway gateway = new SimpleMessagingGateway();
writer.setMessagingGateway(gateway);
gateway.setRequestChannel(requests);
gateway.setReplyChannel(replies);
gateway.setReplyTimeout(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.getLong(ChunkMessageChannelItemWriter.EXPECTED));
assertEquals(0, executionContext.getLong(ChunkMessageChannelItemWriter.ACTUAL));
}
@Test
public void testVanillaIteration() throws Exception {
factory.setItemReader(new ListItemReader<String>(Arrays.asList(StringUtils
.commaDelimitedListToStringArray("1,2,3,4,5,6"))));
Step 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<String>(Arrays.asList(StringUtils
.commaDelimitedListToStringArray("1,2,3,4,5,6"))));
Step step = (Step) factory.getObject();
StepExecution stepExecution = getStepExecution(step);
// Set up context with two messages (chunks) in the backlog
stepExecution.getExecutionContext().putLong(ChunkMessageChannelItemWriter.EXPECTED, 6);
stepExecution.getExecutionContext().putLong(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<String>(Arrays.asList(StringUtils
.commaDelimitedListToStringArray("1,2,3,4,5,6"))));
Step step = (Step) factory.getObject();
StepExecution stepExecution = getStepExecution(step);
// Set up context with two messages (chunks) in the backlog
stepExecution.getExecutionContext().putLong(ChunkMessageChannelItemWriter.EXPECTED, 3);
stepExecution.getExecutionContext().putLong(ChunkMessageChannelItemWriter.ACTUAL, 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());
}
/**
* @param jobId
* @param string
* @return
*/
@SuppressWarnings("unchecked")
private GenericMessage<ChunkRequest> getSimpleMessage(String string, Long jobId) {
StepContribution stepContribution = new JobExecution(new JobInstance(0L, new JobParameters(), "job"), 1L)
.createStepExecution("step").createStepContribution();
ChunkRequest chunk = new ChunkRequest(StringUtils.commaDelimitedListToSet(string), jobId, stepContribution);
GenericMessage<ChunkRequest> message = new GenericMessage<ChunkRequest>(chunk);
return message;
}
@Test
public void testEarlyCompletionSignalledInHandler() throws Exception {
factory.setItemReader(new ListItemReader<String>(Arrays.asList(StringUtils
.commaDelimitedListToStringArray("1,bad,3,4,5,6"))));
factory.setCommitInterval(2);
Step 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 'bad': " + message, message.contains("bad"));
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<String>(Arrays.asList(StringUtils
.commaDelimitedListToStringArray("1,2,3,4,5,6"))));
Step step = (Step) factory.getObject();
StepExecution stepExecution = getStepExecution(step);
// Set up expectation of three messages (chunks) in the backlog
stepExecution.getExecutionContext().putLong(ChunkMessageChannelItemWriter.EXPECTED, 6);
stepExecution.getExecutionContext().putLong(ChunkMessageChannelItemWriter.ACTUAL, 3);
/*
* 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.
*
* @throws Exception
*/
@Test
public void testFailureInStepListener() throws Exception {
factory.setItemReader(new ListItemReader<String>(Arrays.asList(StringUtils
.commaDelimitedListToStringArray("wait,bad,3,4,5,6"))));
Step 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 failure in chunk handler
// TODO : test non-dispatch of empty chunk
/**
* @param expected
* @param maxWait
* @throws InterruptedException
*/
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;
}
}

View File

@@ -0,0 +1,36 @@
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<Object>();
protected int count = 0;
@Test
public void testVanillaHandleChunk() {
handler.setChunkProcessor(new ChunkProcessor<Object>() {
public void process(StepContribution contribution, Chunk<Object> chunk) throws Exception {
count += chunk.size();
}
});
StepContribution stepContribution = MetaDataInstanceFactory.createStepExecution().createStepContribution();
@SuppressWarnings("unchecked")
ChunkResponse response = handler.handleChunk(new ChunkRequest<Object>(StringUtils
.commaDelimitedListToSet("foo,bar"), 12L, stepContribution));
assertEquals(stepContribution, response.getStepContribution());
assertEquals(12, response.getJobId().longValue());
assertTrue(response.isSuccessful());
assertEquals(2, count);
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.chunk;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.apache.commons.lang.SerializationUtils;
import org.junit.Test;
import org.springframework.batch.test.MetaDataInstanceFactory;
/**
* @author Dave Syer
*
*/
public class ChunkRequestTests {
private ChunkRequest<String> request = new ChunkRequest<String>(Arrays.asList("foo", "bar"), 111L,
MetaDataInstanceFactory.createStepExecution().createStepContribution());
@Test
public void testGetJobId() {
assertEquals(new Long(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(new Long(111L), result.getJobId());
assertEquals(2, result.getItems().size());
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.chunk;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.apache.commons.lang.SerializationUtils;
import org.junit.Test;
import org.springframework.batch.test.MetaDataInstanceFactory;
/**
* @author Dave Syer
*
*/
public class ChunkResponseTests {
private ChunkResponse response = new ChunkResponse(111L, MetaDataInstanceFactory.createStepExecution()
.createStepContribution());
@Test
public void testGetJobId() {
assertEquals(new Long(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(new Long(111L), result.getJobId());
}
}

View File

@@ -0,0 +1,36 @@
package org.springframework.batch.integration.chunk;
import static org.junit.Assert.assertEquals;
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.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 ChunkStepIntegrationTests {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job job;
@Test
public void testOpenWithNoState() throws Exception {
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
StepExecution stepExecution = jobExecution.getStepExecutions().iterator().next();
assertEquals(9, stepExecution.getReadCount());
assertEquals(9, stepExecution.getWriteCount());
}
}

View File

@@ -0,0 +1,70 @@
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.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 static 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<T>();
/**
* @param items the items to set
*/
public void setItems(List<T> items) {
this.items = items;
}
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;
}
}

View File

@@ -0,0 +1,56 @@
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 = "bad";
/**
* 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);
}
}
}
}

View File

@@ -0,0 +1,194 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.file;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.BatchStatus;
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.repository.JobRepository;
import org.springframework.batch.integration.JobRepositorySupport;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.PassThroughLineMapper;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.MessageHandler;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.ReflectionUtils;
/**
* @author Dave Syer
*
*/
public class FileToMessagesJobFactoryBeanTests {
private static final String FILE_INPUT_PATH = ResourcePayloadAsJobParameterStrategy.FILE_INPUT_PATH;
private FileToMessagesJobFactoryBean<String> factory = new FileToMessagesJobFactoryBean<String>();
private DirectChannel channel = new DirectChannel();
private List<String> receiver = new ArrayList<String>();
private JobRepositorySupport jobRepository;
@Before
public void setUp() {
jobRepository = new JobRepositorySupport();
factory.setJobRepository(jobRepository);
factory.setTransactionManager(new ResourcelessTransactionManager());
FlatFileItemReader<String> itemReader = new FlatFileItemReader<String>();
itemReader.setLineMapper(new PassThroughLineMapper());
factory.setItemReader(itemReader);
factory.setChannel(channel);
channel.subscribe(new MessageHandler() {
public void handleMessage(Message<?> message) {
// TODO: Ask Mark: unsafe cast...
receiver.add((String) message.getPayload());
}
});
}
/**
* Test method for
* {@link org.springframework.batch.integration.file.FileToMessagesJobFactoryBean#setBeanName(java.lang.String)}.
* @throws Exception
*/
@Test
public void testSetBeanName() throws Exception {
assertNotNull(((Job) factory.getObject()).getName());
}
/**
* Test method for
* {@link org.springframework.batch.integration.file.FileToMessagesJobFactoryBean#setItemReader(org.springframework.batch.item.ItemReader)}.
*/
@Test
public void testSetItemReader() {
Method method = ReflectionUtils.findMethod(FileToMessagesJobFactoryBean.class, "setItemReader",
new Class<?>[] { ItemReader.class });
assertNotNull(method);
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
assertEquals(1, annotations.length);
assertEquals(Required.class, annotations[0].annotationType());
}
/**
* Test method for
* {@link FileToMessagesJobFactoryBean#setChannel(MessageChannel)}.
*/
@Test
public void testSetChannel() {
Method method = ReflectionUtils.findMethod(FileToMessagesJobFactoryBean.class, "setChannel",
new Class<?>[] { MessageChannel.class });
assertNotNull(method);
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
assertEquals(1, annotations.length);
assertEquals(Required.class, annotations[0].annotationType());
}
/**
* Test method for
* {@link org.springframework.batch.integration.file.FileToMessagesJobFactoryBean#setJobRepository(org.springframework.batch.core.repository.JobRepository)}.
*/
@Test
public void testSetJobRepository() {
Method method = ReflectionUtils.findMethod(FileToMessagesJobFactoryBean.class, "setJobRepository",
new Class<?>[] { JobRepository.class });
assertNotNull(method);
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
assertEquals(1, annotations.length);
assertEquals(Required.class, annotations[0].annotationType());
}
/**
* Test method for
* {@link org.springframework.batch.integration.file.FileToMessagesJobFactoryBean#setTransactionManager(org.springframework.transaction.PlatformTransactionManager)}.
*/
@Test
public void testSetTransactionManager() {
Method method = ReflectionUtils.findMethod(FileToMessagesJobFactoryBean.class, "setTransactionManager",
new Class<?>[] { PlatformTransactionManager.class });
assertNotNull(method);
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
assertEquals(1, annotations.length);
assertEquals(Required.class, annotations[0].annotationType());
}
/**
* Test method for
* {@link org.springframework.batch.integration.file.FileToMessagesJobFactoryBean#getObject()}.
* @throws Exception
*/
@Test
public void testGetObjectNotBroken() throws Exception {
assertNotNull(factory.getObject());
}
/**
* Test method for
* {@link org.springframework.batch.integration.file.FileToMessagesJobFactoryBean#getObjectType()}.
*/
@Test
public void testGetObjectType() {
FileToMessagesJobFactoryBean<?> factory = new FileToMessagesJobFactoryBean<Object>();
assertEquals(Job.class, factory.getObjectType());
}
/**
* Test method for
* {@link org.springframework.batch.integration.file.FileToMessagesJobFactoryBean#isSingleton()}.
*/
@Test
public void testIsSingleton() {
FileToMessagesJobFactoryBean<?> factory = new FileToMessagesJobFactoryBean<Object>();
assertEquals(true, factory.isSingleton());
}
@Test
public void testVanillaJobExecution() throws Exception {
Job job = (Job) factory.getObject();
JobParameters jobParameters = new JobParametersBuilder().addString(FILE_INPUT_PATH, "classpath:/log4j.properties").toJobParameters();
JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters);
job.execute(jobExecution);
assertNotNull(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
String payload;
// first line from properties file
payload = receiver.get(0);
assertNotNull(payload);
// second line from properties file
payload = receiver.get(1);
assertNotNull(payload);
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.file;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import org.springframework.batch.core.JobParameters;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.integration.core.Message;
import org.springframework.integration.message.GenericMessage;
/**
* @author Dave Syer
*
*/
public class ResourcePayloadAsJobParameterStrategyTests {
/**
*
*/
private static final String INPUT_FILE_PATH = ResourcePayloadAsJobParameterStrategy.FILE_INPUT_PATH;
/**
* Test method for {@link ResourcePayloadAsJobParameterStrategy#getJobParameters(Message)}.
*/
@Test
public void testGetJobParameters() {
ResourcePayloadAsJobParameterStrategy strategy = new ResourcePayloadAsJobParameterStrategy();
JobParameters parameters = strategy.getJobParameters(new GenericMessage<Resource>(new ClassPathResource("log4j.properties")));
assertTrue(parameters.getParameters().containsKey(INPUT_FILE_PATH));
}
/**
* Test method for {@link ResourcePayloadAsJobParameterStrategy#getJobParameters(Message)}.
*/
@Test
public void testGetJobParametersWithWrongPayload() {
ResourcePayloadAsJobParameterStrategy strategy = new ResourcePayloadAsJobParameterStrategy();
try {
strategy.getJobParameters(new GenericMessage<String>("log4j.properties"));
fail("Expected ClassCastException");
} catch (ClassCastException e) {
String message = e.getMessage();
assertTrue("Wrong message: "+message, message.contains("String"));
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.file;
import static org.junit.Assert.assertNotNull;
import java.util.Arrays;
import java.util.List;
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.integration.channel.PollableChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.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)...<br/>
*
* 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
public void testVanillaConversion() throws Exception {
resources.send(new GenericMessage<String>("classpath:*-context.xml"));
Message<Resource> message = (Message<Resource>) requests.receive(200L);
assertNotNull(message);
message = (Message<Resource>) requests.receive(100L);
assertNotNull(message);
}
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.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 Sprng
* Integration MessageChannel.
*
* @author Dave Syer
*
*/
@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 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;
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.job;
import static org.junit.Assert.assertEquals;
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.JobParameters;
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 MessageOrientedStepIntegrationTests {
@Autowired
private JobLauncher jobLauncher;
@Autowired
@Qualifier("job")
private Job job;
@Test
public void testLaunchJob() throws Exception {
JobExecution jobExecution = jobLauncher.run(job, new JobParameters());
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.job;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
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.StepExecution;
import org.springframework.batch.integration.JobRepositorySupport;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.PollableChannel;
import org.springframework.integration.channel.ThreadLocalChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.message.MessageHandler;
import org.springframework.util.ReflectionUtils;
/**
* @author Dave Syer
*
*/
public class MessageOrientedStepTests {
private MessageOrientedStep step = new MessageOrientedStep();
private JobExecution jobExecution;
private DirectChannel requestChannel;
private PollableChannel replyChannel;
@Before
public void createStep() {
replyChannel = new ThreadLocalChannel();
requestChannel = new DirectChannel();
step.setName("step");
step.setOutputChannel(requestChannel);
step.setInputChannel(replyChannel);
step.setStartLimit(10);
step.setJobRepository(new JobRepositorySupport());
JobInstance jobInstance = new JobInstance(0L, new JobParameters(), "job");
jobExecution = new JobExecution(jobInstance);
}
@Test
public void testSetRequestChannel() {
Method method = ReflectionUtils.findMethod(MessageOrientedStep.class, "setOutputChannel",
new Class<?>[] { MessageChannel.class });
assertNotNull(method);
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
assertEquals(1, annotations.length);
assertEquals(Required.class, annotations[0].annotationType());
}
@Test
public void testSetReplyChannel() {
Method method = ReflectionUtils.findMethod(MessageOrientedStep.class, "setInputChannel",
new Class<?>[] { PollableChannel.class });
assertNotNull(method);
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
assertEquals(1, annotations.length);
assertEquals(Required.class, annotations[0].annotationType());
}
/**
* Test method for
* {@link org.springframework.batch.integration.job.MessageOrientedStep#execute(org.springframework.batch.core.StepExecution)}
* .
* @throws Exception
*/
@Test
public void testExecuteWithTimeout() throws Exception {
requestChannel.subscribe(new MessageHandler() {
public void handleMessage(Message<?> message) {
}
});
step.setExecutionTimeout(1000);
step.setPollingInterval(100);
StepExecution stepExecution = jobExecution.createStepExecution(step.getName());
step.execute(stepExecution);
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
assertEquals(ExitStatus.FAILED.getExitCode(), stepExecution.getExitStatus().getExitCode());
String message = stepExecution.getExitStatus().getExitDescription();
assertTrue("Wrong message: " + message, message.contains("StepExecutionTimeoutException"));
}
@Test
public void testVanillaExecute() throws Exception {
requestChannel.subscribe(new MessageHandler() {
public void handleMessage(Message<?> message) {
JobExecutionRequest jobExecution = (JobExecutionRequest) message.getPayload();
jobExecution.setStatus(BatchStatus.COMPLETED);
replyChannel.send(message);
}
});
step.execute(jobExecution.createStepExecution(step.getName()));
}
@Test
public void testExecuteWithFailure() throws Exception {
requestChannel.subscribe(new MessageHandler() {
public void handleMessage(Message<?> message) {
JobExecutionRequest jobExecution = (JobExecutionRequest) message.getPayload();
jobExecution.registerThrowable(new RuntimeException("Planned failure"));
replyChannel.send(message);
}
});
StepExecution stepExecution = jobExecution.createStepExecution(step.getName());
step.execute(stepExecution);
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
assertEquals(ExitStatus.FAILED.getExitCode(), stepExecution.getExitStatus().getExitCode());
String message = stepExecution.getExitStatus().getExitDescription();
assertTrue("Wrong message: " + message, message.contains("Planned failure"));
}
@Test
public void testExecuteOnRestart() throws Exception {
JobExecutionRequest jobExecutionRequest = new JobExecutionRequest(jobExecution);
jobExecutionRequest.setStatus(BatchStatus.COMPLETED);
// Send a message to the reply channel to simulate step that we were
// waiting for when we failed on the last execution.
replyChannel.send(new GenericMessage<JobExecutionRequest>(jobExecutionRequest));
StepExecution stepExecution = jobExecution.createStepExecution(step.getName());
stepExecution.getExecutionContext().putString(MessageOrientedStep.WAITING, "true");
step.execute(stepExecution);
}
}

View File

@@ -0,0 +1,230 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.job;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StartLimitExceededException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.integration.JobRepositorySupport;
import org.springframework.batch.integration.StepSupport;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
/**
* @author Dave Syer
*
*/
public class StepExecutionMessageHandlerTests {
/**
* Test method for
* {@link org.springframework.batch.integration.job.StepExecutionMessageHandler#setStep(org.springframework.batch.core.Step)}
* .
*/
@Test
public void testSetStep() {
Method method = ReflectionUtils.findMethod(StepExecutionMessageHandler.class, "setStep",
new Class<?>[] { Step.class });
assertNotNull(method);
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
assertEquals(1, annotations.length);
assertEquals(Required.class, annotations[0].annotationType());
}
/**
* Test method for
* {@link org.springframework.batch.integration.job.StepExecutionMessageHandler#setJobRepository(org.springframework.batch.core.repository.JobRepository)}
* .
*/
@Test
public void testSetJobRepository() {
Method method = ReflectionUtils.findMethod(StepExecutionMessageHandler.class, "setJobRepository",
new Class<?>[] { JobRepository.class });
assertNotNull(method);
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
assertEquals(1, annotations.length);
assertEquals(Required.class, annotations[0].annotationType());
}
@Test
public void testVanillaHandle() throws Exception {
JobRepositorySupport jobRepository = new JobRepositorySupport();
StepExecutionMessageHandler handler = createHandler(jobRepository);
JobExecutionRequest message = handler.handle(new JobExecutionRequest(jobRepository.createJobExecution("job",
new JobParameters())));
assertEquals(1, message.getJobExecution().getStepExecutions().size());
assertEquals(BatchStatus.COMPLETED, message.getStatus());
}
@Test
public void testHandleWithInputs() throws Exception {
JobRepositorySupport jobRepository = new JobRepositorySupport();
StepExecutionMessageHandler handler = createHandler(jobRepository);
JobExecutionRequest jobExecutionRequest = new JobExecutionRequest(jobRepository.createJobExecution("job",
new JobParameters()));
jobExecutionRequest.getJobExecution().getExecutionContext().putString("foo", "bar");
JobExecutionRequest message = handler.handle(jobExecutionRequest);
assertEquals(1, message.getJobExecution().getStepExecutions().size());
JobExecution jobExecution = message.getJobExecution();
assertTrue(jobExecution.getExecutionContext().containsKey("foo"));
}
@Test
public void testHandleWithInputsAndOutputs() throws Exception {
JobRepositorySupport jobRepository = new JobRepositorySupport();
StepExecutionMessageHandler handler = createHandler(jobRepository);
JobExecutionRequest jobExecutionRequest = new JobExecutionRequest(jobRepository.createJobExecution("job",
new JobParameters()));
jobExecutionRequest.getJobExecution().getExecutionContext().putString("foo", "bar");
// The step has to add the output attribute to the context
handler.setStep(new StepSupport("step") {
@Override
public void execute(StepExecution stepExecution) throws JobInterruptedException {
stepExecution.getJobExecution().getExecutionContext().putString("bar", "spam");
}
});
JobExecutionRequest message = handler.handle(jobExecutionRequest);
JobExecution jobExecution = message.getJobExecution();
assertTrue(jobExecution.getExecutionContext().containsKey("foo"));
assertTrue(jobExecution.getExecutionContext().containsKey("bar"));
}
@Test
public void testHandleFailedJob() throws Exception {
JobRepositorySupport jobRepository = new JobRepositorySupport();
StepExecutionMessageHandler handler = createHandler(jobRepository);
JobExecution jobExecution = jobRepository.createJobExecution("job", new JobParameters());
jobExecution.setStatus(BatchStatus.FAILED);
JobExecutionRequest message = handler.handle(new JobExecutionRequest(jobExecution));
assertEquals(0, message.getJobExecution().getStepExecutions().size());
}
@Test
public void testHandleRestart() throws Exception {
JobRepositorySupport jobRepository = new JobRepositorySupport() {
@Override
public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) {
StepExecution stepExecution = new StepExecution(stepName, new JobExecution(jobInstance));
stepExecution.setStatus(BatchStatus.FAILED);
stepExecution.setExecutionContext(new ExecutionContext() {
{
put("foo", "bar");
}
});
return stepExecution;
}
@Override
public int getStepExecutionCount(JobInstance jobInstance, String stepName) {
return 1;
}
};
StepExecutionMessageHandler handler = createHandler(jobRepository);
JobExecution jobExecution = jobRepository.createJobExecution("job", new JobParameters());
JobExecutionRequest message = handler.handle(new JobExecutionRequest(jobExecution));
assertNotNull(message);
assertEquals(1, jobExecution.getStepExecutions().size());
StepExecution stepExecution = (StepExecution) jobExecution.getStepExecutions().iterator().next();
assertTrue(stepExecution.getExecutionContext().containsKey("foo"));
}
@Test
public void testHandleRestartAlreadyComplete() throws Exception {
JobRepositorySupport jobRepository = new JobRepositorySupport() {
@Override
public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) {
StepExecution stepExecution = new StepExecution(stepName, new JobExecution(jobInstance));
stepExecution.setStatus(BatchStatus.COMPLETED);
stepExecution.setExecutionContext(new ExecutionContext() {
{
put("foo", "bar");
}
});
return stepExecution;
}
};
StepExecutionMessageHandler handler = createHandler(jobRepository);
JobExecution jobExecution = jobRepository.createJobExecution("job", new JobParameters());
JobExecutionRequest message = handler.handle(new JobExecutionRequest(jobExecution));
assertNotNull(message);
assertEquals(1, jobExecution.getStepExecutions().size());
StepExecution stepExecution = (StepExecution) jobExecution.getStepExecutions().iterator().next();
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
// We expect to get the context from the previous execution, even if we
// do not execute
assertTrue(stepExecution.getExecutionContext().containsKey("foo"));
}
@Test
public void testHandleRestartStartLimitExceeded() throws Exception {
JobRepositorySupport jobRepository = new JobRepositorySupport() {
@Override
public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) {
return new StepExecution(stepName, new JobExecution(jobInstance));
}
@Override
public int getStepExecutionCount(JobInstance jobInstance, String stepName) {
// sufficiently high restart count
return 100;
}
};
StepExecutionMessageHandler handler = createHandler(jobRepository);
JobExecution jobExecution = jobRepository.createJobExecution("job", new JobParameters());
JobExecutionRequest message = handler.handle(new JobExecutionRequest(jobExecution));
assertNotNull(message);
assertEquals(1, jobExecution.getStepExecutions().size());
JobExecutionRequest payload = message;
assertEquals(BatchStatus.FAILED, payload.getStatus());
assertTrue(payload.hasErrors());
Throwable error = payload.getLastThrowable();
assertTrue(error instanceof StartLimitExceededException);
String text = error.getMessage();
assertTrue("Wrong exit description: " + text, text.toLowerCase().contains("start limit"));
}
/**
* @param jobRepository
* @return a handler for step executions
*
*/
public StepExecutionMessageHandler createHandler(JobRepositorySupport jobRepository) {
StepExecutionMessageHandler handler = new StepExecutionMessageHandler();
StepSupport step = new StepSupport("step");
step.setStartLimit(10);
handler.setStep(step);
handler.setJobRepository(jobRepository);
return handler;
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.job;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
/**
* @author Dave Syer
*
*/
public class TestTasklet implements Tasklet {
/*
* (non-Javadoc)
*
*/
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
return RepeatStatus.FINISHED;
}
}

View File

@@ -0,0 +1,85 @@
package org.springframework.batch.integration.launch;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.integration.JobSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.integration.channel.PollableChannel;
import org.springframework.integration.core.Message;
import org.springframework.integration.core.MessageChannel;
import org.springframework.integration.core.MessageHeaders;
import org.springframework.integration.core.MessagingException;
import org.springframework.integration.message.GenericMessage;
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 JobLaunchingMessageHandlerIntegrationTests {
@Autowired
@Qualifier("requests")
private MessageChannel requestChannel;
@Autowired
@Qualifier("response")
private PollableChannel responseChannel;
private JobSupport job = new JobSupport("testJob");
@Before
public void setUp() {
responseChannel.purge(null);
}
@Test
@DirtiesContext
@SuppressWarnings("unchecked")
public void testNoReply() {
GenericMessage<JobLaunchRequest> trigger = new GenericMessage<JobLaunchRequest>(new JobLaunchRequest(job,
new JobParameters()));
try {
requestChannel.send(trigger);
}
catch (MessagingException e) {
String message = e.getMessage();
assertTrue("Wrong message: " + message, message.contains("reply channel"));
}
Message<JobExecution> executionMessage = (Message<JobExecution>) responseChannel.receive(1000);
assertNull("JobExecution message received when no return address set", executionMessage);
}
@SuppressWarnings("unchecked")
@Test
@DirtiesContext
public void testReply() {
JobParametersBuilder builder = new JobParametersBuilder();
builder.addString("dontclash", "12");
Map<String, Object> map = new HashMap<String, Object>();
map.put(MessageHeaders.REPLY_CHANNEL, "response");
MessageHeaders headers = new MessageHeaders(map);
GenericMessage<JobLaunchRequest> trigger = new GenericMessage<JobLaunchRequest>(new JobLaunchRequest(job,
builder.toJobParameters()), headers);
requestChannel.send(trigger);
Message<JobExecution> executionMessage = (Message<JobExecution>) responseChannel.receive(1000);
assertNotNull("No response received", executionMessage);
JobExecution execution = executionMessage.getPayload();
assertNotNull("JobExecution not returned", execution);
}
}

View File

@@ -0,0 +1,58 @@
package org.springframework.batch.integration.launch;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.integration.JobSupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
@ContextConfiguration(locations = { "/job-execution-context.xml" })
public class JobLaunchingMessageHandlerTests extends AbstractJUnit4SpringContextTests {
JobLaunchingMessageHandler messageHandler;
StubJobLauncher jobLauncher;
@Before
public void setUp() {
jobLauncher = new StubJobLauncher();
messageHandler = new JobLaunchingMessageHandler(jobLauncher);
}
@Test
public void testSimpleDelivery() throws Exception{
messageHandler.launch(new JobLaunchRequest(new JobSupport("testjob"), null));
assertEquals("Wrong job count", 1, jobLauncher.jobs.size());
assertEquals("Wrong job name", jobLauncher.jobs.get(0).getName(), "testjob");
}
private static class StubJobLauncher implements JobLauncher {
List<Job> jobs = new ArrayList<Job>();
List<JobParameters> parameters = new ArrayList<JobParameters>();
AtomicLong jobId = new AtomicLong();
public JobExecution run(Job job, JobParameters jobParameters){
jobs.add(job);
parameters.add(jobParameters);
return new JobExecution(new JobInstance(jobId.getAndIncrement(), jobParameters, job.getName()));
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.launch;
import java.util.Properties;
import org.springframework.batch.core.converter.DefaultJobParametersConverter;
import org.springframework.batch.integration.JobSupport;
import org.springframework.integration.annotation.ServiceActivator;
/**
* @author Dave Syer
*
*/
public class JobRequestConverter {
@ServiceActivator
public JobLaunchRequest convert(String jobName) {
// TODO: get these from message header
Properties properties = new Properties();
return new JobLaunchRequest(new JobSupport(jobName), new DefaultJobParametersConverter().getJobParameters(properties));
}
}

View File

@@ -0,0 +1,59 @@
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() {
// TODO Auto-generated method stub
return 0;
}
public boolean isAllowStartIfComplete() {
// TODO Auto-generated method stub
return false;
}
}
}

View File

@@ -0,0 +1,57 @@
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;
/**
* {@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
*/
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);
}
}

View File

@@ -0,0 +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);
}
}

View File

@@ -0,0 +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);
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.partition;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.List;
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.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.explore.JobExplorer;
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;
/**
* @author Dave Syer
*
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class VanillaIntegrationTests {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job job;
@Autowired
private JobExplorer jobExplorer;
@Test
public void testSimpleProperties() throws Exception {
assertNotNull(jobLauncher);
}
@Test
public void testLaunchJob() throws Exception {
int before = jobExplorer.getJobInstances(job.getName(), 0, 100).size();
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(BatchStatus.COMPLETED, jobExecution.getStatus());
assertEquals(3, jobExecution.getStepExecutions().size());
}
}

View File

@@ -0,0 +1,114 @@
package org.springframework.batch.integration.retry;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.Lifecycle;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StringUtils;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@MessageEndpoint
public class RepeatTransactionalPollingIntegrationTests implements ApplicationContextAware {
private Log logger = LogFactory.getLog(getClass());
private static List<String> processed = new ArrayList<String>();
private static List<String> expected;
private static List<String> handled = new ArrayList<String>();
private static List<String> list = new ArrayList<String>();
private Lifecycle bus;
private volatile static int count = 0;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
bus = (Lifecycle) applicationContext;
}
public String process(String message) {
String result = message + ": " + count;
logger.debug("Handling: " + message);
if (count<expected.size()) {
processed.add(message);
count++;
}
if ("fail".equals(message)) {
throw new RuntimeException("Planned failure");
}
return result;
}
public String input() {
logger.debug("Polling: " + count);
if (list.isEmpty()) {
return null;
}
return list.remove(0);
}
public void output(String message) {
handled.add(message);
logger.debug("Handled: " + message);
}
@Before
public void clearLists() {
list.clear();
handled.clear();
processed.clear();
count = 0;
}
@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"));
waitForResults(bus, expected.size(), 60);
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"));
waitForResults(bus, expected.size(), 60);
assertEquals(expected,processed);
assertEquals(2, handled.size()); // a,b
}
private void waitForResults(Lifecycle lifecycle, int count, int maxTries) throws InterruptedException {
lifecycle.start();
int timeout = 0;
while (processed.size() < count && timeout++ < maxTries) {
Thread.sleep(5);
}
lifecycle.stop();
}
}

View File

@@ -0,0 +1,105 @@
package org.springframework.batch.integration.retry;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.Lifecycle;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StringUtils;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@MessageEndpoint
public class RetryRepeatTransactionalPollingIntegrationTests implements ApplicationContextAware {
private Log logger = LogFactory.getLog(getClass());
private volatile static List<String> list = new ArrayList<String>();
@Autowired
private SimpleRecoverer recoverer;
@Autowired
private SimpleService service;
private Lifecycle lifecycle;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
lifecycle = (Lifecycle) applicationContext;
}
private static volatile int count = 0;
@Before
public void clearLists() {
list.clear();
count = 0;
}
public String input() {
logger.debug("Polling: " + count);
if (list.isEmpty()) {
return null;
}
return list.remove(0);
}
public void output(String message) {
count++;
logger.debug("Handled: " + message);
}
@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<String> expected = TransactionAwareProxyFactory.createTransactionalList(Arrays.asList(StringUtils
.commaDelimitedListToStringArray("a,b,c,d")));
service.setExpected(expected);
waitForResults(lifecycle, expected.size(), 60);
assertEquals(4,service.getProcessed().size()); // a,b,c,d
assertEquals(expected, service.getProcessed());
}
@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<String> expected = TransactionAwareProxyFactory.createTransactionalList(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)
System.err.println(service.getProcessed());
assertEquals(7,service.getProcessed().size()); // a,b,fail,fail,d,e,f
assertEquals(1,recoverer.getRecovered().size()); // fail
assertEquals(expected, service.getProcessed());
}
private void waitForResults(Lifecycle lifecycle, int count, int maxTries) throws InterruptedException {
lifecycle.start();
int timeout = 0;
while (service.getProcessed().size() < count && timeout++ < maxTries) {
Thread.sleep(5);
}
lifecycle.stop();
}
}

View File

@@ -0,0 +1,106 @@
package org.springframework.batch.integration.retry;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.Lifecycle;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StringUtils;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@MessageEndpoint
public class RetryTransactionalPollingIntegrationTests implements ApplicationContextAware {
private Log logger = LogFactory.getLog(getClass());
private static List<String> list = new ArrayList<String>();
@Autowired
private SimpleRecoverer recoverer;
@Autowired
private SimpleService service;
private Lifecycle bus;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
bus = (Lifecycle) applicationContext;
}
private static volatile int count = 0;
@Before
public void clearLists() {
list.clear();
count = 0;
}
public String input() {
logger.debug("Polling: " + count);
if (list.isEmpty()) {
return null;
}
return list.remove(0);
}
public void output(String message) {
count++;
logger.debug("Handled: " + message);
}
@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<String> expected = TransactionAwareProxyFactory.createTransactionalList(Arrays.asList(StringUtils
.commaDelimitedListToStringArray("a,b,c,d")));
service.setExpected(expected);
waitForResults(bus, expected.size(), 60);
assertEquals(4,service.getProcessed().size()); // a,b,c,d
assertEquals(expected, service.getProcessed());
}
@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<String> expected = TransactionAwareProxyFactory.createTransactionalList(Arrays.asList(StringUtils
.commaDelimitedListToStringArray("a,b,fail,fail,d,e")));
service.setExpected(expected);
waitForResults(bus, expected.size(), 60);
waitForResults(bus, 6, 100); // a, b, (fail, fail, [fail]), d, e
assertEquals(6,service.getProcessed().size()); // a,b,fail,fail,d,e
assertEquals(1,recoverer.getRecovered().size()); // fail
assertEquals(expected, service.getProcessed());
}
private void waitForResults(Lifecycle lifecycle, int count, int maxTries) throws InterruptedException {
lifecycle.start();
int timeout = 0;
while (service.getProcessed().size() < count && timeout++ < maxTries) {
Thread.sleep(10);
}
lifecycle.stop();
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.integration.retry;
/**
* @author Dave Syer
*
*/
public interface Service {
String process(String message);
}

View File

@@ -0,0 +1,37 @@
package org.springframework.batch.integration.retry;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.retry.interceptor.MethodInvocationRecoverer;
/**
* @author Dave Syer
*
*/
public final class SimpleRecoverer implements MethodInvocationRecoverer<String> {
private Log logger = LogFactory.getLog(getClass());
private final List<String> recovered = new ArrayList<String>();
/**
* Public getter for the recovered.
* @return the recovered
*/
public List<String> getRecovered() {
return recovered;
}
public String recover(Object[] data, Throwable cause) {
if (data == null) {
return null;
}
String payload = (String) data[0];
logger.debug("Recovering: " + payload);
recovered.add(payload);
return null;
}
}

View File

@@ -0,0 +1,48 @@
package org.springframework.batch.integration.retry;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
@MessageEndpoint
public class SimpleService implements Service {
private Log logger = LogFactory.getLog(getClass());
private List<String> processed = new CopyOnWriteArrayList<String>();
private List<String> expected = new ArrayList<String>();
private int count = 0;
public void setExpected(List<String> expected) {
this.expected = expected;
}
/**
* Public getter for the processed.
* @return the processed
*/
public List<String> getProcessed() {
return processed;
}
@ServiceActivator(inputChannel = "requests", outputChannel = "replies")
public String process(String message) {
String result = message + ": " + (count++);
logger.debug("Handling: " + message);
if (count <= expected.size()) {
processed.add(message);
}
if ("fail".equals(message)) {
throw new RuntimeException("Planned failure");
}
return result;
}
}

View File

@@ -0,0 +1,116 @@
package org.springframework.batch.integration.retry;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.Lifecycle;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StringUtils;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@MessageEndpoint
public class TransactionalPollingIntegrationTests implements ApplicationContextAware {
private Log logger = LogFactory.getLog(getClass());
private static List<String> processed = new ArrayList<String>();
private static List<String> handled = new ArrayList<String>();
private static List<String> expected = new ArrayList<String>();
private static List<String> list = new ArrayList<String>();
private Lifecycle bus;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
bus = (Lifecycle) applicationContext;
}
private volatile static int count = 0;
@ServiceActivator(inputChannel = "requests", outputChannel = "replies")
public String process(String message) {
String result = message + ": " + count;
logger.debug("Handling: " + message);
if (count < expected.size()) {
processed.add(message);
count++;
}
if ("fail".equals(message)) {
throw new RuntimeException("Planned failure");
}
return result;
}
public String input() {
logger.debug("Polling: " + count + " of " + list.size());
if (list.isEmpty()) {
return null;
}
return list.remove(0);
}
public void output(String message) {
if (count < expected.size()) {
handled.add(message);
}
logger.debug("Handled: " + message);
}
@Before
public void clearLists() {
list.clear();
handled.clear();
processed.clear();
count = 0;
}
@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"));
waitForResults(bus, 4, 60);
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"));
waitForResults(bus, 4, 30);
assertEquals(expected, processed);
assertEquals(2, handled.size()); // a,b
}
private void waitForResults(Lifecycle lifecycle, int count, int maxTries) throws InterruptedException {
lifecycle.start();
int timeout = 0;
while (processed.size() < count && timeout++ < maxTries) {
Thread.sleep(10);
}
lifecycle.stop();
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<annotation-config/>
<channel id="jobs" />
</beans:beans>

View File

@@ -0,0 +1,9 @@
log4j.rootCategory=WARN, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %5p %t [%c] - <%m>%n
log4j.logger.org.springframework.batch.core=DEBUG
log4j.category.org.springframework.integration=DEBUG
log4j.category.org.springframework.transaction=DEBUG

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<annotation-config/>
<channel id="smokein"/>
<channel id="smokeout">
<queue/>
</channel>
</beans:beans>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<annotation-config />
<channel id="requests" />
<channel id="replies">
<queue />
</channel>
<gateway service-interface="org.springframework.batch.item.ItemProcessor"
id="processor" default-reply-timeout="1000" default-request-channel="requests"
default-reply-channel="replies" />
<service-activator input-channel="requests"
output-channel="replies" ref="doubler" />
<beans:bean id="doubler"
class="org.springframework.batch.integration.async.AsyncItemProcessorMessagingGatewayTests$Doubler" />
</beans:beans>

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<annotation-config/>
<channel id="requests" />
<channel id="replies">
<queue/>
</channel>
<beans:bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<tx:annotation-driven />
<beans:bean id="chunkHandler" class="org.springframework.batch.integration.chunk.ChunkProcessorChunkHandler">
<beans:property name="chunkProcessor">
<beans:bean class="org.springframework.batch.core.step.item.SimpleChunkProcessor">
<beans:property name="itemWriter">
<beans:bean class="org.springframework.batch.integration.chunk.TestItemWriter" />
</beans:property>
<beans:property name="itemProcessor">
<beans:bean class="org.springframework.batch.item.support.PassThroughItemProcessor" />
</beans:property>
</beans:bean>
</beans:property>
</beans:bean>
<service-activator input-channel="requests" output-channel="replies" ref="chunkHandler" />
</beans:beans>

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context" xmlns:batch="http://www.springframework.org/schema/batch"
xsi:schemaLocation="http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<job id="job" xmlns="http://www.springframework.org/schema/batch">
<step id="step">
<tasklet>
<chunk reader="reader" writer="writer" commit-interval="2"/>
</tasklet>
</step>
</job>
<bean id="reader" class="org.springframework.batch.integration.chunk.TestItemReader" scope="step">
<property name="items">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
<value>4</value>
<value>5</value>
<value>6</value>
<value>7</value>
<value>8</value>
<value>9</value>
</list>
</property>
</bean>
<bean id="writer" class="org.springframework.batch.integration.chunk.ChunkMessageChannelItemWriter">
<property name="messagingGateway" ref="messagingGateway"/>
</bean>
<bean id="messagingGateway" class="org.springframework.integration.gateway.SimpleMessagingGateway">
<property name="requestChannel" ref="requests"/>
<property name="replyChannel" ref="replies"/>
<property name="replyTimeout" value="1000"/>
</bean>
<integration:annotation-config/>
<integration:channel id="requests" />
<integration:channel id="replies">
<integration:queue/>
</integration:channel>
<integration:service-activator input-channel="requests" output-channel="replies" ref="chunkHandler" />
<bean id="chunkHandler" class="org.springframework.batch.integration.chunk.ChunkProcessorChunkHandler">
<property name="chunkProcessor">
<bean class="org.springframework.batch.core.step.item.SimpleChunkProcessor">
<property name="itemWriter">
<bean class="org.springframework.batch.integration.chunk.TestItemWriter" />
</property>
<property name="itemProcessor">
<bean class="org.springframework.batch.item.support.PassThroughItemProcessor" />
</property>
</bean>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
</bean>
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository"/>
</bean>
</beans>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<integration:annotation-config/>
<integration:channel id="resources" />
<integration:channel id="requests">
<integration:queue/>
</integration:channel>
</beans>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<integration:annotation-config />
<integration:channel id="requests" />
<integration:channel id="transforms" />
<integration:channel id="replies" />
<integration:gateway service-interface="org.springframework.batch.item.ItemProcessor" id="processor" default-reply-timeout="10"
default-request-timeout="100" default-request-channel="requests" default-reply-channel="replies" />
<integration:service-activator input-channel="requests" output-channel="transforms" ref="activator" />
<integration:service-activator input-channel="transforms" output-channel="replies" ref="activator" />
<bean id="activator" class="org.springframework.batch.integration.item.MessagingGatewayIntegrationTests$Activator" />
<integration:gateway service-interface="org.springframework.batch.item.ItemWriter" id="writer"
default-request-timeout="100" default-request-channel="inputs" />
<integration:channel id="inputs" />
<integration:channel id="outputs" />
<integration:splitter input-channel="inputs" output-channel="outputs" ref="splitter" />
<integration:service-activator input-channel="outputs" ref="service" />
<bean id="splitter" class="org.springframework.batch.integration.item.MessagingGatewayIntegrationTests$SplitService" />
<bean id="service" class="org.springframework.batch.integration.item.MessagingGatewayIntegrationTests$EndService" />
</beans>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<import resource="classpath:/simple-job-launcher-context.xml" />
<integration:annotation-config/>
<integration:channel id="requests" />
<integration:channel id="replies">
<integration:queue/>
</integration:channel>
<bean id="job" parent="simpleJob">
<property name="steps">
<bean id="stepController" class="org.springframework.batch.integration.job.MessageOrientedStep">
<property name="outputChannel" ref="requests" />
<property name="inputChannel" ref="replies" />
<property name="jobRepository" ref="jobRepository" />
</bean>
</property>
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean id="step" class="org.springframework.batch.integration.job.StepExecutionMessageHandler">
<property name="step">
<bean parent="taskletStep">
<property name="tasklet">
<bean class="org.springframework.batch.integration.job.TestTasklet" />
</property>
</bean>
</property>
<property name="jobRepository" ref="jobRepository" />
</bean>
<integration:service-activator ref="step" input-channel="requests" output-channel="replies" />
</beans>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<import resource="classpath:simple-job-launcher-context.xml" />
<integration:annotation-config/>
<integration:channel id="requests" />
<integration:channel id="response">
<integration:queue/>
</integration:channel>
<integration:service-activator input-channel="requests" ref="jobLaunchingHandler" method="launch" />
<bean id="jobLaunchingHandler" class="org.springframework.batch.integration.launch.JobLaunchingMessageHandler">
<constructor-arg ref="jobLauncher" />
</bean>
<bean id="testJob" parent="simpleJob">
<property name="steps" ref="step" />
</bean>
<bean id="step" parent="taskletStep">
<property name="tasklet">
<bean class="org.springframework.batch.integration.job.TestTasklet" />
</property>
</bean>
</beans>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p" xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<import resource="classpath:/simple-job-launcher-context.xml" />
<integration:channel id="requests">
<integration:queue />
</integration:channel>
<integration:channel id="staging">
<integration:queue />
</integration:channel>
<integration:channel id="replies">
<integration:queue />
</integration:channel>
<integration:service-activator ref="stepExecutionRequestHandler"
input-channel="requests" output-channel="staging">
<integration:poller>
<integration:interval-trigger interval="10" />
</integration:poller>
</integration:service-activator>
<integration:aggregator ref="partitionHandler"
timeout="10000" input-channel="staging" output-channel="replies">
<integration:poller>
<integration:interval-trigger interval="10" />
</integration:poller>
</integration:aggregator>
<!-- This is the "remote" worker (which in this case is local) -->
<bean id="stepExecutionRequestHandler"
class="org.springframework.batch.integration.partition.StepExecutionRequestHandler"
p:jobExplorer-ref="jobExplorer" p:stepLocator-ref="stepLocator" />
<bean id="stepLocator"
class="org.springframework.batch.integration.partition.BeanFactoryStepLocator" />
<bean id="jobExplorer"
class="org.springframework.batch.core.explore.support.MapJobExplorerFactoryBean">
<property name="repositoryFactory" ref="&amp;jobRepository"/>
</bean>
<bean id="partitionHandler"
class="org.springframework.batch.integration.partition.MessageChannelPartitionHandler">
<property name="messagingGateway">
<bean
class="org.springframework.integration.gateway.SimpleMessagingGateway">
<property name="requestChannel" ref="requests" />
<property name="replyChannel" ref="replies" />
<property name="replyTimeout" value="10000" />
</bean>
</property>
<property name="stepName" value="step1" />
<property name="gridSize" value="2" />
</bean>
<bean id="job1" parent="simpleJob">
<property name="steps">
<bean name="step1:master"
class="org.springframework.batch.core.partition.support.PartitionStep">
<property name="partitionHandler" ref="partitionHandler" />
<property name="stepExecutionSplitter">
<bean
class="org.springframework.batch.core.partition.support.SimpleStepExecutionSplitter">
<property name="jobRepository" ref="jobRepository" />
<property name="stepName" ref="step1" />
<property name="partitioner">
<bean class="org.springframework.batch.core.partition.support.SimplePartitioner"/>
</property>
</bean>
</property>
<property name="jobRepository" ref="jobRepository" />
</bean>
</property>
</bean>
<bean id="step1" parent="simpleStep">
<property name="itemReader">
<bean
class="org.springframework.batch.integration.partition.ExampleItemReader"
scope="step">
<!--
<property name="resource"
value="#{stepAttributes[jobParameters['resource']]}"/>
-->
</bean>
</property>
<property name="itemWriter">
<bean
class="org.springframework.batch.integration.partition.ExampleItemWriter" />
</property>
</bean>
<bean class="org.springframework.batch.core.scope.StepScope" />
</beans>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<integration:annotation-config />
<integration:channel id="requests" />
<integration:channel id="replies" />
<integration:service-activator ref="testCase" input-channel="requests" output-channel="replies" method="process"/>
<integration:inbound-channel-adapter
ref="testCase" method="input" channel="requests">
<integration:poller max-messages-per-poll="1">
<integration:interval-trigger interval="10" initial-delay="100"/>
<integration:advice-chain>
<ref bean="txAdvice"/>
<ref bean="repeatAdvice"/>
</integration:advice-chain>
</integration:poller>
</integration:inbound-channel-adapter>
<integration:outbound-channel-adapter
ref="testCase" method="output" channel="replies" />
<bean id="testCase"
class="org.springframework.batch.integration.retry.RepeatTransactionalPollingIntegrationTests" />
<bean id="transactionManager"
class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<tx:advice id="txAdvice">
<tx:attributes><tx:method name="*"/></tx:attributes>
</tx:advice>
<bean id="repeatAdvice" class="org.springframework.batch.repeat.interceptor.RepeatOperationsInterceptor">
<property name="repeatOperations">
<bean class="org.springframework.batch.repeat.support.RepeatTemplate">
<property name="completionPolicy">
<bean class="org.springframework.batch.repeat.policy.SimpleCompletionPolicy">
<property name="chunkSize" value="2" />
</bean>
</property>
</bean>
</property>
</bean>
</beans>

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<integration:annotation-config />
<integration:channel id="requests" />
<integration:channel id="replies" />
<integration:inbound-channel-adapter
ref="testCase" method="input" channel="requests">
<integration:poller max-messages-per-poll="1">
<!--
TODO: the initial delay is a hack - it usually prevents the poller
from picking up data in the list before it is cleared, but one day
100ms will not be enough... (see INT-536)
-->
<integration:interval-trigger interval="10"
initial-delay="100" />
<integration:advice-chain>
<ref bean="txAdvice" />
<ref bean="repeatAdvice" />
</integration:advice-chain>
</integration:poller>
</integration:inbound-channel-adapter>
<integration:outbound-channel-adapter
ref="testCase" method="output" channel="replies" />
<bean id="testCase"
class="org.springframework.batch.integration.retry.RetryRepeatTransactionalPollingIntegrationTests" />
<bean id="transactionManager"
class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<bean id="repeatAdvice"
class="org.springframework.batch.repeat.interceptor.RepeatOperationsInterceptor">
<property name="repeatOperations">
<bean class="org.springframework.batch.repeat.support.RepeatTemplate">
<property name="completionPolicy">
<bean
class="org.springframework.batch.repeat.policy.SimpleCompletionPolicy">
<property name="chunkSize" value="2" />
</bean>
</property>
</bean>
</property>
</bean>
<bean id="service"
class="org.springframework.batch.integration.retry.SimpleService" />
<bean id="recoverer"
class="org.springframework.batch.integration.retry.SimpleRecoverer" />
<bean id="retryAdvice"
class="org.springframework.batch.retry.interceptor.StatefulRetryOperationsInterceptor">
<property name="retryOperations">
<bean class="org.springframework.batch.retry.support.RetryTemplate">
<property name="retryPolicy">
<bean class="org.springframework.batch.retry.policy.SimpleRetryPolicy">
<property name="maxAttempts" value="2" />
</bean>
</property>
</bean>
</property>
<property name="recoverer" ref="recoverer" />
</bean>
<aop:config proxy-target-class="true">
<aop:advisor advice-ref="retryAdvice"
pointcut="execution(* org.springframework.batch.integration.retry.Service+.process(..))" />
</aop:config>
</beans>

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<integration:annotation-config />
<integration:channel id="requests" />
<integration:channel id="replies" />
<integration:inbound-channel-adapter
ref="testCase" method="input" channel="requests">
<integration:poller max-messages-per-poll="1">
<integration:interval-trigger interval="10" initial-delay="100" />
<integration:transactional />
</integration:poller>
</integration:inbound-channel-adapter>
<integration:outbound-channel-adapter
ref="testCase" method="output" channel="replies" />
<bean id="testCase"
class="org.springframework.batch.integration.retry.RetryTransactionalPollingIntegrationTests" />
<bean id="transactionManager"
class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<bean id="service" class="org.springframework.batch.integration.retry.SimpleService" />
<bean id="recoverer" class="org.springframework.batch.integration.retry.SimpleRecoverer" />
<bean id="retryAdvice" class="org.springframework.batch.retry.interceptor.StatefulRetryOperationsInterceptor">
<property name="retryOperations">
<bean class="org.springframework.batch.retry.support.RetryTemplate">
<property name="retryPolicy">
<bean class="org.springframework.batch.retry.policy.SimpleRetryPolicy">
<property name="maxAttempts" value="2" />
</bean>
</property>
</bean>
</property>
<property name="recoverer" ref="recoverer" />
</bean>
<aop:config proxy-target-class="true">
<aop:advisor advice-ref="retryAdvice" pointcut="execution(* org.springframework.batch.integration.retry.Service+.process(..))" />
</aop:config>
</beans>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:integration="http://www.springframework.org/schema/integration"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<integration:annotation-config />
<integration:channel id="requests" />
<integration:channel id="replies" />
<integration:inbound-channel-adapter
ref="testCase" method="input" channel="requests">
<integration:poller max-messages-per-poll="1">
<integration:interval-trigger interval="10" initial-delay="100"/>
<integration:transactional />
</integration:poller>
</integration:inbound-channel-adapter>
<integration:outbound-channel-adapter
ref="testCase" method="output" channel="replies" />
<bean id="testCase"
class="org.springframework.batch.integration.retry.TransactionalPollingIntegrationTests" />
<bean id="transactionManager"
class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
</beans>

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean id="transactionManager" class="org.springframework.batch.support.transaction.ResourcelessTransactionManager" />
<bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
<property name="transactionManager" ref="transactionManager"/>
</bean>
<bean id="simpleJob" class="org.springframework.batch.core.job.SimpleJob" abstract="true">
<property name="jobRepository" ref="jobRepository" />
<property name="restartable" value="true" />
</bean>
<bean id="taskletStep" class="org.springframework.batch.core.step.tasklet.TaskletStep" abstract="true">
<property name="transactionManager" ref="transactionManager" />
<property name="jobRepository" ref="jobRepository" />
<property name="allowStartIfComplete" value="true" />
</bean>
<bean id="simpleStep" class="org.springframework.batch.core.step.item.SimpleStepFactoryBean" abstract="true">
<property name="transactionManager" ref="transactionManager" />
<property name="jobRepository" ref="jobRepository" />
<property name="startLimit" value="100" />
<property name="commitInterval" value="1" />
</bean>
<bean id="skipLimitStep" class="org.springframework.batch.core.step.item.FaultTolerantStepFactoryBean" parent="simpleStep"
abstract="true">
<property name="skipLimit" value="0" />
</bean>
<bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="int[]">
<bean class="org.springframework.batch.support.IntArrayPropertyEditor" />
</entry>
<entry key="org.springframework.batch.item.file.transform.Range[]">
<bean class="org.springframework.batch.item.file.transform.RangeArrayPropertyEditor" />
</entry>
<entry key="java.util.Date">
<bean class="org.springframework.beans.propertyeditors.CustomDateEditor">
<constructor-arg>
<bean class="java.text.SimpleDateFormat">
<constructor-arg value="yyyyMMdd" />
</bean>
</constructor-arg>
<constructor-arg value="false" />
</bean>
</entry>
</map>
</property>
</bean>
</beans>