From a0bc87c3e426dc7e17a5bb80452e0ab8d0147e61 Mon Sep 17 00:00:00 2001 From: David Syer Date: Tue, 29 Dec 2009 09:18:06 +0000 Subject: [PATCH] BATCH-1466, BATCHADM-16: move integration project --- spring-batch-integration/.classpath | 10 + spring-batch-integration/.project | 32 ++ .../.settings/org.eclipse.jdt.core.prefs | 83 ++++ .../org.eclipse.wst.common.component | 9 + ....eclipse.wst.common.project.facet.core.xml | 5 + .../.settings/org.maven.ide.eclipse.prefs | 9 + ...ringframework.ide.eclipse.beans.core.prefs | 3 + ...org.springframework.ide.eclipse.core.prefs | 49 +++ spring-batch-integration/.springBeans | 27 ++ spring-batch-integration/pom.xml | 143 +++++++ .../src/main/java/META-INF/MANIFEST.MF | 3 + .../integration/async/AsyncItemProcessor.java | 77 ++++ .../integration/async/AsyncItemWriter.java | 34 ++ .../chunk/AsynchronousFailureException.java | 53 +++ .../batch/integration/chunk/ChunkHandler.java | 23 ++ .../chunk/ChunkMessageChannelItemWriter.java | 202 +++++++++ .../chunk/ChunkProcessorChunkHandler.java | 78 ++++ .../batch/integration/chunk/ChunkRequest.java | 59 +++ .../integration/chunk/ChunkResponse.java | 69 ++++ .../file/FileToMessagesJobFactoryBean.java | 389 ++++++++++++++++++ .../file/MessageToJobParametersStrategy.java | 32 ++ ...ResourcePayloadAsJobParameterStrategy.java | 55 +++ .../integration/job/JobExecutionReply.java | 24 ++ .../integration/job/JobExecutionRequest.java | 114 +++++ .../integration/job/MessageOrientedStep.java | 179 ++++++++ .../job/StepExecutionMessageHandler.java | 193 +++++++++ .../job/StepExecutionTimeoutException.java | 36 ++ .../integration/launch/JobLaunchRequest.java | 57 +++ .../launch/JobLaunchingMessageHandler.java | 60 +++ .../partition/BeanFactoryStepLocator.java | 47 +++ .../MessageChannelPartitionHandler.java | 138 +++++++ .../partition/StepExecutionRequest.java | 37 ++ .../StepExecutionRequestHandler.java | 81 ++++ .../src/site/apt/index.apt | 187 +++++++++ spring-batch-integration/src/site/site.xml | 17 + .../integration/JobRepositorySupport.java | 94 +++++ .../batch/integration/JobSupport.java | 30 ++ .../batch/integration/SmokeTests.java | 59 +++ .../batch/integration/StepSupport.java | 74 ++++ ...yncItemProcessorMessagingGatewayTests.java | 59 +++ .../async/AsyncItemProcessorTests.java | 49 +++ ...hunkMessageItemWriterIntegrationTests.java | 315 ++++++++++++++ .../ChunkProcessorChunkHandlerTests.java | 36 ++ .../integration/chunk/ChunkRequestTests.java | 65 +++ .../integration/chunk/ChunkResponseTests.java | 56 +++ .../chunk/ChunkStepIntegrationTests.java | 36 ++ .../integration/chunk/TestItemReader.java | 70 ++++ .../integration/chunk/TestItemWriter.java | 56 +++ .../FileToMessagesJobFactoryBeanTests.java | 194 +++++++++ ...rcePayloadAsJobParameterStrategyTests.java | 65 +++ .../ResourceSplitterIntegrationTests.java | 77 ++++ .../MessagingGatewayIntegrationTests.java | 142 +++++++ .../MessageOrientedStepIntegrationTests.java | 53 +++ .../job/MessageOrientedStepTests.java | 155 +++++++ .../job/StepExecutionMessageHandlerTests.java | 230 +++++++++++ .../batch/integration/job/TestTasklet.java | 37 ++ ...unchingMessageHandlerIntegrationTests.java | 85 ++++ .../JobLaunchingMessageHandlerTests.java | 58 +++ .../launch/JobRequestConverter.java | 37 ++ .../BeanFactoryStepLocatorTests.java | 59 +++ .../partition/ExampleItemReader.java | 57 +++ .../partition/ExampleItemReaderTests.java | 70 ++++ .../partition/ExampleItemWriter.java | 23 ++ .../partition/VanillaIntegrationTests.java | 70 ++++ ...tTransactionalPollingIntegrationTests.java | 114 +++++ ...tTransactionalPollingIntegrationTests.java | 105 +++++ ...yTransactionalPollingIntegrationTests.java | 106 +++++ .../batch/integration/retry/Service.java | 26 ++ .../integration/retry/SimpleRecoverer.java | 37 ++ .../integration/retry/SimpleService.java | 48 +++ .../TransactionalPollingIntegrationTests.java | 116 ++++++ .../test/resources/job-execution-context.xml | 14 + .../src/test/resources/log4j.properties | 9 + .../batch/integration/SmokeTests-context.xml | 20 + ...ProcessorMessagingGatewayTests-context.xml | 25 ++ ...sageItemWriterIntegrationTests-context.xml | 33 ++ .../ChunkStepIntegrationTests-context.xml | 75 ++++ ...sourceSplitterIntegrationTests-context.xml | 19 + ...ssagingGatewayIntegrationTests-context.xml | 38 ++ ...geOrientedStepIntegrationTests-context.xml | 39 ++ ...MessageHandlerIntegrationTests-context.xml | 32 ++ .../VanillaIntegrationTests-context.xml | 101 +++++ ...ctionalPollingIntegrationTests-context.xml | 53 +++ ...ctionalPollingIntegrationTests-context.xml | 90 ++++ ...ctionalPollingIntegrationTests-context.xml | 51 +++ ...ctionalPollingIntegrationTests-context.xml | 36 ++ .../resources/simple-job-launcher-context.xml | 61 +++ 87 files changed, 6273 insertions(+) create mode 100644 spring-batch-integration/.classpath create mode 100644 spring-batch-integration/.project create mode 100644 spring-batch-integration/.settings/org.eclipse.jdt.core.prefs create mode 100644 spring-batch-integration/.settings/org.eclipse.wst.common.component create mode 100644 spring-batch-integration/.settings/org.eclipse.wst.common.project.facet.core.xml create mode 100644 spring-batch-integration/.settings/org.maven.ide.eclipse.prefs create mode 100644 spring-batch-integration/.settings/org.springframework.ide.eclipse.beans.core.prefs create mode 100644 spring-batch-integration/.settings/org.springframework.ide.eclipse.core.prefs create mode 100644 spring-batch-integration/.springBeans create mode 100644 spring-batch-integration/pom.xml create mode 100644 spring-batch-integration/src/main/java/META-INF/MANIFEST.MF create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemProcessor.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemWriter.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/AsynchronousFailureException.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkHandler.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkMessageChannelItemWriter.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandler.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkRequest.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkResponse.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/file/FileToMessagesJobFactoryBean.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/file/MessageToJobParametersStrategy.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/file/ResourcePayloadAsJobParameterStrategy.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/job/JobExecutionReply.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/job/JobExecutionRequest.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/job/MessageOrientedStep.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/job/StepExecutionMessageHandler.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/job/StepExecutionTimeoutException.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchRequest.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandler.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/BeanFactoryStepLocator.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/MessageChannelPartitionHandler.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/StepExecutionRequest.java create mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/StepExecutionRequestHandler.java create mode 100644 spring-batch-integration/src/site/apt/index.apt create mode 100644 spring-batch-integration/src/site/site.xml create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/JobRepositorySupport.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/JobSupport.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/SmokeTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/StepSupport.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/async/AsyncItemProcessorTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandlerTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkRequestTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkResponseTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkStepIntegrationTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/TestItemReader.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/TestItemWriter.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/file/FileToMessagesJobFactoryBeanTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/file/ResourcePayloadAsJobParameterStrategyTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/file/ResourceSplitterIntegrationTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/job/MessageOrientedStepIntegrationTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/job/MessageOrientedStepTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/job/StepExecutionMessageHandlerTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/job/TestTasklet.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobRequestConverter.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/BeanFactoryStepLocatorTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/ExampleItemReader.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/ExampleItemReaderTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/ExampleItemWriter.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/VanillaIntegrationTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/RepeatTransactionalPollingIntegrationTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/RetryRepeatTransactionalPollingIntegrationTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/RetryTransactionalPollingIntegrationTests.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/Service.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/SimpleRecoverer.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/SimpleService.java create mode 100644 spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/TransactionalPollingIntegrationTests.java create mode 100644 spring-batch-integration/src/test/resources/job-execution-context.xml create mode 100644 spring-batch-integration/src/test/resources/log4j.properties create mode 100644 spring-batch-integration/src/test/resources/org/springframework/batch/integration/SmokeTests-context.xml create mode 100644 spring-batch-integration/src/test/resources/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests-context.xml create mode 100644 spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests-context.xml create mode 100644 spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/ChunkStepIntegrationTests-context.xml create mode 100644 spring-batch-integration/src/test/resources/org/springframework/batch/integration/file/ResourceSplitterIntegrationTests-context.xml create mode 100644 spring-batch-integration/src/test/resources/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests-context.xml create mode 100644 spring-batch-integration/src/test/resources/org/springframework/batch/integration/job/MessageOrientedStepIntegrationTests-context.xml create mode 100644 spring-batch-integration/src/test/resources/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests-context.xml create mode 100644 spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/VanillaIntegrationTests-context.xml create mode 100644 spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/RepeatTransactionalPollingIntegrationTests-context.xml create mode 100644 spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/RetryRepeatTransactionalPollingIntegrationTests-context.xml create mode 100644 spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/RetryTransactionalPollingIntegrationTests-context.xml create mode 100644 spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/TransactionalPollingIntegrationTests-context.xml create mode 100644 spring-batch-integration/src/test/resources/simple-job-launcher-context.xml diff --git a/spring-batch-integration/.classpath b/spring-batch-integration/.classpath new file mode 100644 index 000000000..2daddec39 --- /dev/null +++ b/spring-batch-integration/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/spring-batch-integration/.project b/spring-batch-integration/.project new file mode 100644 index 000000000..695b024dd --- /dev/null +++ b/spring-batch-integration/.project @@ -0,0 +1,32 @@ + + + spring-batch-integration + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + org.maven.ide.eclipse.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + + diff --git a/spring-batch-integration/.settings/org.eclipse.jdt.core.prefs b/spring-batch-integration/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..edb5db1ad --- /dev/null +++ b/spring-batch-integration/.settings/org.eclipse.jdt.core.prefs @@ -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 diff --git a/spring-batch-integration/.settings/org.eclipse.wst.common.component b/spring-batch-integration/.settings/org.eclipse.wst.common.component new file mode 100644 index 000000000..3d9da4b76 --- /dev/null +++ b/spring-batch-integration/.settings/org.eclipse.wst.common.component @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/spring-batch-integration/.settings/org.eclipse.wst.common.project.facet.core.xml b/spring-batch-integration/.settings/org.eclipse.wst.common.project.facet.core.xml new file mode 100644 index 000000000..16a4875d0 --- /dev/null +++ b/spring-batch-integration/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/spring-batch-integration/.settings/org.maven.ide.eclipse.prefs b/spring-batch-integration/.settings/org.maven.ide.eclipse.prefs new file mode 100644 index 000000000..a2b35c47b --- /dev/null +++ b/spring-batch-integration/.settings/org.maven.ide.eclipse.prefs @@ -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 diff --git a/spring-batch-integration/.settings/org.springframework.ide.eclipse.beans.core.prefs b/spring-batch-integration/.settings/org.springframework.ide.eclipse.beans.core.prefs new file mode 100644 index 000000000..a03e4a5ac --- /dev/null +++ b/spring-batch-integration/.settings/org.springframework.ide.eclipse.beans.core.prefs @@ -0,0 +1,3 @@ +#Wed May 06 08:20:22 BST 2009 +eclipse.preferences.version=1 +org.springframework.ide.eclipse.beans.core.ignoreMissingNamespaceHandler=false diff --git a/spring-batch-integration/.settings/org.springframework.ide.eclipse.core.prefs b/spring-batch-integration/.settings/org.springframework.ide.eclipse.core.prefs new file mode 100644 index 000000000..99192453d --- /dev/null +++ b/spring-batch-integration/.settings/org.springframework.ide.eclipse.core.prefs @@ -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 diff --git a/spring-batch-integration/.springBeans b/spring-batch-integration/.springBeans new file mode 100644 index 000000000..3fd911e10 --- /dev/null +++ b/spring-batch-integration/.springBeans @@ -0,0 +1,27 @@ + + + 1 + + + + + + + src/test/resources/job-execution-context.xml + src/test/resources/simple-job-launcher-context.xml + src/test/resources/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests-context.xml + src/test/resources/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests-context.xml + src/test/resources/org/springframework/batch/integration/job/MessageOrientedStepIntegrationTests-context.xml + src/test/resources/org/springframework/batch/integration/file/ResourceSplitterIntegrationTests-context.xml + src/test/resources/org/springframework/batch/integration/retry/TransactionalPollingIntegrationTests-context.xml + src/test/resources/org/springframework/batch/integration/retry/RepeatTransactionalPollingIntegrationTests-context.xml + src/test/resources/org/springframework/batch/integration/retry/RetryRepeatTransactionalPollingIntegrationTests-context.xml + src/test/resources/org/springframework/batch/integration/SmokeTests-context.xml + src/test/resources/org/springframework/batch/integration/retry/RetryTransactionalPollingIntegrationTests-context.xml + src/test/resources/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests-context.xml + src/test/resources/org/springframework/batch/integration/partition/VanillaIntegrationTests-context.xml + src/test/resources/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests-context.xml + + + + diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml new file mode 100644 index 000000000..ffc06cd6d --- /dev/null +++ b/spring-batch-integration/pom.xml @@ -0,0 +1,143 @@ + + + 4.0.0 + spring-batch-integration + Spring Batch Integration + http://static.springframework.org/spring-batch/${project.artifactId} + + org.springframework.batch + spring-batch-admin-parent + 1.0.0.CI-SNAPSHOT + ../spring-batch-admin-parent + + + + + true + + + + + maven-surefire-plugin + + true + + + + + + + test + + + + maven-surefire-plugin + + false + + + + + + + + + org.springframework.batch + spring-batch-core + ${spring.batch.version} + compile + + + org.springframework.batch + spring-batch-test + ${spring.batch.version} + test + + + org.apache.geronimo.specs + geronimo-jms_1.1_spec + true + + + junit + junit + + + log4j + log4j + true + + + org.aspectj + aspectjrt + test + + + org.aspectj + aspectjweaver + test + + + commons-lang + commons-lang + test + + + cglib + cglib-nodep + test + + + org.springframework.integration + spring-integration-core + ${spring.integration.version} + compile + + + org.springframework.integration + spring-integration-adapter + ${spring.integration.version} + compile + + + org.springframework.integration + spring-integration-jms + ${spring.integration.version} + compile + + + org.springframework + spring-context + + + org.springframework + spring-aop + + + org.springframework + spring-jms + true + + + org.springframework + spring-tx + + + org.springframework + spring-test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + junit:junit + + + + + + diff --git a/spring-batch-integration/src/main/java/META-INF/MANIFEST.MF b/spring-batch-integration/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..5e9495128 --- /dev/null +++ b/spring-batch-integration/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemProcessor.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemProcessor.java new file mode 100644 index 000000000..87a5ee7cf --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemProcessor.java @@ -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 the input object type + * @param the output object type (will be wrapped in a Future) + */ +public class AsyncItemProcessor implements ItemProcessor>, InitializingBean { + + private ItemProcessor 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 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 process(final I item) throws Exception { + FutureTask task = new FutureTask(new Callable() { + public O call() throws Exception { + return delegate.process(item); + } + }); + taskExecutor.execute(task); + return task; + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemWriter.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemWriter.java new file mode 100644 index 000000000..af5ea416b --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/async/AsyncItemWriter.java @@ -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 implements ItemWriter>, InitializingBean { + + private ItemWriter delegate; + + public void afterPropertiesSet() throws Exception { + Assert.notNull(delegate, "A delegate ItemWriter must be provided."); + } + + /** + * @param delegate + */ + public void setDelegate(ItemWriter delegate) { + this.delegate = delegate; + } + + public void write(List> items) throws Exception { + List list = new ArrayList(); + for (Future future : items) { + list.add(future.get()); + } + delegate.write(list); + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/AsynchronousFailureException.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/AsynchronousFailureException.java new file mode 100644 index 000000000..a3c49628f --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/AsynchronousFailureException.java @@ -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); + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkHandler.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkHandler.java new file mode 100644 index 000000000..9eddc4e39 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkHandler.java @@ -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 { + + ChunkResponse handleChunk(ChunkRequest chunk); + +} \ No newline at end of file diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkMessageChannelItemWriter.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkMessageChannelItemWriter.java new file mode 100644 index 000000000..25a77fedd --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkMessageChannelItemWriter.java @@ -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 extends StepExecutionListenerSupport implements ItemWriter, 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 items) throws Exception { + + // Block until expecting <= throttle limit + while (localState.getExpecting() > throttleLimit) { + getNextResult(); + } + + if (!items.isEmpty()) { + + logger.debug("Dispatching chunk: " + items); + ChunkRequest request = new ChunkRequest(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; + } + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandler.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandler.java new file mode 100644 index 000000000..6401088a5 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandler.java @@ -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 implements ChunkHandler, InitializingBean { + + private static final Log logger = LogFactory.getLog(ChunkProcessorChunkHandler.class); + + private ChunkProcessor 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 chunkProcessor) { + this.chunkProcessor = chunkProcessor; + } + + /** + * + * @see ChunkHandler#handleChunk(ChunkRequest) + */ + @ServiceActivator + public ChunkResponse handleChunk(ChunkRequest chunkRequest) { + + logger.debug("Handling chunk: " + chunkRequest); + + StepContribution stepContribution = chunkRequest.getStepContribution(); + try { + chunkProcessor.process(stepContribution, new Chunk(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); + + } +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkRequest.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkRequest.java new file mode 100644 index 000000000..ef14e0860 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkRequest.java @@ -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 implements Serializable { + + private final Long jobId; + private final Collection items; + private final StepContribution stepContribution; + + public ChunkRequest(Collection items, Long jobId, StepContribution stepContribution) { + this.items = items; + this.jobId = jobId; + this.stepContribution = stepContribution; + } + + public Long getJobId() { + return jobId; + } + + public Collection 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(); + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkResponse.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkResponse.java new file mode 100644 index 000000000..fec255ab3 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/chunk/ChunkResponse.java @@ -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; + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/file/FileToMessagesJobFactoryBean.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/file/FileToMessagesJobFactoryBean.java new file mode 100644 index 000000000..0b10d04ec --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/file/FileToMessagesJobFactoryBean.java @@ -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 implements FactoryBean, BeanNameAware { + + private String name = "fileToMessageJob"; + + private ItemReader 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 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 stepFactory = new SimpleStepFactoryBean(); + 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 itemWriter = new MessageChannelItemWriter(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 itemReader, Resource resource) { + if (itemReader instanceof FlatFileItemReader) { + ((FlatFileItemReader) itemReader).setResource(resource); + } + else { + ((StaxEventItemReader) itemReader).setResource(resource); + } + } + + /** + * Always returns {@link Job}. + * + * @see org.springframework.beans.factory.FactoryBean#getObjectType() + */ + public Class 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 implements ItemWriter { + + private MessageChannel channel; + + public MessageChannelItemWriter(MessageChannel channel) { + super(); + this.channel = channel; + } + + public void write(List items) throws Exception { + for (T item : items) { + channel.send(new GenericMessage(item)); + } + } + + } +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/file/MessageToJobParametersStrategy.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/file/MessageToJobParametersStrategy.java new file mode 100644 index 000000000..9fff389f0 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/file/MessageToJobParametersStrategy.java @@ -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); + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/file/ResourcePayloadAsJobParameterStrategy.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/file/ResourcePayloadAsJobParameterStrategy.java new file mode 100644 index 000000000..ca3e9083e --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/file/ResourcePayloadAsJobParameterStrategy.java @@ -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(); + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/JobExecutionReply.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/JobExecutionReply.java new file mode 100644 index 000000000..6990ae5c1 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/JobExecutionReply.java @@ -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 { + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/JobExecutionRequest.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/JobExecutionRequest.java new file mode 100644 index 000000000..b9ba90360 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/JobExecutionRequest.java @@ -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).
+ * + * 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; + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/MessageOrientedStep.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/MessageOrientedStep.java new file mode 100644 index 000000000..78253d7b0 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/MessageOrientedStep.java @@ -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(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 message = (Message) 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; + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/StepExecutionMessageHandler.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/StepExecutionMessageHandler.java new file mode 100644 index 000000000..cccdc5fa1 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/StepExecutionMessageHandler.java @@ -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()); + } + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/StepExecutionTimeoutException.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/StepExecutionTimeoutException.java new file mode 100644 index 000000000..424a63dd0 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/job/StepExecutionTimeoutException.java @@ -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); + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchRequest.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchRequest.java new file mode 100644 index 000000000..4154b70ec --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchRequest.java @@ -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; + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandler.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandler.java new file mode 100644 index 000000000..ca02f07c8 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandler.java @@ -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); + } + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/BeanFactoryStepLocator.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/BeanFactoryStepLocator.java new file mode 100644 index 000000000..262b450a7 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/BeanFactoryStepLocator.java @@ -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 getStepNames() { + Assert.state(beanFactory instanceof ListableBeanFactory, "BeanFactory is not listable."); + return Arrays.asList(((ListableBeanFactory) beanFactory).getBeanNamesForType(Step.class)); + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/MessageChannelPartitionHandler.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/MessageChannelPartitionHandler.java new file mode 100644 index 000000000..b5ce67a11 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/MessageChannelPartitionHandler.java @@ -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: + *
    + *
  • request channel capable of accepting {@link StepExecutionRequest} + * payloads
  • + *
  • reply channel that returns a list of {@link StepExecution} results
  • + *
+ * 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} and the aggregator, so that there is a + * good chance of all work being done. + * + * @see PartitionHandler#handle(StepExecutionSplitter, StepExecution) + */ + public Collection handle(StepExecutionSplitter stepExecutionSplitter, + StepExecution masterStepExecution) throws Exception { + + Set 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 result = (Collection) messagingGateway.receive(); + return result; + + } + + private Message createMessage(int sequenceNumber, int sequenceSize, + StepExecutionRequest stepExecutionRequest) { + return MessageBuilder.withPayload(stepExecutionRequest).setSequenceNumber(sequenceNumber).setSequenceSize( + sequenceSize).setCorrelationId( + stepExecutionRequest.getJobExecutionId() + ":" + stepExecutionRequest.getStepName()).build(); + } +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/StepExecutionRequest.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/StepExecutionRequest.java new file mode 100644 index 000000000..b412a6b73 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/StepExecutionRequest.java @@ -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); + } + +} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/StepExecutionRequestHandler.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/StepExecutionRequestHandler.java new file mode 100644 index 000000000..e50bcf3c5 --- /dev/null +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/partition/StepExecutionRequestHandler.java @@ -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; + + } + +} diff --git a/spring-batch-integration/src/site/apt/index.apt b/spring-batch-integration/src/site/apt/index.apt new file mode 100644 index 000000000..5b6a23462 --- /dev/null +++ b/spring-batch-integration/src/site/apt/index.apt @@ -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. + +*---+---+---+---+---+ +|<>|<>|<>|<>|<>| +*---- +|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 + diff --git a/spring-batch-integration/src/site/site.xml b/spring-batch-integration/src/site/site.xml new file mode 100644 index 000000000..4aac57281 --- /dev/null +++ b/spring-batch-integration/src/site/site.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobRepositorySupport.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobRepositorySupport.java new file mode 100644 index 000000000..8ab84f5be --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobRepositorySupport.java @@ -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; + } + + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobSupport.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobSupport.java new file mode 100644 index 000000000..84d9f2e83 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/JobSupport.java @@ -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; + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/SmokeTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/SmokeTests.java new file mode 100644 index 000000000..df2a38b4f --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/SmokeTests.java @@ -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("foo")); + @SuppressWarnings("unchecked") + Message message = (Message) smokeout.receive(100); + String result = (String) (message == null ? null : message.getPayload()); + assertEquals("foo: 1", result); + assertEquals(1, count); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/StepSupport.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/StepSupport.java new file mode 100644 index 000000000..7b02b7a13 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/StepSupport.java @@ -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; + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests.java new file mode 100644 index 000000000..207223fd0 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests.java @@ -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 processor = new AsyncItemProcessor(); + + @Autowired + private ItemProcessor delegate; + + @Test + public void testMultiExecution() throws Exception { + processor.setDelegate(delegate); + processor.setTaskExecutor(new SimpleAsyncTaskExecutor()); + List> list = new ArrayList>(); + for (int count = 0; count < 10; count++) { + list.add(processor.process("foo" + count)); + } + for (Future 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; + } + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/AsyncItemProcessorTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/AsyncItemProcessorTests.java new file mode 100644 index 000000000..c6093d310 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/async/AsyncItemProcessorTests.java @@ -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 processor = new AsyncItemProcessor(); + + private ItemProcessor delegate = new ItemProcessor() { + 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 result = processor.process("foo"); + assertEquals("foofoo", result.get()); + } + + @Test + public void testMultiExecution() throws Exception { + processor.setDelegate(delegate); + processor.setTaskExecutor(new SimpleAsyncTaskExecutor()); + List> list = new ArrayList>(); + for (int count = 0; count < 10; count++) { + list.add(processor.process("foo" + count)); + } + for (Future future : list) { + assertTrue(future.get().matches("foo.*foo.*")); + } + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests.java new file mode 100644 index 000000000..ed4080671 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests.java @@ -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 writer = new ChunkMessageChannelItemWriter(); + + @Autowired + @Qualifier("requests") + private MessageChannel requests; + + @Autowired + @Qualifier("replies") + private PollableChannel replies; + + private SimpleStepFactoryBean factory = new SimpleStepFactoryBean(); + + 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(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(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(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 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 message = new GenericMessage(chunk); + return message; + } + + @Test + public void testEarlyCompletionSignalledInHandler() throws Exception { + + factory.setItemReader(new ListItemReader(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(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(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; + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandlerTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandlerTests.java new file mode 100644 index 000000000..9c59f7834 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkProcessorChunkHandlerTests.java @@ -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 handler = new ChunkProcessorChunkHandler(); + + protected int count = 0; + + @Test + public void testVanillaHandleChunk() { + handler.setChunkProcessor(new ChunkProcessor() { + public void process(StepContribution contribution, Chunk chunk) throws Exception { + count += chunk.size(); + } + }); + StepContribution stepContribution = MetaDataInstanceFactory.createStepExecution().createStepContribution(); + @SuppressWarnings("unchecked") + ChunkResponse response = handler.handleChunk(new ChunkRequest(StringUtils + .commaDelimitedListToSet("foo,bar"), 12L, stepContribution)); + assertEquals(stepContribution, response.getStepContribution()); + assertEquals(12, response.getJobId().longValue()); + assertTrue(response.isSuccessful()); + assertEquals(2, count); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkRequestTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkRequestTests.java new file mode 100644 index 000000000..662d79e73 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkRequestTests.java @@ -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 request = new ChunkRequest(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 result = (ChunkRequest) SerializationUtils.deserialize(SerializationUtils + .serialize(request)); + assertNotNull(result.getStepContribution()); + assertEquals(new Long(111L), result.getJobId()); + assertEquals(2, result.getItems().size()); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkResponseTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkResponseTests.java new file mode 100644 index 000000000..ee2303a1b --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkResponseTests.java @@ -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()); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkStepIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkStepIntegrationTests.java new file mode 100644 index 000000000..b538e91d6 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/ChunkStepIntegrationTests.java @@ -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()); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/TestItemReader.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/TestItemReader.java new file mode 100644 index 000000000..50883237a --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/TestItemReader.java @@ -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 implements ItemReader { + + 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 items = new ArrayList(); + + /** + * @param items the items to set + */ + public void setItems(List 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; + + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/TestItemWriter.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/TestItemWriter.java new file mode 100644 index 000000000..c2e16a474 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/chunk/TestItemWriter.java @@ -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 implements ItemWriter { + + 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 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); + } + + } + + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/file/FileToMessagesJobFactoryBeanTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/file/FileToMessagesJobFactoryBeanTests.java new file mode 100644 index 000000000..6f3eba334 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/file/FileToMessagesJobFactoryBeanTests.java @@ -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 factory = new FileToMessagesJobFactoryBean(); + private DirectChannel channel = new DirectChannel(); + private List receiver = new ArrayList(); + private JobRepositorySupport jobRepository; + + @Before + public void setUp() { + jobRepository = new JobRepositorySupport(); + factory.setJobRepository(jobRepository); + factory.setTransactionManager(new ResourcelessTransactionManager()); + FlatFileItemReader itemReader = new FlatFileItemReader(); + 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(); + assertEquals(Job.class, factory.getObjectType()); + } + + /** + * Test method for + * {@link org.springframework.batch.integration.file.FileToMessagesJobFactoryBean#isSingleton()}. + */ + @Test + public void testIsSingleton() { + FileToMessagesJobFactoryBean factory = new FileToMessagesJobFactoryBean(); + 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); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/file/ResourcePayloadAsJobParameterStrategyTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/file/ResourcePayloadAsJobParameterStrategyTests.java new file mode 100644 index 000000000..9b352f0b3 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/file/ResourcePayloadAsJobParameterStrategyTests.java @@ -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(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("log4j.properties")); + fail("Expected ClassCastException"); + } catch (ClassCastException e) { + String message = e.getMessage(); + assertTrue("Wrong message: "+message, message.contains("String")); + } + + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/file/ResourceSplitterIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/file/ResourceSplitterIntegrationTests.java new file mode 100644 index 000000000..988417e55 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/file/ResourceSplitterIntegrationTests.java @@ -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)...
+ * + * 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 list = Arrays.asList(message); + System.err.println(list); + return message; + } + + @SuppressWarnings("unchecked") + @Test + public void testVanillaConversion() throws Exception { + resources.send(new GenericMessage("classpath:*-context.xml")); + Message message = (Message) requests.receive(200L); + assertNotNull(message); + message = (Message) requests.receive(100L); + assertNotNull(message); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests.java new file mode 100644 index 000000000..4e1a99097 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests.java @@ -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 processor; + + @Autowired + private ItemWriter 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 split(List 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; + } + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/job/MessageOrientedStepIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/job/MessageOrientedStepIntegrationTests.java new file mode 100644 index 000000000..40e2e245b --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/job/MessageOrientedStepIntegrationTests.java @@ -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()); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/job/MessageOrientedStepTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/job/MessageOrientedStepTests.java new file mode 100644 index 000000000..16940458b --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/job/MessageOrientedStepTests.java @@ -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)); + StepExecution stepExecution = jobExecution.createStepExecution(step.getName()); + stepExecution.getExecutionContext().putString(MessageOrientedStep.WAITING, "true"); + step.execute(stepExecution); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/job/StepExecutionMessageHandlerTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/job/StepExecutionMessageHandlerTests.java new file mode 100644 index 000000000..c097fd5b1 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/job/StepExecutionMessageHandlerTests.java @@ -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; + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/job/TestTasklet.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/job/TestTasklet.java new file mode 100644 index 000000000..6d99da878 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/job/TestTasklet.java @@ -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; + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests.java new file mode 100644 index 000000000..082427eb1 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests.java @@ -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 trigger = new GenericMessage(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 executionMessage = (Message) 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 map = new HashMap(); + map.put(MessageHeaders.REPLY_CHANNEL, "response"); + MessageHeaders headers = new MessageHeaders(map); + GenericMessage trigger = new GenericMessage(new JobLaunchRequest(job, + builder.toJobParameters()), headers); + requestChannel.send(trigger); + Message executionMessage = (Message) responseChannel.receive(1000); + + assertNotNull("No response received", executionMessage); + JobExecution execution = executionMessage.getPayload(); + assertNotNull("JobExecution not returned", execution); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerTests.java new file mode 100644 index 000000000..6674e2d34 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerTests.java @@ -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 jobs = new ArrayList(); + + List parameters = new ArrayList(); + + 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())); + } + + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobRequestConverter.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobRequestConverter.java new file mode 100644 index 000000000..01c33fde7 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/launch/JobRequestConverter.java @@ -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)); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/BeanFactoryStepLocatorTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/BeanFactoryStepLocatorTests.java new file mode 100644 index 000000000..c50aa27ba --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/BeanFactoryStepLocatorTests.java @@ -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; + } + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/ExampleItemReader.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/ExampleItemReader.java new file mode 100644 index 000000000..c9b1c299d --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/ExampleItemReader.java @@ -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, 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); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/ExampleItemReaderTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/ExampleItemReaderTests.java new file mode 100644 index 000000000..3f2a7f96e --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/ExampleItemReaderTests.java @@ -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); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/ExampleItemWriter.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/ExampleItemWriter.java new file mode 100644 index 000000000..fd3965190 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/ExampleItemWriter.java @@ -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 { + + private static final Log log = LogFactory.getLog(ExampleItemWriter.class); + + /** + * @see ItemWriter#write(List) + */ + public void write(List data) throws Exception { + log.info(data); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/VanillaIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/VanillaIntegrationTests.java new file mode 100644 index 000000000..e4484528b --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/partition/VanillaIntegrationTests.java @@ -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 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()); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/RepeatTransactionalPollingIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/RepeatTransactionalPollingIntegrationTests.java new file mode 100644 index 000000000..c8fdc2ef2 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/RepeatTransactionalPollingIntegrationTests.java @@ -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 processed = new ArrayList(); + + private static List expected; + + private static List handled = new ArrayList(); + + private static List list = new ArrayList(); + + 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 list = new ArrayList(); + + @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 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 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(); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/RetryTransactionalPollingIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/RetryTransactionalPollingIntegrationTests.java new file mode 100644 index 000000000..175670173 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/RetryTransactionalPollingIntegrationTests.java @@ -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 list = new ArrayList(); + + @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 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 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(); + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/Service.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/Service.java new file mode 100644 index 000000000..711f1cc50 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/Service.java @@ -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); + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/SimpleRecoverer.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/SimpleRecoverer.java new file mode 100644 index 000000000..f9be6fd76 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/SimpleRecoverer.java @@ -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 { + + private Log logger = LogFactory.getLog(getClass()); + + private final List recovered = new ArrayList(); + + /** + * Public getter for the recovered. + * @return the recovered + */ + public List 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; + } +} \ No newline at end of file diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/SimpleService.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/SimpleService.java new file mode 100644 index 000000000..22d75adf3 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/SimpleService.java @@ -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 processed = new CopyOnWriteArrayList(); + + private List expected = new ArrayList(); + + private int count = 0; + + public void setExpected(List expected) { + this.expected = expected; + } + + /** + * Public getter for the processed. + * @return the processed + */ + public List 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; + } + +} diff --git a/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/TransactionalPollingIntegrationTests.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/TransactionalPollingIntegrationTests.java new file mode 100644 index 000000000..ffc005039 --- /dev/null +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/retry/TransactionalPollingIntegrationTests.java @@ -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 processed = new ArrayList(); + + private static List handled = new ArrayList(); + + private static List expected = new ArrayList(); + + private static List list = new ArrayList(); + + 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(); + } + +} diff --git a/spring-batch-integration/src/test/resources/job-execution-context.xml b/spring-batch-integration/src/test/resources/job-execution-context.xml new file mode 100644 index 000000000..e29ed821a --- /dev/null +++ b/spring-batch-integration/src/test/resources/job-execution-context.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/log4j.properties b/spring-batch-integration/src/test/resources/log4j.properties new file mode 100644 index 000000000..8c5be0336 --- /dev/null +++ b/spring-batch-integration/src/test/resources/log4j.properties @@ -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 \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/SmokeTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/SmokeTests-context.xml new file mode 100644 index 000000000..09184a31f --- /dev/null +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/SmokeTests-context.xml @@ -0,0 +1,20 @@ + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests-context.xml new file mode 100644 index 000000000..9139dcf0e --- /dev/null +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/async/AsyncItemProcessorMessagingGatewayTests-context.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests-context.xml new file mode 100644 index 000000000..b8fb0a312 --- /dev/null +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/ChunkMessageItemWriterIntegrationTests-context.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/ChunkStepIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/ChunkStepIntegrationTests-context.xml new file mode 100644 index 000000000..781477457 --- /dev/null +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/chunk/ChunkStepIntegrationTests-context.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/file/ResourceSplitterIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/file/ResourceSplitterIntegrationTests-context.xml new file mode 100644 index 000000000..60535b6aa --- /dev/null +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/file/ResourceSplitterIntegrationTests-context.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests-context.xml new file mode 100644 index 000000000..403af7fb5 --- /dev/null +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/item/MessagingGatewayIntegrationTests-context.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/job/MessageOrientedStepIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/job/MessageOrientedStepIntegrationTests-context.xml new file mode 100644 index 000000000..f1c86262e --- /dev/null +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/job/MessageOrientedStepIntegrationTests-context.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests-context.xml new file mode 100644 index 000000000..d10465146 --- /dev/null +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/launch/JobLaunchingMessageHandlerIntegrationTests-context.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/VanillaIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/VanillaIntegrationTests-context.xml new file mode 100644 index 000000000..fd5e1498b --- /dev/null +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/partition/VanillaIntegrationTests-context.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/RepeatTransactionalPollingIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/RepeatTransactionalPollingIntegrationTests-context.xml new file mode 100644 index 000000000..fe5623d59 --- /dev/null +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/RepeatTransactionalPollingIntegrationTests-context.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/RetryRepeatTransactionalPollingIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/RetryRepeatTransactionalPollingIntegrationTests-context.xml new file mode 100644 index 000000000..c7a28dad1 --- /dev/null +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/RetryRepeatTransactionalPollingIntegrationTests-context.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/RetryTransactionalPollingIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/RetryTransactionalPollingIntegrationTests-context.xml new file mode 100644 index 000000000..5112176f5 --- /dev/null +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/RetryTransactionalPollingIntegrationTests-context.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/TransactionalPollingIntegrationTests-context.xml b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/TransactionalPollingIntegrationTests-context.xml new file mode 100644 index 000000000..9dfdc6f21 --- /dev/null +++ b/spring-batch-integration/src/test/resources/org/springframework/batch/integration/retry/TransactionalPollingIntegrationTests-context.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-integration/src/test/resources/simple-job-launcher-context.xml b/spring-batch-integration/src/test/resources/simple-job-launcher-context.xml new file mode 100644 index 000000000..1f75530a3 --- /dev/null +++ b/spring-batch-integration/src/test/resources/simple-job-launcher-context.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file