From 9bdfcf58ce141557f1803a9c8f05acd28fb1ad4e Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Wed, 24 Jan 2024 23:46:58 +0100 Subject: [PATCH 001/152] test(check unicity of job name in a flow) failing test Signed-off-by: Fabrice Bibonne --- .../builder/AlreadyUsedStepNameException.java | 33 ++++++++ .../core/job/builder/FlowJobBuilderTests.java | 80 ++++++++++--------- 2 files changed, 76 insertions(+), 37 deletions(-) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/AlreadyUsedStepNameException.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/AlreadyUsedStepNameException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/AlreadyUsedStepNameException.java new file mode 100644 index 0000000000..6866f17314 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/AlreadyUsedStepNameException.java @@ -0,0 +1,33 @@ +/* + * Copyright 2006-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.batch.core.job.builder; + +/** + * Exception to indicate the name of a step is already used by a different step in the same flow. + * Step names must be unique within a flow definition because the search of the next step to find + * relies on the step name + * + * @author Fabrice Bibonne + * + */ +public class AlreadyUsedStepNameException extends RuntimeException{ + + public AlreadyUsedStepNameException(String name){ + super("the name "+name+" is already used"); + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java index dcff2e0eb3..9e7620887d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java @@ -15,24 +15,9 @@ */ package org.springframework.batch.core.job.builder; -import java.util.Arrays; - -import javax.sql.DataSource; - -import static org.junit.jupiter.api.Assertions.assertEquals; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; +import org.springframework.batch.core.*; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.configuration.annotation.JobScope; import org.springframework.batch.core.job.flow.Flow; @@ -51,12 +36,18 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.SimpleAsyncTaskExecutor; -import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.lang.Nullable; import org.springframework.transaction.PlatformTransactionManager; +import javax.sql.DataSource; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + /** * @author Dave Syer * @author Mahmoud Ben Hassine @@ -262,26 +253,6 @@ public FlowExecutionStatus decide(JobExecution jobExecution, @Nullable StepExecu assertEquals(1, execution.getStepExecutions().size()); } - @Test - void testBuildWithDeciderPriorityOnWildcardCount() { - JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("COMPLETED_PARTIALLY"); - JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); - builder.on("**").end(); - builder.on("*").fail(); - builder.build().preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - } - - @Test - void testBuildWithDeciderPriorityWithEqualWildcard() { - JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("COMPLETED_PARTIALLY"); - JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); - builder.on("COMPLETED*").end(); - builder.on("*").fail(); - builder.build().preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - } - @Test void testBuildWithDeciderPriority() { JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("COMPLETED_PARTIALLY"); @@ -383,6 +354,41 @@ void testBuildWithJobScopedStep() throws Exception { assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); } + //https://github.com/spring-projects/spring-batch/issues/3757#issuecomment-1821593539 + @Test + void testStepNamesMustBeUniqueWithinFlowDefinition() { + Step conditionalStep = new StepSupport("conditionalStep") { + @Override + public void execute(StepExecution stepExecution) { + stepExecution.upgradeStatus(BatchStatus.COMPLETED); + stepExecution.setExitStatus(ExitStatus.COMPLETED); + String exitStatus = (System.currentTimeMillis() % 2 == 0) ? "EVEN" : "ODD"; + stepExecution.setExitStatus(new ExitStatus(exitStatus)); + jobRepository.update(stepExecution); + } + }; + + StepSupport misnamedStep = new StepSupport(step3Name) { + @Override + public void execute(StepExecution stepExecution) + throws UnexpectedJobExecutionException { + + stepExecution.upgradeStatus(BatchStatus.COMPLETED); + stepExecution.setExitStatus(ExitStatus.COMPLETED); + jobRepository.update(stepExecution); + } + }; + + JobBuilder jobBuilder = new JobBuilder("flow", jobRepository); + FlowBuilder flowBuilder = jobBuilder.start(conditionalStep) + .on("ODD").to(step2) + .from(conditionalStep).on("EVEN").to(step3) + .from(step3); + assertThrows(AlreadyUsedStepNameException.class, () -> flowBuilder.next(misnamedStep)); + flowBuilder.end().build(); + } + + @EnableBatchProcessing @Configuration static class JobConfiguration { From 094814d587737fa8fd042947683e37f4b62aa95f Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Thu, 1 Feb 2024 18:19:36 +0100 Subject: [PATCH 002/152] test(check unicity of job name in a flow) step name as a job parameter Signed-off-by: Fabrice Bibonne --- .../builder/AlreadyUsedStepNameException.java | 14 +- .../core/job/builder/FlowJobBuilderTests.java | 712 ++++++++++-------- 2 files changed, 395 insertions(+), 331 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/AlreadyUsedStepNameException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/AlreadyUsedStepNameException.java index 6866f17314..fdedfabf6a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/AlreadyUsedStepNameException.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/AlreadyUsedStepNameException.java @@ -17,17 +17,17 @@ package org.springframework.batch.core.job.builder; /** - * Exception to indicate the name of a step is already used by a different step in the same flow. - * Step names must be unique within a flow definition because the search of the next step to find - * relies on the step name + * Exception to indicate the name of a step is already used by a different step in the + * same flow. Step names must be unique within a flow definition because the search of the + * next step to find relies on the step name * * @author Fabrice Bibonne * */ -public class AlreadyUsedStepNameException extends RuntimeException{ +public class AlreadyUsedStepNameException extends RuntimeException { - public AlreadyUsedStepNameException(String name){ - super("the name "+name+" is already used"); - } + public AlreadyUsedStepNameException(String name) { + super("the name " + name + " is already used"); + } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java index 9e7620887d..9dfbd7cb30 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java @@ -15,6 +15,8 @@ */ package org.springframework.batch.core.job.builder; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.batch.core.*; @@ -27,9 +29,12 @@ import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; +import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.StepSupport; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.item.support.ListItemReader; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -51,378 +56,437 @@ /** * @author Dave Syer * @author Mahmoud Ben Hassine - * */ class FlowJobBuilderTests { - private JobRepository jobRepository; + private JobRepository jobRepository; + + private JobExecution execution; + + private final StepSupport step1 = new StepSupport("step1") { + @Override + public void execute(StepExecution stepExecution) + throws JobInterruptedException, UnexpectedJobExecutionException { + stepExecution.upgradeStatus(BatchStatus.COMPLETED); + stepExecution.setExitStatus(ExitStatus.COMPLETED); + jobRepository.update(stepExecution); + } + }; + + private final StepSupport fails = new StepSupport("fails") { + @Override + public void execute(StepExecution stepExecution) + throws JobInterruptedException, UnexpectedJobExecutionException { + stepExecution.upgradeStatus(BatchStatus.FAILED); + stepExecution.setExitStatus(ExitStatus.FAILED); + jobRepository.update(stepExecution); + } + }; + + private final StepSupport step2 = new StepSupport("step2") { + @Override + public void execute(StepExecution stepExecution) + throws JobInterruptedException, UnexpectedJobExecutionException { + stepExecution.upgradeStatus(BatchStatus.COMPLETED); + stepExecution.setExitStatus(ExitStatus.COMPLETED); + jobRepository.update(stepExecution); + } + }; + + private final StepSupport step3 = new StepSupport("step3") { + @Override + public void execute(StepExecution stepExecution) + throws JobInterruptedException, UnexpectedJobExecutionException { + stepExecution.upgradeStatus(BatchStatus.COMPLETED); + stepExecution.setExitStatus(ExitStatus.COMPLETED); + jobRepository.update(stepExecution); + } + }; + + @BeforeEach + void init() throws Exception { + EmbeddedDatabase embeddedDatabase = new EmbeddedDatabaseBuilder() + .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") + .addScript("/org/springframework/batch/core/schema-hsqldb.sql") + .build(); + JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + factory.setDataSource(embeddedDatabase); + factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); + factory.afterPropertiesSet(); + jobRepository = factory.getObject(); + execution = jobRepository.createJobExecution("flow", new JobParameters()); + } - private JobExecution execution; + @Test + void testBuildOnOneLine() { + FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1) + .on("COMPLETED") + .to(step2) + .end() + .preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } - private final StepSupport step1 = new StepSupport("step1") { - @Override - public void execute(StepExecution stepExecution) - throws JobInterruptedException, UnexpectedJobExecutionException { - stepExecution.upgradeStatus(BatchStatus.COMPLETED); - stepExecution.setExitStatus(ExitStatus.COMPLETED); - jobRepository.update(stepExecution); - } - }; - - private final StepSupport fails = new StepSupport("fails") { - @Override - public void execute(StepExecution stepExecution) - throws JobInterruptedException, UnexpectedJobExecutionException { - stepExecution.upgradeStatus(BatchStatus.FAILED); - stepExecution.setExitStatus(ExitStatus.FAILED); - jobRepository.update(stepExecution); - } - }; - - private final StepSupport step2 = new StepSupport("step2") { - @Override - public void execute(StepExecution stepExecution) - throws JobInterruptedException, UnexpectedJobExecutionException { - stepExecution.upgradeStatus(BatchStatus.COMPLETED); - stepExecution.setExitStatus(ExitStatus.COMPLETED); - jobRepository.update(stepExecution); - } - }; - - private final StepSupport step3 = new StepSupport("step3") { - @Override - public void execute(StepExecution stepExecution) - throws JobInterruptedException, UnexpectedJobExecutionException { - stepExecution.upgradeStatus(BatchStatus.COMPLETED); - stepExecution.setExitStatus(ExitStatus.COMPLETED); - jobRepository.update(stepExecution); - } - }; - - @BeforeEach - void init() throws Exception { - EmbeddedDatabase embeddedDatabase = new EmbeddedDatabaseBuilder() - .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") - .addScript("/org/springframework/batch/core/schema-hsqldb.sql") - .build(); - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); - factory.setDataSource(embeddedDatabase); - factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); - factory.afterPropertiesSet(); - jobRepository = factory.getObject(); - execution = jobRepository.createJobExecution("flow", new JobParameters()); - } + @Test + void testBuildSingleFlow() { + Flow flow = new FlowBuilder("subflow").from(step1).next(step2).build(); + FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(flow).end().preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } - @Test - void testBuildOnOneLine() { - FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1) - .on("COMPLETED") - .to(step2) - .end() - .preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } + @Test + void testBuildSingleFlowAddingStepsViaNext() { + Flow flow = new FlowBuilder("subflow").next(step1).next(step2).build(); + FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(flow).end().preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } - @Test - void testBuildSingleFlow() { - Flow flow = new FlowBuilder("subflow").from(step1).next(step2).build(); - FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(flow).end().preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } + @Test + void testBuildOverTwoLines() { + FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1).on("COMPLETED").to(step2).end(); + builder.preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } - @Test - void testBuildSingleFlowAddingStepsViaNext() { - Flow flow = new FlowBuilder("subflow").next(step1).next(step2).build(); - FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(flow).end().preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } + @Test + void testBuildSubflow() { + Flow flow = new FlowBuilder("subflow").from(step1).end(); + JobFlowBuilder builder = new JobBuilder("flow", jobRepository).start(flow); + builder.on("COMPLETED").to(step2); + builder.end().preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } - @Test - void testBuildOverTwoLines() { - FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1).on("COMPLETED").to(step2).end(); - builder.preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } + @Test + void testBuildSplit() { + Flow flow = new FlowBuilder("subflow").from(step1).end(); + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step2); + builder.split(new SimpleAsyncTaskExecutor()).add(flow).end(); + builder.preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } - @Test - void testBuildSubflow() { - Flow flow = new FlowBuilder("subflow").from(step1).end(); - JobFlowBuilder builder = new JobBuilder("flow", jobRepository).start(flow); - builder.on("COMPLETED").to(step2); - builder.end().preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } + @Test + void testNestedSplitsWithSingleThread() { + SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); + taskExecutor.setConcurrencyLimit(1); + + FlowBuilder flowBuilder = new FlowBuilder<>("flow"); + FlowBuilder.SplitBuilder splitBuilder = flowBuilder.split(taskExecutor); + splitBuilder.add(new FlowBuilder("subflow1").from(step1).end()); + splitBuilder.add(new FlowBuilder("subflow2").from(step2).end()); + Job job = new JobBuilder("job", jobRepository).start(flowBuilder.build()).end().build(); + job.execute(execution); + + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } - @Test - void testBuildSplit() { - Flow flow = new FlowBuilder("subflow").from(step1).end(); - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step2); - builder.split(new SimpleAsyncTaskExecutor()).add(flow).end(); - builder.preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } + @Test + void testBuildSplitUsingStartAndAdd_BATCH_2346() { + Flow subflow1 = new FlowBuilder("subflow1").from(step2).end(); + Flow subflow2 = new FlowBuilder("subflow2").from(step3).end(); + Flow splitflow = new FlowBuilder("splitflow").start(subflow1) + .split(new SimpleAsyncTaskExecutor()) + .add(subflow2) + .build(); + + FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(splitflow).end(); + builder.preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } - @Test - void testNestedSplitsWithSingleThread() { - SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); - taskExecutor.setConcurrencyLimit(1); - - FlowBuilder flowBuilder = new FlowBuilder<>("flow"); - FlowBuilder.SplitBuilder splitBuilder = flowBuilder.split(taskExecutor); - splitBuilder.add(new FlowBuilder("subflow1").from(step1).end()); - splitBuilder.add(new FlowBuilder("subflow2").from(step2).end()); - Job job = new JobBuilder("job", jobRepository).start(flowBuilder.build()).end().build(); - job.execute(execution); - - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } + @Test + void testBuildSplit_BATCH_2282() { + Flow flow1 = new FlowBuilder("subflow1").from(step1).end(); + Flow flow2 = new FlowBuilder("subflow2").from(step2).end(); + Flow splitFlow = new FlowBuilder("splitflow").split(new SimpleAsyncTaskExecutor()) + .add(flow1, flow2) + .build(); + FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(splitFlow).end(); + builder.preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } - @Test - void testBuildSplitUsingStartAndAdd_BATCH_2346() { - Flow subflow1 = new FlowBuilder("subflow1").from(step2).end(); - Flow subflow2 = new FlowBuilder("subflow2").from(step3).end(); - Flow splitflow = new FlowBuilder("splitflow").start(subflow1) - .split(new SimpleAsyncTaskExecutor()) - .add(subflow2) - .build(); - - FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(splitflow).end(); - builder.preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } + @Test + void testBuildDecision() { + JobExecutionDecider decider = new JobExecutionDecider() { + private int count = 0; - @Test - void testBuildSplit_BATCH_2282() { - Flow flow1 = new FlowBuilder("subflow1").from(step1).end(); - Flow flow2 = new FlowBuilder("subflow2").from(step2).end(); - Flow splitFlow = new FlowBuilder("splitflow").split(new SimpleAsyncTaskExecutor()) - .add(flow1, flow2) - .build(); - FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(splitFlow).end(); - builder.preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } + @Override + public FlowExecutionStatus decide(JobExecution jobExecution, @Nullable StepExecution stepExecution) { + count++; + return count < 2 ? new FlowExecutionStatus("ONGOING") : FlowExecutionStatus.COMPLETED; + } + }; + step1.setAllowStartIfComplete(true); + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); + builder.next(decider).on("COMPLETED").end().from(decider).on("*").to(step1).end(); + builder.preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } - @Test - void testBuildDecision() { - JobExecutionDecider decider = new JobExecutionDecider() { - private int count = 0; - - @Override - public FlowExecutionStatus decide(JobExecution jobExecution, @Nullable StepExecution stepExecution) { - count++; - return count < 2 ? new FlowExecutionStatus("ONGOING") : FlowExecutionStatus.COMPLETED; - } - }; - step1.setAllowStartIfComplete(true); - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); - builder.next(decider).on("COMPLETED").end().from(decider).on("*").to(step1).end(); - builder.preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } + @Test + void testBuildWithDeciderAtStart() { + JobExecutionDecider decider = new JobExecutionDecider() { + private int count = 0; - @Test - void testBuildWithDeciderAtStart() { - JobExecutionDecider decider = new JobExecutionDecider() { - private int count = 0; - - @Override - public FlowExecutionStatus decide(JobExecution jobExecution, @Nullable StepExecution stepExecution) { - count++; - return count < 2 ? new FlowExecutionStatus("ONGOING") : FlowExecutionStatus.COMPLETED; - } - }; - JobFlowBuilder builder = new JobBuilder("flow", jobRepository).start(decider); - builder.on("COMPLETED").end().from(decider).on("*").to(step1).end(); - builder.build().preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(1, execution.getStepExecutions().size()); - } + @Override + public FlowExecutionStatus decide(JobExecution jobExecution, @Nullable StepExecution stepExecution) { + count++; + return count < 2 ? new FlowExecutionStatus("ONGOING") : FlowExecutionStatus.COMPLETED; + } + }; + JobFlowBuilder builder = new JobBuilder("flow", jobRepository).start(decider); + builder.on("COMPLETED").end().from(decider).on("*").to(step1).end(); + builder.build().preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(1, execution.getStepExecutions().size()); + } - @Test - void testBuildWithDeciderPriority() { - JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("COMPLETED_PARTIALLY"); - JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); - builder.on("COMPLETED_PARTIALLY").end(); - builder.on("COMPLETED*").fail(); - builder.build().preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - } + @Test + void testBuildWithDeciderPriority() { + JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("COMPLETED_PARTIALLY"); + JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); + builder.on("COMPLETED_PARTIALLY").end(); + builder.on("COMPLETED*").fail(); + builder.build().preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + } - @Test - void testBuildWithWildcardDeciderPriority() { - JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("COMPLETED_PARTIALLY"); - JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); - builder.on("COMPLETED_?ARTIALLY").end(); - builder.on("COMPLETED_*ARTIALLY").fail(); - builder.build().preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - } + @Test + void testBuildWithWildcardDeciderPriority() { + JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("COMPLETED_PARTIALLY"); + JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); + builder.on("COMPLETED_?ARTIALLY").end(); + builder.on("COMPLETED_*ARTIALLY").fail(); + builder.build().preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + } - @Test - void testBuildWithDeciderPrioritySubstringAndWildcard() { - JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("CONTINUABLE"); - JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); - builder.on("CONTINUABLE").end(); - builder.on("CONTIN*").fail(); - builder.build().preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - } + @Test + void testBuildWithDeciderPrioritySubstringAndWildcard() { + JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("CONTINUABLE"); + JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); + builder.on("CONTINUABLE").end(); + builder.on("CONTIN*").fail(); + builder.build().preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + } - @Test - void testBuildWithIntermediateSimpleJob() { - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); - builder.on("COMPLETED").to(step2).end(); - builder.preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } + @Test + void testBuildWithIntermediateSimpleJob() { + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); + builder.on("COMPLETED").to(step2).end(); + builder.preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } - @Test - void testBuildWithIntermediateSimpleJobTwoSteps() { - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1).next(step2); - builder.on("FAILED").to(step3).end(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } + @Test + void testBuildWithIntermediateSimpleJobTwoSteps() { + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1).next(step2); + builder.on("FAILED").to(step3).end(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } - @Test - void testBuildWithCustomEndState() { - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); - builder.on("COMPLETED").end("FOO"); - builder.preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals("FOO", execution.getExitStatus().getExitCode()); - assertEquals(1, execution.getStepExecutions().size()); - } + @Test + void testBuildWithCustomEndState() { + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); + builder.on("COMPLETED").end("FOO"); + builder.preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals("FOO", execution.getExitStatus().getExitCode()); + assertEquals(1, execution.getStepExecutions().size()); + } - @Test - void testBuildWithStop() { - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); - builder.on("COMPLETED").stop(); - builder.preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.STOPPED, execution.getStatus()); - assertEquals("STOPPED", execution.getExitStatus().getExitCode()); - assertEquals(1, execution.getStepExecutions().size()); - } + @Test + void testBuildWithStop() { + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); + builder.on("COMPLETED").stop(); + builder.preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.STOPPED, execution.getStatus()); + assertEquals("STOPPED", execution.getExitStatus().getExitCode()); + assertEquals(1, execution.getStepExecutions().size()); + } - @Test - void testBuildWithStopAndRestart() throws Exception { - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(fails); - builder.on("FAILED").stopAndRestart(step2); - Job job = builder.build(); - job.execute(execution); - assertEquals(BatchStatus.STOPPED, execution.getStatus()); - assertEquals(1, execution.getStepExecutions().size()); - execution = jobRepository.createJobExecution("flow", new JobParameters()); - job.execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(1, execution.getStepExecutions().size()); - assertEquals("step2", execution.getStepExecutions().iterator().next().getStepName()); - } + @Test + void testBuildWithStopAndRestart() throws Exception { + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(fails); + builder.on("FAILED").stopAndRestart(step2); + Job job = builder.build(); + job.execute(execution); + assertEquals(BatchStatus.STOPPED, execution.getStatus()); + assertEquals(1, execution.getStepExecutions().size()); + execution = jobRepository.createJobExecution("flow", new JobParameters()); + job.execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(1, execution.getStepExecutions().size()); + assertEquals("step2", execution.getStepExecutions().iterator().next().getStepName()); + } + + @Test + void testBuildWithJobScopedStep() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(JobConfiguration.class); + JobLauncher jobLauncher = context.getBean(JobLauncher.class); + Job job = context.getBean(Job.class); + JobParameters jobParameters = new JobParametersBuilder().addLong("chunkSize", 2L).toJobParameters(); + + // when + JobExecution jobExecution = jobLauncher.run(job, jobParameters); + + // then + assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + } + // https://github.com/spring-projects/spring-batch/issues/3757#issuecomment-1821593539 @Test - void testBuildWithJobScopedStep() throws Exception { - // given - ApplicationContext context = new AnnotationConfigApplicationContext(JobConfiguration.class); + void testStepNamesMustBeUniqueWithinFlowDefinition() { + ApplicationContext context = new AnnotationConfigApplicationContext(JobConfigurationForStepNameUnique.class); JobLauncher jobLauncher = context.getBean(JobLauncher.class); Job job = context.getBean(Job.class); - JobParameters jobParameters = new JobParametersBuilder().addLong("chunkSize", 2L).toJobParameters(); - - // when - JobExecution jobExecution = jobLauncher.run(job, jobParameters); - - // then - assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + assertThrows(AlreadyUsedStepNameException.class, ()->jobLauncher.run(job, new JobParametersBuilder().addLong("random", 2L).addString("stepTwo.name", JobConfigurationForStepNameUnique.SHARED_NAME).toJobParameters())); + assertThrows(AlreadyUsedStepNameException.class, ()->jobLauncher.run(job, new JobParametersBuilder().addLong("random", 1L).addString("stepTwo.name",JobConfigurationForStepNameUnique.SHARED_NAME).toJobParameters())); } - //https://github.com/spring-projects/spring-batch/issues/3757#issuecomment-1821593539 - @Test - void testStepNamesMustBeUniqueWithinFlowDefinition() { - Step conditionalStep = new StepSupport("conditionalStep") { - @Override - public void execute(StepExecution stepExecution) { - stepExecution.upgradeStatus(BatchStatus.COMPLETED); - stepExecution.setExitStatus(ExitStatus.COMPLETED); - String exitStatus = (System.currentTimeMillis() % 2 == 0) ? "EVEN" : "ODD"; - stepExecution.setExitStatus(new ExitStatus(exitStatus)); - jobRepository.update(stepExecution); - } - }; - - StepSupport misnamedStep = new StepSupport(step3Name) { - @Override - public void execute(StepExecution stepExecution) - throws UnexpectedJobExecutionException { + @EnableBatchProcessing + @Configuration + static class JobConfigurationForStepNameUnique{ - stepExecution.upgradeStatus(BatchStatus.COMPLETED); - stepExecution.setExitStatus(ExitStatus.COMPLETED); - jobRepository.update(stepExecution); - } - }; + private static final String SHARED_NAME ="sharedName"; - JobBuilder jobBuilder = new JobBuilder("flow", jobRepository); - FlowBuilder flowBuilder = jobBuilder.start(conditionalStep) - .on("ODD").to(step2) - .from(conditionalStep).on("EVEN").to(step3) - .from(step3); - assertThrows(AlreadyUsedStepNameException.class, () -> flowBuilder.next(misnamedStep)); - flowBuilder.end().build(); - } + private static final Log logger = LogFactory.getLog(FlowJobBuilderTests.class); - @EnableBatchProcessing - @Configuration - static class JobConfiguration { + @Bean + @JobScope + public Step conditionalStep(JobRepository jobRepository, PlatformTransactionManager transactionManager, + @Value("#{jobParameters['random']}") Integer random) { + return new StepBuilder("conditionalStep", jobRepository).tasklet( + (StepContribution contribution, ChunkContext chunkContext) ->{ + String exitStatus = (random % 2 == 0) ? "EVEN" : "ODD"; + logger.info("'conditionalStep' with exitStatus "+exitStatus); + contribution.setExitStatus(new ExitStatus(exitStatus)); + return RepeatStatus.FINISHED; + }, transactionManager + ).build(); + } @Bean @JobScope - public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager, - @Value("#{jobParameters['chunkSize']}") Integer chunkSize) { - return new StepBuilder("step", jobRepository).chunk(chunkSize, transactionManager) - .reader(new ListItemReader<>(Arrays.asList(1, 2, 3, 4))) - .writer(items -> { - }) + public Step stepTwo(JobRepository jobRepository, PlatformTransactionManager transactionManager, + @Value("#{jobParameters['stepTwo.name']}") String name) { + return new StepBuilder(name, jobRepository) + .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { + logger.info("Hello from stepTwo"); + return RepeatStatus.FINISHED; + }, transactionManager) .build(); } @Bean - public Job job(JobRepository jobRepository, PlatformTransactionManager transactionManager) { - Step step = step(jobRepository, transactionManager, null); - return new JobBuilder("job", jobRepository).flow(step).build().build(); + public Step stepThree(JobRepository jobRepository, PlatformTransactionManager transactionManager) { + return new StepBuilder(SHARED_NAME, jobRepository) + .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { + logger.info("Hello from stepThree"); + return RepeatStatus.FINISHED; + }, transactionManager) + .build(); } @Bean - public DataSource dataSource() { - return new EmbeddedDatabaseBuilder().addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") - .addScript("/org/springframework/batch/core/schema-hsqldb.sql") - .generateUniqueName(true) + public Step stepFour(JobRepository jobRepository, PlatformTransactionManager transactionManager) { + return new StepBuilder(SHARED_NAME, jobRepository) + .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { + logger.info("Hello from stepFour"); + return RepeatStatus.FINISHED; + }, transactionManager) .build(); } @Bean - public JdbcTransactionManager transactionManager(DataSource dataSource) { - return new JdbcTransactionManager(dataSource); - } + public Job job(JobRepository jobRepository, @Qualifier("conditionalStep") Step conditionalStep, + @Qualifier("stepFour") Step step4, @Qualifier("stepTwo") Step step2, + @Qualifier("stepThree") Step step3) { + JobBuilder jobBuilder = new JobBuilder("flow", jobRepository); + return jobBuilder.start(conditionalStep) + .on("ODD").to(step2) + .from(conditionalStep).on("EVEN").to(step3) + .from(step3) + .next(step4) + .from(step2).next(step4).end().build(); + } + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder().addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") + .addScript("/org/springframework/batch/core/schema-hsqldb.sql") + .generateUniqueName(true) + .build(); + } + + @Bean + public JdbcTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } - } + + } + + + @EnableBatchProcessing + @Configuration + static class JobConfiguration { + + @Bean + @JobScope + public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager, + @Value("#{jobParameters['chunkSize']}") Integer chunkSize) { + return new StepBuilder("step", jobRepository).chunk(chunkSize, transactionManager) + .reader(new ListItemReader<>(Arrays.asList(1, 2, 3, 4))) + .writer(items -> { + }) + .build(); + } + + @Bean + public Job job(JobRepository jobRepository, PlatformTransactionManager transactionManager) { + Step step = step(jobRepository, transactionManager, null); + return new JobBuilder("job", jobRepository).flow(step).build().build(); + } + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder().addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") + .addScript("/org/springframework/batch/core/schema-hsqldb.sql") + .generateUniqueName(true) + .build(); + } + + @Bean + public JdbcTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } + + } } From aed184b8876e426a5c1d7ef199fad1977a7ba531 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Thu, 1 Feb 2024 18:20:19 +0100 Subject: [PATCH 003/152] fix(log for tests) print log in console for tests Signed-off-by: Fabrice Bibonne --- pom.xml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pom.xml b/pom.xml index 47fa59050f..a328cc16b3 100644 --- a/pom.xml +++ b/pom.xml @@ -153,6 +153,26 @@ 0.0.39 + + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + + + + From 261548568b01d57f66798903d3c3f18c302241f5 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Mon, 5 Feb 2024 22:05:59 +0100 Subject: [PATCH 004/152] feat(step name unicity) #3757 the names of different steps in a job must be different Signed-off-by: Fabrice Bibonne --- .../batch/core/job/AbstractJob.java | 30 +- .../batch/core/job/SimpleJob.java | 11 +- .../batch/core/job/flow/FlowJob.java | 55 +- .../core/job/ExtendedAbstractJobTests.java | 7 + .../core/job/builder/FlowJobBuilderTests.java | 802 +++++++++--------- .../support/SimpleJobOperatorTests.java | 5 + 6 files changed, 485 insertions(+), 425 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java index 34d6d19f58..3b98aafb2b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java @@ -17,8 +17,7 @@ package org.springframework.batch.core.job; import java.time.LocalDateTime; -import java.util.Collection; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; import io.micrometer.core.instrument.LongTaskTimer; @@ -43,6 +42,7 @@ import org.springframework.batch.core.StartLimitExceededException; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.builder.AlreadyUsedStepNameException; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.launch.support.ExitCodeMapper; import org.springframework.batch.core.listener.CompositeJobExecutionListener; @@ -300,6 +300,7 @@ public final void execute(JobExecution execution) { execution.setStartTime(LocalDateTime.now()); updateStatus(execution, BatchStatus.STARTED); + checkStepNamesUnicity(); listener.beforeJob(execution); @@ -368,9 +369,23 @@ public final void execute(JobExecution execution) { finally { JobSynchronizationManager.release(); } - } + } + + protected abstract void checkStepNamesUnicity() throws AlreadyUsedStepNameException ; + private Optional findFirstDoubleElementInList(List strings) { + if (strings==null){ + return Optional.empty(); + } + Set alreadyChecked=new HashSet<>(); + for (String value:strings){ + if (alreadyChecked.contains(value)){ + return Optional.of(value); + } + alreadyChecked.add(value); + } + return Optional.empty(); } private void stopObservation(JobExecution execution, Observation observation) { @@ -430,6 +445,15 @@ else if (ex instanceof NoSuchJobException || ex.getCause() instanceof NoSuchJobE return exitStatus; } + protected static void addToMapCheckingUnicity(Map map, Step step, String name) throws AlreadyUsedStepNameException { + map.merge(name, step, (old, value)->{ + if (!old.equals(value)){ + throw new AlreadyUsedStepNameException(name); + } + return old; + }); + } + private void updateStatus(JobExecution jobExecution, BatchStatus status) { jobExecution.setStatus(status); jobRepository.update(jobExecution); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java index b22317ef28..94aa2911de 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java @@ -16,9 +16,7 @@ package org.springframework.batch.core.job; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.Job; @@ -27,6 +25,7 @@ import org.springframework.batch.core.StartLimitExceededException; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.builder.AlreadyUsedStepNameException; import org.springframework.batch.core.repository.JobRestartException; import org.springframework.batch.core.step.StepLocator; @@ -145,4 +144,10 @@ protected void doExecute(JobExecution execution) } } + @Override + protected void checkStepNamesUnicity() throws AlreadyUsedStepNameException { + Map map = new HashMap<>(); + steps.forEach(step->{addToMapCheckingUnicity(map, step, step.getName());}); + } + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java index 33e2f491fe..37405db252 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java @@ -15,19 +15,20 @@ */ package org.springframework.batch.core.job.flow; -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecutionException; import org.springframework.batch.core.Step; import org.springframework.batch.core.job.AbstractJob; import org.springframework.batch.core.job.SimpleStepHandler; +import org.springframework.batch.core.job.builder.AlreadyUsedStepNameException; import org.springframework.batch.core.step.StepHolder; import org.springframework.batch.core.step.StepLocator; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + /** * Implementation of the {@link Job} interface that allows for complex flows of steps, * rather than requiring sequential execution. In general, this job implementation was @@ -74,40 +75,47 @@ public void setFlow(Flow flow) { */ @Override public Step getStep(String stepName) { - if (!initialized) { - init(); - } + init(); return stepMap.get(stepName); } + /** * Initialize the step names */ private void init() { - findSteps(flow, stepMap); - initialized = true; + if (!initialized) { + findStepsThrowingIfNameNotUnique(flow, stepMap); + initialized = true; + } } - private void findSteps(Flow flow, Map map) { + private void findStepsThrowingIfNameNotUnique(Flow flow, Map map) { for (State state : flow.getStates()) { if (state instanceof StepLocator locator) { for (String name : locator.getStepNames()) { - map.put(name, locator.getStep(name)); + addToMapCheckingUnicity(map, locator.getStep(name), name); } } - else if (state instanceof StepHolder) { - Step step = ((StepHolder) state).getStep(); - String name = step.getName(); - stepMap.put(name, step); + //TODO remove this else bock ? not executed during tests : the only State wich implements StepHolder is StepState which implements also StepLocator + /* + Tests Coverage + Hits : 30 + state instanceof StepHolder + true hits: 0 + false hits : 30 + */ + else if (state instanceof StepHolder stepHolder) { + Step step = stepHolder.getStep(); + addToMapCheckingUnicity(map, step, step.getName()); } - else if (state instanceof FlowHolder) { - for (Flow subflow : ((FlowHolder) state).getFlows()) { - findSteps(subflow, map); + else if (state instanceof FlowHolder flowHolder) { + for (Flow subflow : flowHolder.getFlows()) { + findStepsThrowingIfNameNotUnique(subflow, map); } } } - } /** @@ -115,9 +123,7 @@ else if (state instanceof FlowHolder) { */ @Override public Collection getStepNames() { - if (!initialized) { - init(); - } + init(); return stepMap.keySet(); } @@ -139,4 +145,9 @@ protected void doExecute(final JobExecution execution) throws JobExecutionExcept } } + @Override + protected void checkStepNamesUnicity() throws AlreadyUsedStepNameException { + init(); + } + } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java index 79a5684b1f..cf04807204 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java @@ -25,6 +25,7 @@ import org.springframework.batch.core.JobParametersInvalidException; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.job.builder.AlreadyUsedStepNameException; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; import org.springframework.batch.core.step.StepSupport; @@ -36,6 +37,7 @@ import java.time.LocalDateTime; import java.util.Collection; import java.util.Collections; +import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -215,6 +217,11 @@ public StubJob() { protected void doExecute(JobExecution execution) throws JobExecutionException { } + @Override + protected void checkStepNamesUnicity() throws AlreadyUsedStepNameException { + + } + @Override public Step getStep(String stepName) { return null; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java index 9dfbd7cb30..cd7f22ecf3 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java @@ -27,7 +27,10 @@ import org.springframework.batch.core.job.flow.JobExecutionDecider; import org.springframework.batch.core.job.flow.support.SimpleFlow; import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.JobRestartException; import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; import org.springframework.batch.core.scope.context.ChunkContext; import org.springframework.batch.core.step.StepSupport; @@ -50,6 +53,7 @@ import javax.sql.DataSource; import java.util.Arrays; +import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -59,434 +63,438 @@ */ class FlowJobBuilderTests { - private JobRepository jobRepository; - - private JobExecution execution; - - private final StepSupport step1 = new StepSupport("step1") { - @Override - public void execute(StepExecution stepExecution) - throws JobInterruptedException, UnexpectedJobExecutionException { - stepExecution.upgradeStatus(BatchStatus.COMPLETED); - stepExecution.setExitStatus(ExitStatus.COMPLETED); - jobRepository.update(stepExecution); - } - }; - - private final StepSupport fails = new StepSupport("fails") { - @Override - public void execute(StepExecution stepExecution) - throws JobInterruptedException, UnexpectedJobExecutionException { - stepExecution.upgradeStatus(BatchStatus.FAILED); - stepExecution.setExitStatus(ExitStatus.FAILED); - jobRepository.update(stepExecution); - } - }; - - private final StepSupport step2 = new StepSupport("step2") { - @Override - public void execute(StepExecution stepExecution) - throws JobInterruptedException, UnexpectedJobExecutionException { - stepExecution.upgradeStatus(BatchStatus.COMPLETED); - stepExecution.setExitStatus(ExitStatus.COMPLETED); - jobRepository.update(stepExecution); - } - }; - - private final StepSupport step3 = new StepSupport("step3") { - @Override - public void execute(StepExecution stepExecution) - throws JobInterruptedException, UnexpectedJobExecutionException { - stepExecution.upgradeStatus(BatchStatus.COMPLETED); - stepExecution.setExitStatus(ExitStatus.COMPLETED); - jobRepository.update(stepExecution); - } - }; - - @BeforeEach - void init() throws Exception { - EmbeddedDatabase embeddedDatabase = new EmbeddedDatabaseBuilder() - .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") - .addScript("/org/springframework/batch/core/schema-hsqldb.sql") - .build(); - JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); - factory.setDataSource(embeddedDatabase); - factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); - factory.afterPropertiesSet(); - jobRepository = factory.getObject(); - execution = jobRepository.createJobExecution("flow", new JobParameters()); - } - - @Test - void testBuildOnOneLine() { - FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1) - .on("COMPLETED") - .to(step2) - .end() - .preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } - - @Test - void testBuildSingleFlow() { - Flow flow = new FlowBuilder("subflow").from(step1).next(step2).build(); - FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(flow).end().preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } - - @Test - void testBuildSingleFlowAddingStepsViaNext() { - Flow flow = new FlowBuilder("subflow").next(step1).next(step2).build(); - FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(flow).end().preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } - - @Test - void testBuildOverTwoLines() { - FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1).on("COMPLETED").to(step2).end(); - builder.preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } - - @Test - void testBuildSubflow() { - Flow flow = new FlowBuilder("subflow").from(step1).end(); - JobFlowBuilder builder = new JobBuilder("flow", jobRepository).start(flow); - builder.on("COMPLETED").to(step2); - builder.end().preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } - - @Test - void testBuildSplit() { - Flow flow = new FlowBuilder("subflow").from(step1).end(); - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step2); - builder.split(new SimpleAsyncTaskExecutor()).add(flow).end(); - builder.preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } - - @Test - void testNestedSplitsWithSingleThread() { - SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); - taskExecutor.setConcurrencyLimit(1); - - FlowBuilder flowBuilder = new FlowBuilder<>("flow"); - FlowBuilder.SplitBuilder splitBuilder = flowBuilder.split(taskExecutor); - splitBuilder.add(new FlowBuilder("subflow1").from(step1).end()); - splitBuilder.add(new FlowBuilder("subflow2").from(step2).end()); - Job job = new JobBuilder("job", jobRepository).start(flowBuilder.build()).end().build(); - job.execute(execution); - - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } - - @Test - void testBuildSplitUsingStartAndAdd_BATCH_2346() { - Flow subflow1 = new FlowBuilder("subflow1").from(step2).end(); - Flow subflow2 = new FlowBuilder("subflow2").from(step3).end(); - Flow splitflow = new FlowBuilder("splitflow").start(subflow1) - .split(new SimpleAsyncTaskExecutor()) - .add(subflow2) - .build(); - - FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(splitflow).end(); - builder.preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } - - @Test - void testBuildSplit_BATCH_2282() { - Flow flow1 = new FlowBuilder("subflow1").from(step1).end(); - Flow flow2 = new FlowBuilder("subflow2").from(step2).end(); - Flow splitFlow = new FlowBuilder("splitflow").split(new SimpleAsyncTaskExecutor()) - .add(flow1, flow2) - .build(); - FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(splitFlow).end(); - builder.preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } - - @Test - void testBuildDecision() { - JobExecutionDecider decider = new JobExecutionDecider() { - private int count = 0; - - @Override - public FlowExecutionStatus decide(JobExecution jobExecution, @Nullable StepExecution stepExecution) { - count++; - return count < 2 ? new FlowExecutionStatus("ONGOING") : FlowExecutionStatus.COMPLETED; - } - }; - step1.setAllowStartIfComplete(true); - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); - builder.next(decider).on("COMPLETED").end().from(decider).on("*").to(step1).end(); - builder.preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } - - @Test - void testBuildWithDeciderAtStart() { - JobExecutionDecider decider = new JobExecutionDecider() { - private int count = 0; - - @Override - public FlowExecutionStatus decide(JobExecution jobExecution, @Nullable StepExecution stepExecution) { - count++; - return count < 2 ? new FlowExecutionStatus("ONGOING") : FlowExecutionStatus.COMPLETED; - } - }; - JobFlowBuilder builder = new JobBuilder("flow", jobRepository).start(decider); - builder.on("COMPLETED").end().from(decider).on("*").to(step1).end(); - builder.build().preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(1, execution.getStepExecutions().size()); - } - - @Test - void testBuildWithDeciderPriority() { - JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("COMPLETED_PARTIALLY"); - JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); - builder.on("COMPLETED_PARTIALLY").end(); - builder.on("COMPLETED*").fail(); - builder.build().preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - } - - @Test - void testBuildWithWildcardDeciderPriority() { - JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("COMPLETED_PARTIALLY"); - JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); - builder.on("COMPLETED_?ARTIALLY").end(); - builder.on("COMPLETED_*ARTIALLY").fail(); - builder.build().preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - } - - @Test - void testBuildWithDeciderPrioritySubstringAndWildcard() { - JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("CONTINUABLE"); - JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); - builder.on("CONTINUABLE").end(); - builder.on("CONTIN*").fail(); - builder.build().preventRestart().build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - } - - @Test - void testBuildWithIntermediateSimpleJob() { - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); - builder.on("COMPLETED").to(step2).end(); - builder.preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } - - @Test - void testBuildWithIntermediateSimpleJobTwoSteps() { - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1).next(step2); - builder.on("FAILED").to(step3).end(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(2, execution.getStepExecutions().size()); - } - - @Test - void testBuildWithCustomEndState() { - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); - builder.on("COMPLETED").end("FOO"); - builder.preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals("FOO", execution.getExitStatus().getExitCode()); - assertEquals(1, execution.getStepExecutions().size()); - } - - @Test - void testBuildWithStop() { - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); - builder.on("COMPLETED").stop(); - builder.preventRestart(); - builder.build().execute(execution); - assertEquals(BatchStatus.STOPPED, execution.getStatus()); - assertEquals("STOPPED", execution.getExitStatus().getExitCode()); - assertEquals(1, execution.getStepExecutions().size()); - } - - @Test - void testBuildWithStopAndRestart() throws Exception { - SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(fails); - builder.on("FAILED").stopAndRestart(step2); - Job job = builder.build(); - job.execute(execution); - assertEquals(BatchStatus.STOPPED, execution.getStatus()); - assertEquals(1, execution.getStepExecutions().size()); - execution = jobRepository.createJobExecution("flow", new JobParameters()); - job.execute(execution); - assertEquals(BatchStatus.COMPLETED, execution.getStatus()); - assertEquals(1, execution.getStepExecutions().size()); - assertEquals("step2", execution.getStepExecutions().iterator().next().getStepName()); - } - - @Test - void testBuildWithJobScopedStep() throws Exception { - // given - ApplicationContext context = new AnnotationConfigApplicationContext(JobConfiguration.class); - JobLauncher jobLauncher = context.getBean(JobLauncher.class); - Job job = context.getBean(Job.class); - JobParameters jobParameters = new JobParametersBuilder().addLong("chunkSize", 2L).toJobParameters(); - - // when - JobExecution jobExecution = jobLauncher.run(job, jobParameters); - - // then - assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); - } + private JobRepository jobRepository; + + private JobExecution execution; + + private final StepSupport step1 = new StepSupport("step1") { + @Override + public void execute(StepExecution stepExecution) + throws JobInterruptedException, UnexpectedJobExecutionException { + stepExecution.upgradeStatus(BatchStatus.COMPLETED); + stepExecution.setExitStatus(ExitStatus.COMPLETED); + jobRepository.update(stepExecution); + } + }; + + private final StepSupport fails = new StepSupport("fails") { + @Override + public void execute(StepExecution stepExecution) + throws JobInterruptedException, UnexpectedJobExecutionException { + stepExecution.upgradeStatus(BatchStatus.FAILED); + stepExecution.setExitStatus(ExitStatus.FAILED); + jobRepository.update(stepExecution); + } + }; + + private final StepSupport step2 = new StepSupport("step2") { + @Override + public void execute(StepExecution stepExecution) + throws JobInterruptedException, UnexpectedJobExecutionException { + stepExecution.upgradeStatus(BatchStatus.COMPLETED); + stepExecution.setExitStatus(ExitStatus.COMPLETED); + jobRepository.update(stepExecution); + } + }; + + private final StepSupport step3 = new StepSupport("step3") { + @Override + public void execute(StepExecution stepExecution) + throws JobInterruptedException, UnexpectedJobExecutionException { + stepExecution.upgradeStatus(BatchStatus.COMPLETED); + stepExecution.setExitStatus(ExitStatus.COMPLETED); + jobRepository.update(stepExecution); + } + }; + + @BeforeEach + void init() throws Exception { + EmbeddedDatabase embeddedDatabase = new EmbeddedDatabaseBuilder() + .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") + .addScript("/org/springframework/batch/core/schema-hsqldb.sql") + .build(); + JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); + factory.setDataSource(embeddedDatabase); + factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); + factory.afterPropertiesSet(); + jobRepository = factory.getObject(); + execution = jobRepository.createJobExecution("flow", new JobParameters()); + } + + @Test + void testBuildOnOneLine() { + FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1) + .on("COMPLETED") + .to(step2) + .end() + .preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } + + @Test + void testBuildSingleFlow() { + Flow flow = new FlowBuilder("subflow").from(step1).next(step2).build(); + FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(flow).end().preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } + + @Test + void testBuildSingleFlowAddingStepsViaNext() { + Flow flow = new FlowBuilder("subflow").next(step1).next(step2).build(); + FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(flow).end().preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } + + @Test + void testBuildOverTwoLines() { + FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1).on("COMPLETED").to(step2).end(); + builder.preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } + + @Test + void testBuildSubflow() { + Flow flow = new FlowBuilder("subflow").from(step1).end(); + JobFlowBuilder builder = new JobBuilder("flow", jobRepository).start(flow); + builder.on("COMPLETED").to(step2); + builder.end().preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } + + @Test + void testBuildSplit() { + Flow flow = new FlowBuilder("subflow").from(step1).end(); + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step2); + builder.split(new SimpleAsyncTaskExecutor()).add(flow).end(); + builder.preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } + + @Test + void testNestedSplitsWithSingleThread() { + SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); + taskExecutor.setConcurrencyLimit(1); + + FlowBuilder flowBuilder = new FlowBuilder<>("flow"); + FlowBuilder.SplitBuilder splitBuilder = flowBuilder.split(taskExecutor); + splitBuilder.add(new FlowBuilder("subflow1").from(step1).end()); + splitBuilder.add(new FlowBuilder("subflow2").from(step2).end()); + Job job = new JobBuilder("job", jobRepository).start(flowBuilder.build()).end().build(); + job.execute(execution); + + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } + + @Test + void testBuildSplitUsingStartAndAdd_BATCH_2346() { + Flow subflow1 = new FlowBuilder("subflow1").from(step2).end(); + Flow subflow2 = new FlowBuilder("subflow2").from(step3).end(); + Flow splitflow = new FlowBuilder("splitflow").start(subflow1) + .split(new SimpleAsyncTaskExecutor()) + .add(subflow2) + .build(); + + FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(splitflow).end(); + builder.preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } + + @Test + void testBuildSplit_BATCH_2282() { + Flow flow1 = new FlowBuilder("subflow1").from(step1).end(); + Flow flow2 = new FlowBuilder("subflow2").from(step2).end(); + Flow splitFlow = new FlowBuilder("splitflow").split(new SimpleAsyncTaskExecutor()) + .add(flow1, flow2) + .build(); + FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(splitFlow).end(); + builder.preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } + + @Test + void testBuildDecision() { + JobExecutionDecider decider = new JobExecutionDecider() { + private int count = 0; + + @Override + public FlowExecutionStatus decide(JobExecution jobExecution, @Nullable StepExecution stepExecution) { + count++; + return count < 2 ? new FlowExecutionStatus("ONGOING") : FlowExecutionStatus.COMPLETED; + } + }; + step1.setAllowStartIfComplete(true); + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); + builder.next(decider).on("COMPLETED").end().from(decider).on("*").to(step1).end(); + builder.preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } + + @Test + void testBuildWithDeciderAtStart() { + JobExecutionDecider decider = new JobExecutionDecider() { + private int count = 0; + + @Override + public FlowExecutionStatus decide(JobExecution jobExecution, @Nullable StepExecution stepExecution) { + count++; + return count < 2 ? new FlowExecutionStatus("ONGOING") : FlowExecutionStatus.COMPLETED; + } + }; + JobFlowBuilder builder = new JobBuilder("flow", jobRepository).start(decider); + builder.on("COMPLETED").end().from(decider).on("*").to(step1).end(); + builder.build().preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(1, execution.getStepExecutions().size()); + } + + @Test + void testBuildWithDeciderPriority() { + JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("COMPLETED_PARTIALLY"); + JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); + builder.on("COMPLETED_PARTIALLY").end(); + builder.on("COMPLETED*").fail(); + builder.build().preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + } + + @Test + void testBuildWithWildcardDeciderPriority() { + JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("COMPLETED_PARTIALLY"); + JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); + builder.on("COMPLETED_?ARTIALLY").end(); + builder.on("COMPLETED_*ARTIALLY").fail(); + builder.build().preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + } + + @Test + void testBuildWithDeciderPrioritySubstringAndWildcard() { + JobExecutionDecider decider = (jobExecution, stepExecution) -> new FlowExecutionStatus("CONTINUABLE"); + JobFlowBuilder builder = new JobBuilder("flow_priority", jobRepository).start(decider); + builder.on("CONTINUABLE").end(); + builder.on("CONTIN*").fail(); + builder.build().preventRestart().build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + } + + @Test + void testBuildWithIntermediateSimpleJob() { + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); + builder.on("COMPLETED").to(step2).end(); + builder.preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } + + @Test + void testBuildWithIntermediateSimpleJobTwoSteps() { + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1).next(step2); + builder.on("FAILED").to(step3).end(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(2, execution.getStepExecutions().size()); + } + + @Test + void testBuildWithCustomEndState() { + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); + builder.on("COMPLETED").end("FOO"); + builder.preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals("FOO", execution.getExitStatus().getExitCode()); + assertEquals(1, execution.getStepExecutions().size()); + } + + @Test + void testBuildWithStop() { + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1); + builder.on("COMPLETED").stop(); + builder.preventRestart(); + builder.build().execute(execution); + assertEquals(BatchStatus.STOPPED, execution.getStatus()); + assertEquals("STOPPED", execution.getExitStatus().getExitCode()); + assertEquals(1, execution.getStepExecutions().size()); + } + + @Test + void testBuildWithStopAndRestart() throws Exception { + SimpleJobBuilder builder = new JobBuilder("flow", jobRepository).start(fails); + builder.on("FAILED").stopAndRestart(step2); + Job job = builder.build(); + job.execute(execution); + assertEquals(BatchStatus.STOPPED, execution.getStatus()); + assertEquals(1, execution.getStepExecutions().size()); + execution = jobRepository.createJobExecution("flow", new JobParameters()); + job.execute(execution); + assertEquals(BatchStatus.COMPLETED, execution.getStatus()); + assertEquals(1, execution.getStepExecutions().size()); + assertEquals("step2", execution.getStepExecutions().iterator().next().getStepName()); + } + + @Test + void testBuildWithJobScopedStep() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(JobConfiguration.class); + JobLauncher jobLauncher = context.getBean(JobLauncher.class); + Job job = context.getBean(Job.class); + JobParameters jobParameters = new JobParametersBuilder().addLong("chunkSize", 2L).toJobParameters(); + + // when + JobExecution jobExecution = jobLauncher.run(job, jobParameters); + + // then + assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + } // https://github.com/spring-projects/spring-batch/issues/3757#issuecomment-1821593539 @Test - void testStepNamesMustBeUniqueWithinFlowDefinition() { + void testStepNamesMustBeUniqueWithinFlowDefinition() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { ApplicationContext context = new AnnotationConfigApplicationContext(JobConfigurationForStepNameUnique.class); JobLauncher jobLauncher = context.getBean(JobLauncher.class); Job job = context.getBean(Job.class); - assertThrows(AlreadyUsedStepNameException.class, ()->jobLauncher.run(job, new JobParametersBuilder().addLong("random", 2L).addString("stepTwo.name", JobConfigurationForStepNameUnique.SHARED_NAME).toJobParameters())); - assertThrows(AlreadyUsedStepNameException.class, ()->jobLauncher.run(job, new JobParametersBuilder().addLong("random", 1L).addString("stepTwo.name",JobConfigurationForStepNameUnique.SHARED_NAME).toJobParameters())); + JobExecution jobExecution=jobLauncher.run(job, new JobParametersBuilder().addLong("random", 2L).addString("stepTwo.name", JobConfigurationForStepNameUnique.SHARED_NAME).toJobParameters()); + assertTrue(jobExecution.getAllFailureExceptions().stream().map(Object::getClass).anyMatch(AlreadyUsedStepNameException.class::equals)); + assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution.getExitStatus().getExitCode()); + jobExecution=jobLauncher.run(job, new JobParametersBuilder().addLong("random", 1L).addString("stepTwo.name", JobConfigurationForStepNameUnique.SHARED_NAME).toJobParameters()); + assertTrue(jobExecution.getAllFailureExceptions().stream().map(Object::getClass).anyMatch(AlreadyUsedStepNameException.class::equals)); + assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution.getExitStatus().getExitCode()); } - @EnableBatchProcessing - @Configuration - static class JobConfigurationForStepNameUnique{ + @EnableBatchProcessing + @Configuration + static class JobConfigurationForStepNameUnique { - private static final String SHARED_NAME ="sharedName"; + private static final String SHARED_NAME = "sharedName"; - private static final Log logger = LogFactory.getLog(FlowJobBuilderTests.class); + private static final Log logger = LogFactory.getLog(FlowJobBuilderTests.class); - @Bean - @JobScope - public Step conditionalStep(JobRepository jobRepository, PlatformTransactionManager transactionManager, - @Value("#{jobParameters['random']}") Integer random) { - return new StepBuilder("conditionalStep", jobRepository).tasklet( - (StepContribution contribution, ChunkContext chunkContext) ->{ - String exitStatus = (random % 2 == 0) ? "EVEN" : "ODD"; - logger.info("'conditionalStep' with exitStatus "+exitStatus); - contribution.setExitStatus(new ExitStatus(exitStatus)); - return RepeatStatus.FINISHED; - }, transactionManager - ).build(); - } + @Bean + @JobScope + public Step conditionalStep(JobRepository jobRepository, PlatformTransactionManager transactionManager, + @Value("#{jobParameters['random']}") Integer random) { + return new StepBuilder("conditionalStep", jobRepository).tasklet( + (StepContribution contribution, ChunkContext chunkContext) -> { + String exitStatus = (random % 2 == 0) ? "EVEN" : "ODD"; + logger.info("'conditionalStep' with exitStatus " + exitStatus); + contribution.setExitStatus(new ExitStatus(exitStatus)); + return RepeatStatus.FINISHED; + }, transactionManager + ).build(); + } @Bean @JobScope public Step stepTwo(JobRepository jobRepository, PlatformTransactionManager transactionManager, @Value("#{jobParameters['stepTwo.name']}") String name) { return new StepBuilder(name, jobRepository) - .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { - logger.info("Hello from stepTwo"); - return RepeatStatus.FINISHED; - }, transactionManager) - .build(); + .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { + logger.info("Hello from stepTwo"); + return RepeatStatus.FINISHED; + }, transactionManager) + .build(); } @Bean public Step stepThree(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder(SHARED_NAME, jobRepository) - .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { - logger.info("Hello from stepThree"); - return RepeatStatus.FINISHED; - }, transactionManager) - .build(); + .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { + logger.info("Hello from stepThree"); + return RepeatStatus.FINISHED; + }, transactionManager) + .build(); } @Bean public Step stepFour(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder(SHARED_NAME, jobRepository) - .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { - logger.info("Hello from stepFour"); - return RepeatStatus.FINISHED; - }, transactionManager) - .build(); + .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { + logger.info("Hello from stepFour"); + return RepeatStatus.FINISHED; + }, transactionManager) + .build(); } @Bean public Job job(JobRepository jobRepository, @Qualifier("conditionalStep") Step conditionalStep, - @Qualifier("stepFour") Step step4, @Qualifier("stepTwo") Step step2, - @Qualifier("stepThree") Step step3) { - JobBuilder jobBuilder = new JobBuilder("flow", jobRepository); - return jobBuilder.start(conditionalStep) - .on("ODD").to(step2) - .from(conditionalStep).on("EVEN").to(step3) - .from(step3) - .next(step4) - .from(step2).next(step4).end().build(); - } - - @Bean - public DataSource dataSource() { - return new EmbeddedDatabaseBuilder().addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") - .addScript("/org/springframework/batch/core/schema-hsqldb.sql") - .generateUniqueName(true) - .build(); - } - - @Bean - public JdbcTransactionManager transactionManager(DataSource dataSource) { - return new JdbcTransactionManager(dataSource); - } - - - } - - - @EnableBatchProcessing - @Configuration - static class JobConfiguration { - - @Bean - @JobScope - public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager, - @Value("#{jobParameters['chunkSize']}") Integer chunkSize) { - return new StepBuilder("step", jobRepository).chunk(chunkSize, transactionManager) - .reader(new ListItemReader<>(Arrays.asList(1, 2, 3, 4))) - .writer(items -> { - }) - .build(); - } - - @Bean - public Job job(JobRepository jobRepository, PlatformTransactionManager transactionManager) { - Step step = step(jobRepository, transactionManager, null); - return new JobBuilder("job", jobRepository).flow(step).build().build(); - } - - @Bean - public DataSource dataSource() { - return new EmbeddedDatabaseBuilder().addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") - .addScript("/org/springframework/batch/core/schema-hsqldb.sql") - .generateUniqueName(true) - .build(); - } - - @Bean - public JdbcTransactionManager transactionManager(DataSource dataSource) { - return new JdbcTransactionManager(dataSource); - } - - } + @Qualifier("stepFour") Step step4, @Qualifier("stepTwo") Step step2, + @Qualifier("stepThree") Step step3) { + JobBuilder jobBuilder = new JobBuilder("flow", jobRepository); + return jobBuilder.start(conditionalStep) + .on("ODD").to(step2) + .from(conditionalStep).on("EVEN").to(step3) + .from(step3) + .next(step4) + .from(step2).next(step4).end().build(); + } + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder().addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") + .addScript("/org/springframework/batch/core/schema-hsqldb.sql") + .generateUniqueName(true) + .build(); + } + + @Bean + public JdbcTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } + + + } + + + @EnableBatchProcessing + @Configuration + static class JobConfiguration { + + @Bean + @JobScope + public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager, + @Value("#{jobParameters['chunkSize']}") Integer chunkSize) { + return new StepBuilder("step", jobRepository).chunk(chunkSize, transactionManager) + .reader(new ListItemReader<>(Arrays.asList(1, 2, 3, 4))) + .writer(items -> { + }) + .build(); + } + + @Bean + public Job job(JobRepository jobRepository, PlatformTransactionManager transactionManager) { + Step step = step(jobRepository, transactionManager, null); + return new JobBuilder("job", jobRepository).flow(step).build().build(); + } + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder().addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") + .addScript("/org/springframework/batch/core/schema-hsqldb.sql") + .generateUniqueName(true) + .build(); + } + + @Bean + public JdbcTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } + + } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java index d5d3951c2c..daf961cbc1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java @@ -43,6 +43,7 @@ import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.job.AbstractJob; import org.springframework.batch.core.job.JobSupport; +import org.springframework.batch.core.job.builder.AlreadyUsedStepNameException; import org.springframework.batch.core.launch.JobInstanceAlreadyExistsException; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.launch.NoSuchJobExecutionException; @@ -455,6 +456,10 @@ protected void doExecute(JobExecution execution) throws JobExecutionException { } + @Override + protected void checkStepNamesUnicity() throws AlreadyUsedStepNameException { + } + } } From 54209eedb0d6201b034bbeb56a269360f034e90d Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Tue, 6 Feb 2024 06:30:33 +0100 Subject: [PATCH 005/152] fix(step name unicity WITHIN A FLOW) Signed-off-by: Fabrice Bibonne --- .../batch/core/job/AbstractJob.java | 14 ------------ .../batch/core/job/SimpleJob.java | 6 ++--- .../batch/core/job/flow/FlowJob.java | 12 +++++----- .../core/job/ExtendedAbstractJobTests.java | 22 ++++--------------- .../batch/core/job/SimpleJobTests.java | 19 ++++++++++++++++ .../core/job/builder/FlowJobBuilderTests.java | 1 - .../support/SimpleJobOperatorTests.java | 3 +-- 7 files changed, 32 insertions(+), 45 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java index 3b98aafb2b..29b01314f6 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java @@ -374,20 +374,6 @@ public final void execute(JobExecution execution) { protected abstract void checkStepNamesUnicity() throws AlreadyUsedStepNameException ; - private Optional findFirstDoubleElementInList(List strings) { - if (strings==null){ - return Optional.empty(); - } - Set alreadyChecked=new HashSet<>(); - for (String value:strings){ - if (alreadyChecked.contains(value)){ - return Optional.of(value); - } - alreadyChecked.add(value); - } - return Optional.empty(); - } - private void stopObservation(JobExecution execution, Observation observation) { List throwables = execution.getFailureExceptions(); if (!throwables.isEmpty()) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java index 94aa2911de..7b90d3931f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java @@ -25,7 +25,6 @@ import org.springframework.batch.core.StartLimitExceededException; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.job.builder.AlreadyUsedStepNameException; import org.springframework.batch.core.repository.JobRestartException; import org.springframework.batch.core.step.StepLocator; @@ -145,9 +144,8 @@ protected void doExecute(JobExecution execution) } @Override - protected void checkStepNamesUnicity() throws AlreadyUsedStepNameException { - Map map = new HashMap<>(); - steps.forEach(step->{addToMapCheckingUnicity(map, step, step.getName());}); + protected void checkStepNamesUnicity() { + //noop : steps of SimpleJob can share the same name } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java index 37405db252..6ce84fc8a2 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java @@ -85,20 +85,20 @@ public Step getStep(String stepName) { */ private void init() { if (!initialized) { - findStepsThrowingIfNameNotUnique(flow, stepMap); + findStepsThrowingIfNameNotUnique(flow); initialized = true; } } - private void findStepsThrowingIfNameNotUnique(Flow flow, Map map) { + private void findStepsThrowingIfNameNotUnique(Flow flow) { for (State state : flow.getStates()) { if (state instanceof StepLocator locator) { for (String name : locator.getStepNames()) { - addToMapCheckingUnicity(map, locator.getStep(name), name); + addToMapCheckingUnicity(this.stepMap, locator.getStep(name), name); } } - //TODO remove this else bock ? not executed during tests : the only State wich implements StepHolder is StepState which implements also StepLocator + //TODO remove this else bock ? not executed during tests : the only State which implements StepHolder is StepState which already implements StepLocator /* Tests Coverage Hits : 30 @@ -108,11 +108,11 @@ private void findStepsThrowingIfNameNotUnique(Flow flow, Map map) */ else if (state instanceof StepHolder stepHolder) { Step step = stepHolder.getStep(); - addToMapCheckingUnicity(map, step, step.getName()); + addToMapCheckingUnicity(this.stepMap, step, step.getName()); } else if (state instanceof FlowHolder flowHolder) { for (Flow subflow : flowHolder.getFlows()) { - findStepsThrowingIfNameNotUnique(subflow, map); + findStepsThrowingIfNameNotUnique(subflow); } } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java index cf04807204..96ca98c68d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java @@ -17,33 +17,20 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.JobInterruptedException; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.job.builder.AlreadyUsedStepNameException; +import org.springframework.batch.core.*; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; import org.springframework.batch.core.step.StepSupport; -import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.support.JdbcTransactionManager; import org.springframework.lang.Nullable; import java.time.LocalDateTime; import java.util.Collection; import java.util.Collections; -import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; /** * @author Dave Syer @@ -218,8 +205,7 @@ protected void doExecute(JobExecution execution) throws JobExecutionException { } @Override - protected void checkStepNamesUnicity() throws AlreadyUsedStepNameException { - + protected void checkStepNamesUnicity(){ } @Override diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java index 37a1390600..e992855304 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java @@ -513,6 +513,25 @@ void testGetMultipleJobParameters() throws Exception { } + @Test + public void testMultipleStepsWithSameName(){ + job.setName("MultipleStepsWithSameName"); + String sharedName="stepName"; + final List executionsCallbacks=new ArrayList<>(); + StubStep sharedNameStep1=new StubStep(sharedName, jobRepository); + sharedNameStep1.setCallback(()->executionsCallbacks.add("step1")); + job.addStep(sharedNameStep1); + StubStep sharedNameStep2=new StubStep(sharedName, jobRepository); + sharedNameStep2.setCallback(()->executionsCallbacks.add("step2")); + job.addStep(sharedNameStep2); + StubStep sharedNameStep3=new StubStep(sharedName, jobRepository); + sharedNameStep3.setCallback(()->executionsCallbacks.add("step3")); + job.addStep(sharedNameStep3); + job.execute(jobExecution); + assertEquals(List.of("step1", "step2", "step3"), executionsCallbacks); + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + /* * Check JobRepository to ensure status is being saved. */ diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java index cd7f22ecf3..42c49fd3fc 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java @@ -55,7 +55,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; /** * @author Dave Syer diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java index daf961cbc1..e61a8fb5a1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/SimpleJobOperatorTests.java @@ -43,7 +43,6 @@ import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.job.AbstractJob; import org.springframework.batch.core.job.JobSupport; -import org.springframework.batch.core.job.builder.AlreadyUsedStepNameException; import org.springframework.batch.core.launch.JobInstanceAlreadyExistsException; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.batch.core.launch.NoSuchJobExecutionException; @@ -457,7 +456,7 @@ protected void doExecute(JobExecution execution) throws JobExecutionException { } @Override - protected void checkStepNamesUnicity() throws AlreadyUsedStepNameException { + protected void checkStepNamesUnicity() { } } From 7dcd990454b2f1a0ffd042ff7157de36ac4354ba Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Tue, 6 Feb 2024 06:44:37 +0100 Subject: [PATCH 006/152] fix(java format) Signed-off-by: Fabrice Bibonne --- .../batch/core/job/AbstractJob.java | 9 +- .../batch/core/job/SimpleJob.java | 2 +- .../batch/core/job/flow/FlowJob.java | 13 +- .../core/job/ExtendedAbstractJobTests.java | 2 +- .../batch/core/job/SimpleJobTests.java | 18 +-- .../core/job/builder/FlowJobBuilderTests.java | 142 ++++++++++-------- 6 files changed, 99 insertions(+), 87 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java index 29b01314f6..a5f6b20122 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/AbstractJob.java @@ -372,7 +372,7 @@ public final void execute(JobExecution execution) { } } - protected abstract void checkStepNamesUnicity() throws AlreadyUsedStepNameException ; + protected abstract void checkStepNamesUnicity() throws AlreadyUsedStepNameException; private void stopObservation(JobExecution execution, Observation observation) { List throwables = execution.getFailureExceptions(); @@ -431,9 +431,10 @@ else if (ex instanceof NoSuchJobException || ex.getCause() instanceof NoSuchJobE return exitStatus; } - protected static void addToMapCheckingUnicity(Map map, Step step, String name) throws AlreadyUsedStepNameException { - map.merge(name, step, (old, value)->{ - if (!old.equals(value)){ + protected static void addToMapCheckingUnicity(Map map, Step step, String name) + throws AlreadyUsedStepNameException { + map.merge(name, step, (old, value) -> { + if (!old.equals(value)) { throw new AlreadyUsedStepNameException(name); } return old; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java index 7b90d3931f..e45176e199 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java @@ -145,7 +145,7 @@ protected void doExecute(JobExecution execution) @Override protected void checkStepNamesUnicity() { - //noop : steps of SimpleJob can share the same name + // noop : steps of SimpleJob can share the same name } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java index 6ce84fc8a2..9655d62a4f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/FlowJob.java @@ -79,7 +79,6 @@ public Step getStep(String stepName) { return stepMap.get(stepName); } - /** * Initialize the step names */ @@ -98,14 +97,10 @@ private void findStepsThrowingIfNameNotUnique(Flow flow) { addToMapCheckingUnicity(this.stepMap, locator.getStep(name), name); } } - //TODO remove this else bock ? not executed during tests : the only State which implements StepHolder is StepState which already implements StepLocator - /* - Tests Coverage - Hits : 30 - state instanceof StepHolder - true hits: 0 - false hits : 30 - */ + // TODO remove this else bock ? not executed during tests : the only State + // which implements StepHolder is StepState which already implements + // StepLocator + // within tests coverage `state instanceof StepHolder` is false 30 times/30 else if (state instanceof StepHolder stepHolder) { Step step = stepHolder.getStep(); addToMapCheckingUnicity(this.stepMap, step, step.getName()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java index 96ca98c68d..33b886f584 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/ExtendedAbstractJobTests.java @@ -205,7 +205,7 @@ protected void doExecute(JobExecution execution) throws JobExecutionException { } @Override - protected void checkStepNamesUnicity(){ + protected void checkStepNamesUnicity() { } @Override diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java index e992855304..742975fd15 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java @@ -514,18 +514,18 @@ void testGetMultipleJobParameters() throws Exception { } @Test - public void testMultipleStepsWithSameName(){ + public void testMultipleStepsWithSameName() { job.setName("MultipleStepsWithSameName"); - String sharedName="stepName"; - final List executionsCallbacks=new ArrayList<>(); - StubStep sharedNameStep1=new StubStep(sharedName, jobRepository); - sharedNameStep1.setCallback(()->executionsCallbacks.add("step1")); + String sharedName = "stepName"; + final List executionsCallbacks = new ArrayList<>(); + StubStep sharedNameStep1 = new StubStep(sharedName, jobRepository); + sharedNameStep1.setCallback(() -> executionsCallbacks.add("step1")); job.addStep(sharedNameStep1); - StubStep sharedNameStep2=new StubStep(sharedName, jobRepository); - sharedNameStep2.setCallback(()->executionsCallbacks.add("step2")); + StubStep sharedNameStep2 = new StubStep(sharedName, jobRepository); + sharedNameStep2.setCallback(() -> executionsCallbacks.add("step2")); job.addStep(sharedNameStep2); - StubStep sharedNameStep3=new StubStep(sharedName, jobRepository); - sharedNameStep3.setCallback(()->executionsCallbacks.add("step3")); + StubStep sharedNameStep3 = new StubStep(sharedName, jobRepository); + sharedNameStep3.setCallback(() -> executionsCallbacks.add("step3")); job.addStep(sharedNameStep3); job.execute(jobExecution); assertEquals(List.of("step1", "step2", "step3"), executionsCallbacks); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java index 42c49fd3fc..e3d950df0c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/builder/FlowJobBuilderTests.java @@ -109,9 +109,9 @@ public void execute(StepExecution stepExecution) @BeforeEach void init() throws Exception { EmbeddedDatabase embeddedDatabase = new EmbeddedDatabaseBuilder() - .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") - .addScript("/org/springframework/batch/core/schema-hsqldb.sql") - .build(); + .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") + .addScript("/org/springframework/batch/core/schema-hsqldb.sql") + .build(); JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean(); factory.setDataSource(embeddedDatabase); factory.setTransactionManager(new JdbcTransactionManager(embeddedDatabase)); @@ -123,10 +123,10 @@ void init() throws Exception { @Test void testBuildOnOneLine() { FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(step1) - .on("COMPLETED") - .to(step2) - .end() - .preventRestart(); + .on("COMPLETED") + .to(step2) + .end() + .preventRestart(); builder.build().execute(execution); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); assertEquals(2, execution.getStepExecutions().size()); @@ -200,9 +200,9 @@ void testBuildSplitUsingStartAndAdd_BATCH_2346() { Flow subflow1 = new FlowBuilder("subflow1").from(step2).end(); Flow subflow2 = new FlowBuilder("subflow2").from(step3).end(); Flow splitflow = new FlowBuilder("splitflow").start(subflow1) - .split(new SimpleAsyncTaskExecutor()) - .add(subflow2) - .build(); + .split(new SimpleAsyncTaskExecutor()) + .add(subflow2) + .build(); FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(splitflow).end(); builder.preventRestart().build().execute(execution); @@ -215,8 +215,8 @@ void testBuildSplit_BATCH_2282() { Flow flow1 = new FlowBuilder("subflow1").from(step1).end(); Flow flow2 = new FlowBuilder("subflow2").from(step2).end(); Flow splitFlow = new FlowBuilder("splitflow").split(new SimpleAsyncTaskExecutor()) - .add(flow1, flow2) - .build(); + .add(flow1, flow2) + .build(); FlowJobBuilder builder = new JobBuilder("flow", jobRepository).start(splitFlow).end(); builder.preventRestart().build().execute(execution); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); @@ -363,15 +363,28 @@ void testBuildWithJobScopedStep() throws Exception { // https://github.com/spring-projects/spring-batch/issues/3757#issuecomment-1821593539 @Test - void testStepNamesMustBeUniqueWithinFlowDefinition() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { + void testStepNamesMustBeUniqueWithinFlowDefinition() throws JobInstanceAlreadyCompleteException, + JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException { ApplicationContext context = new AnnotationConfigApplicationContext(JobConfigurationForStepNameUnique.class); JobLauncher jobLauncher = context.getBean(JobLauncher.class); Job job = context.getBean(Job.class); - JobExecution jobExecution=jobLauncher.run(job, new JobParametersBuilder().addLong("random", 2L).addString("stepTwo.name", JobConfigurationForStepNameUnique.SHARED_NAME).toJobParameters()); - assertTrue(jobExecution.getAllFailureExceptions().stream().map(Object::getClass).anyMatch(AlreadyUsedStepNameException.class::equals)); + JobExecution jobExecution = jobLauncher.run(job, + new JobParametersBuilder().addLong("random", 2L) + .addString("stepTwo.name", JobConfigurationForStepNameUnique.SHARED_NAME) + .toJobParameters()); + assertTrue(jobExecution.getAllFailureExceptions() + .stream() + .map(Object::getClass) + .anyMatch(AlreadyUsedStepNameException.class::equals)); assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution.getExitStatus().getExitCode()); - jobExecution=jobLauncher.run(job, new JobParametersBuilder().addLong("random", 1L).addString("stepTwo.name", JobConfigurationForStepNameUnique.SHARED_NAME).toJobParameters()); - assertTrue(jobExecution.getAllFailureExceptions().stream().map(Object::getClass).anyMatch(AlreadyUsedStepNameException.class::equals)); + jobExecution = jobLauncher.run(job, + new JobParametersBuilder().addLong("random", 1L) + .addString("stepTwo.name", JobConfigurationForStepNameUnique.SHARED_NAME) + .toJobParameters()); + assertTrue(jobExecution.getAllFailureExceptions() + .stream() + .map(Object::getClass) + .anyMatch(AlreadyUsedStepNameException.class::equals)); assertEquals(ExitStatus.FAILED.getExitCode(), jobExecution.getExitStatus().getExitCode()); } @@ -383,72 +396,77 @@ static class JobConfigurationForStepNameUnique { private static final Log logger = LogFactory.getLog(FlowJobBuilderTests.class); - @Bean @JobScope public Step conditionalStep(JobRepository jobRepository, PlatformTransactionManager transactionManager, - @Value("#{jobParameters['random']}") Integer random) { - return new StepBuilder("conditionalStep", jobRepository).tasklet( - (StepContribution contribution, ChunkContext chunkContext) -> { - String exitStatus = (random % 2 == 0) ? "EVEN" : "ODD"; - logger.info("'conditionalStep' with exitStatus " + exitStatus); - contribution.setExitStatus(new ExitStatus(exitStatus)); - return RepeatStatus.FINISHED; - }, transactionManager - ).build(); + @Value("#{jobParameters['random']}") Integer random) { + return new StepBuilder("conditionalStep", jobRepository) + .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { + String exitStatus = (random % 2 == 0) ? "EVEN" : "ODD"; + logger.info("'conditionalStep' with exitStatus " + exitStatus); + contribution.setExitStatus(new ExitStatus(exitStatus)); + return RepeatStatus.FINISHED; + }, transactionManager) + .build(); } @Bean @JobScope public Step stepTwo(JobRepository jobRepository, PlatformTransactionManager transactionManager, - @Value("#{jobParameters['stepTwo.name']}") String name) { + @Value("#{jobParameters['stepTwo.name']}") String name) { return new StepBuilder(name, jobRepository) - .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { - logger.info("Hello from stepTwo"); - return RepeatStatus.FINISHED; - }, transactionManager) - .build(); + .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { + logger.info("Hello from stepTwo"); + return RepeatStatus.FINISHED; + }, transactionManager) + .build(); } @Bean public Step stepThree(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder(SHARED_NAME, jobRepository) - .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { - logger.info("Hello from stepThree"); - return RepeatStatus.FINISHED; - }, transactionManager) - .build(); + .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { + logger.info("Hello from stepThree"); + return RepeatStatus.FINISHED; + }, transactionManager) + .build(); } @Bean public Step stepFour(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder(SHARED_NAME, jobRepository) - .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { - logger.info("Hello from stepFour"); - return RepeatStatus.FINISHED; - }, transactionManager) - .build(); + .tasklet((StepContribution contribution, ChunkContext chunkContext) -> { + logger.info("Hello from stepFour"); + return RepeatStatus.FINISHED; + }, transactionManager) + .build(); } @Bean public Job job(JobRepository jobRepository, @Qualifier("conditionalStep") Step conditionalStep, - @Qualifier("stepFour") Step step4, @Qualifier("stepTwo") Step step2, - @Qualifier("stepThree") Step step3) { + @Qualifier("stepFour") Step step4, @Qualifier("stepTwo") Step step2, + @Qualifier("stepThree") Step step3) { JobBuilder jobBuilder = new JobBuilder("flow", jobRepository); return jobBuilder.start(conditionalStep) - .on("ODD").to(step2) - .from(conditionalStep).on("EVEN").to(step3) - .from(step3) - .next(step4) - .from(step2).next(step4).end().build(); + .on("ODD") + .to(step2) + .from(conditionalStep) + .on("EVEN") + .to(step3) + .from(step3) + .next(step4) + .from(step2) + .next(step4) + .end() + .build(); } @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder().addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") - .addScript("/org/springframework/batch/core/schema-hsqldb.sql") - .generateUniqueName(true) - .build(); + .addScript("/org/springframework/batch/core/schema-hsqldb.sql") + .generateUniqueName(true) + .build(); } @Bean @@ -456,10 +474,8 @@ public JdbcTransactionManager transactionManager(DataSource dataSource) { return new JdbcTransactionManager(dataSource); } - } - @EnableBatchProcessing @Configuration static class JobConfiguration { @@ -467,12 +483,12 @@ static class JobConfiguration { @Bean @JobScope public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager, - @Value("#{jobParameters['chunkSize']}") Integer chunkSize) { + @Value("#{jobParameters['chunkSize']}") Integer chunkSize) { return new StepBuilder("step", jobRepository).chunk(chunkSize, transactionManager) - .reader(new ListItemReader<>(Arrays.asList(1, 2, 3, 4))) - .writer(items -> { - }) - .build(); + .reader(new ListItemReader<>(Arrays.asList(1, 2, 3, 4))) + .writer(items -> { + }) + .build(); } @Bean @@ -484,9 +500,9 @@ public Job job(JobRepository jobRepository, PlatformTransactionManager transacti @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder().addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") - .addScript("/org/springframework/batch/core/schema-hsqldb.sql") - .generateUniqueName(true) - .build(); + .addScript("/org/springframework/batch/core/schema-hsqldb.sql") + .generateUniqueName(true) + .build(); } @Bean From f9172e27b67d1256492f5e1c371b26a077295c3f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 5 Feb 2024 15:52:44 +0100 Subject: [PATCH 007/152] Fix bean configuration in DefaultBatchConfiguration Before this commit, the dependency injection style used to define some infrastructure beans was incompatible with the bean method proxying being disabled. This commit fixes the issue by injecting dependencies through the parameters of bean definition methods. Resolves #4543 Signed-off-by: Fabrice Bibonne --- .../support/DefaultBatchConfiguration.java | 82 +++++++++++++++---- 1 file changed, 66 insertions(+), 16 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java index eee6738096..4789ccc264 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -52,7 +52,6 @@ import org.springframework.batch.item.database.support.DefaultDataFieldMaxValueIncrementerFactory; import org.springframework.batch.support.DatabaseType; 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.annotation.Bean; @@ -115,11 +114,8 @@ @Import(ScopeConfiguration.class) public class DefaultBatchConfiguration implements ApplicationContextAware { - @Autowired protected ApplicationContext applicationContext; - private final JobRegistry jobRegistry = new MapJobRegistry(); - @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; @@ -152,10 +148,28 @@ public JobRepository jobRepository() throws BatchConfigurationException { } } - @Bean + /** + * Define a job launcher. + * @return a job launcher + * @throws BatchConfigurationException if unable to configure the default job launcher + * @deprecated Since 5.2. Use {@link #jobLauncher(JobRepository)} instead + */ + @Deprecated(forRemoval = true) public JobLauncher jobLauncher() throws BatchConfigurationException { + return jobLauncher(jobRepository()); + } + + /** + * Define a job launcher bean. + * @param jobRepository the job repository + * @return a job launcher + * @throws BatchConfigurationException if unable to configure the default job launcher + * @since 5.2 + */ + @Bean + public JobLauncher jobLauncher(JobRepository jobRepository) throws BatchConfigurationException { TaskExecutorJobLauncher taskExecutorJobLauncher = new TaskExecutorJobLauncher(); - taskExecutorJobLauncher.setJobRepository(jobRepository()); + taskExecutorJobLauncher.setJobRepository(jobRepository); taskExecutorJobLauncher.setTaskExecutor(getTaskExecutor()); try { taskExecutorJobLauncher.afterPropertiesSet(); @@ -189,17 +203,40 @@ public JobExplorer jobExplorer() throws BatchConfigurationException { @Bean public JobRegistry jobRegistry() throws BatchConfigurationException { - return this.jobRegistry; // FIXME returning a new instance here does not work + return new MapJobRegistry(); } - @Bean + /** + * Define a job operator. + * @return a job operator + * @throws BatchConfigurationException if unable to configure the default job operator + * @deprecated Since 5.2. Use + * {@link #jobOperator(JobRepository, JobExplorer, JobRegistry, JobLauncher)} instead + */ + @Deprecated(forRemoval = true) public JobOperator jobOperator() throws BatchConfigurationException { + return jobOperator(jobRepository(), jobExplorer(), jobRegistry(), jobLauncher()); + } + + /** + * Define a job operator bean. + * @param jobRepository a job repository + * @param jobExplorer a job explorer + * @param jobRegistry a job registry + * @param jobLauncher a job launcher + * @return a job operator + * @throws BatchConfigurationException if unable to configure the default job operator + * @since 5.2 + */ + @Bean + public JobOperator jobOperator(JobRepository jobRepository, JobExplorer jobExplorer, JobRegistry jobRegistry, + JobLauncher jobLauncher) throws BatchConfigurationException { JobOperatorFactoryBean jobOperatorFactoryBean = new JobOperatorFactoryBean(); jobOperatorFactoryBean.setTransactionManager(getTransactionManager()); - jobOperatorFactoryBean.setJobRepository(jobRepository()); - jobOperatorFactoryBean.setJobExplorer(jobExplorer()); - jobOperatorFactoryBean.setJobRegistry(jobRegistry()); - jobOperatorFactoryBean.setJobLauncher(jobLauncher()); + jobOperatorFactoryBean.setJobRepository(jobRepository); + jobOperatorFactoryBean.setJobExplorer(jobExplorer); + jobOperatorFactoryBean.setJobRegistry(jobRegistry); + jobOperatorFactoryBean.setJobLauncher(jobLauncher); try { jobOperatorFactoryBean.afterPropertiesSet(); return jobOperatorFactoryBean.getObject(); @@ -209,16 +246,29 @@ public JobOperator jobOperator() throws BatchConfigurationException { } } + /** + * Defines a {@link JobRegistryBeanPostProcessor}. + * @return a {@link JobRegistryBeanPostProcessor} + * @throws BatchConfigurationException if unable to register the bean + * @since 5.1 + * @deprecated Use {@link #jobRegistryBeanPostProcessor(JobRegistry)} instead + */ + @Deprecated(forRemoval = true) + public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor() throws BatchConfigurationException { + return jobRegistryBeanPostProcessor(jobRegistry()); + } + /** * Defines a {@link JobRegistryBeanPostProcessor} bean. * @return a {@link JobRegistryBeanPostProcessor} bean * @throws BatchConfigurationException if unable to register the bean - * @since 5.1 + * @since 5.2 */ @Bean - public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor() throws BatchConfigurationException { + public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) + throws BatchConfigurationException { JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor(); - jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry()); + jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry); try { jobRegistryBeanPostProcessor.afterPropertiesSet(); return jobRegistryBeanPostProcessor; From 831b975e6efb6b7dc650dc8f0d7ce75202e1b007 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Mon, 18 Dec 2023 15:36:16 +0100 Subject: [PATCH 008/152] Add `JobRegistrySmartInitializingSingleton` Signed-off-by: Fabrice Bibonne --- .../support/JobRegistryBeanPostProcessor.java | 6 +- ...JobRegistrySmartInitializingSingleton.java | 174 ++++++++++++++++++ ...gistrySmartInitializingSingletonTests.java | 116 ++++++++++++ ...text-with-smart-initializing-singleton.xml | 76 ++++++++ 4 files changed, 371 insertions(+), 1 deletion(-) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingletonTests.java create mode 100644 spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/test-context-with-smart-initializing-singleton.xml diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java index 35625920f7..7b66601fb6 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -40,6 +40,10 @@ * {@link JobRegistry}. Include a bean of this type along with your job configuration and * use the same {@link JobRegistry} as a {@link JobLocator} when you need to locate a * {@link Job} to launch. + *

+ * An alternative to this class is {@link JobRegistrySmartInitializingSingleton}, which is + * recommended in cases where this class may cause early bean initializations. You must + * include at most one of either of them as a bean. * * @author Dave Syer * @author Mahmoud Ben Hassine diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java new file mode 100644 index 0000000000..9e4bbb3a4f --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java @@ -0,0 +1,174 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.configuration.support; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.DuplicateJobException; +import org.springframework.batch.core.configuration.JobLocator; +import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.beans.BeansException; +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.util.Assert; + +/** + * A {@link SmartInitializingSingleton} that registers {@link Job} beans with a + * {@link JobRegistry}. Include a bean of this type along with your job configuration and + * use the same {@link JobRegistry} as a {@link JobLocator} when you need to locate a + * {@link Job} to launch. + *

+ * This class is an alternative to {@link JobRegistryBeanPostProcessor} and prevents early + * bean initializations. You must include at most one of either of them as a bean. + * + * @author Henning Pöttker + * @since 5.1.1 + */ +public class JobRegistrySmartInitializingSingleton + implements SmartInitializingSingleton, BeanFactoryAware, InitializingBean, DisposableBean { + + private static final Log logger = LogFactory.getLog(JobRegistrySmartInitializingSingleton.class); + + // It doesn't make sense for this to have a default value... + private JobRegistry jobRegistry = null; + + private final Collection jobNames = new HashSet<>(); + + private String groupName = null; + + private ListableBeanFactory beanFactory; + + /** + * Default constructor. + */ + public JobRegistrySmartInitializingSingleton() { + } + + /** + * Convenience constructor for setting the {@link JobRegistry}. + * @param jobRegistry the {@link JobRegistry} to register the {@link Job}s with + */ + public JobRegistrySmartInitializingSingleton(JobRegistry jobRegistry) { + this.jobRegistry = jobRegistry; + } + + /** + * The group name for jobs registered by this component. Optional (defaults to null, + * which means that jobs are registered with their bean names). Useful where there is + * a hierarchy of application contexts all contributing to the same + * {@link JobRegistry}: child contexts can then define an instance with a unique group + * name to avoid clashes between job names. + * @param groupName the groupName to set + */ + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + /** + * Injection setter for {@link JobRegistry}. + * @param jobRegistry the {@link JobRegistry} to register the {@link Job}s with + */ + public void setJobRegistry(JobRegistry jobRegistry) { + this.jobRegistry = jobRegistry; + } + + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + if (beanFactory instanceof ListableBeanFactory listableBeanFactory) { + this.beanFactory = listableBeanFactory; + } + } + + /** + * Make sure the registry is set before use. + */ + @Override + public void afterPropertiesSet() throws Exception { + Assert.state(jobRegistry != null, "JobRegistry must not be null"); + } + + /** + * Unregister all the {@link Job} instances that were registered by this post + * processor. + */ + @Override + public void destroy() throws Exception { + for (String name : jobNames) { + if (logger.isDebugEnabled()) { + logger.debug("Unregistering job: " + name); + } + jobRegistry.unregister(name); + } + jobNames.clear(); + } + + @Override + public void afterSingletonsInstantiated() { + if (beanFactory == null) { + return; + } + Map jobs = beanFactory.getBeansOfType(Job.class, false, false); + for (var entry : jobs.entrySet()) { + postProcessAfterInitialization(entry.getValue(), entry.getKey()); + } + } + + private void postProcessAfterInitialization(Job job, String beanName) { + try { + String groupName = this.groupName; + if (beanFactory instanceof DefaultListableBeanFactory defaultListableBeanFactory + && beanFactory.containsBean(beanName)) { + groupName = getGroupName(defaultListableBeanFactory.getBeanDefinition(beanName), job); + } + job = groupName == null ? job : new GroupAwareJob(groupName, job); + ReferenceJobFactory jobFactory = new ReferenceJobFactory(job); + String name = jobFactory.getJobName(); + if (logger.isDebugEnabled()) { + logger.debug("Registering job: " + name); + } + jobRegistry.register(jobFactory); + jobNames.add(name); + } + catch (DuplicateJobException e) { + throw new FatalBeanException("Cannot register job configuration", e); + } + } + + /** + * Determine a group name for the job to be registered. The default implementation + * returns the {@link #setGroupName(String) groupName} configured. Provides an + * extension point for specialised subclasses. + * @param beanDefinition the bean definition for the job + * @param job the job + * @return a group name for the job (or null if not needed) + */ + protected String getGroupName(BeanDefinition beanDefinition, Job job) { + return groupName; + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingletonTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingletonTests.java new file mode 100644 index 0000000000..7738ee2d4f --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingletonTests.java @@ -0,0 +1,116 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.configuration.support; + +import java.util.Collection; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.DuplicateJobException; +import org.springframework.batch.core.configuration.JobRegistry; +import org.springframework.batch.core.job.JobSupport; +import org.springframework.beans.FatalBeanException; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; + +/** + * @author Henning Pöttker + */ +class JobRegistrySmartInitializingSingletonTests { + + private final JobRegistry jobRegistry = new MapJobRegistry(); + + private final JobRegistrySmartInitializingSingleton singleton = new JobRegistrySmartInitializingSingleton( + jobRegistry); + + private final ListableBeanFactory beanFactory = mock(ListableBeanFactory.class); + + @BeforeEach + void setUp() { + var job = new JobSupport(); + job.setName("foo"); + lenient().when(beanFactory.getBeansOfType(Job.class, false, false)).thenReturn(Map.of("bar", job)); + singleton.setBeanFactory(beanFactory); + } + + @Test + void testInitializationFails() { + singleton.setJobRegistry(null); + var exception = assertThrows(IllegalStateException.class, singleton::afterPropertiesSet); + assertTrue(exception.getMessage().contains("JobRegistry")); + } + + @Test + void testAfterSingletonsInstantiated() { + singleton.afterSingletonsInstantiated(); + assertEquals("[foo]", jobRegistry.getJobNames().toString()); + } + + @Test + void testAfterSingletonsInstantiatedWithGroupName() { + singleton.setGroupName("jobs"); + singleton.afterSingletonsInstantiated(); + assertEquals("[jobs.foo]", jobRegistry.getJobNames().toString()); + } + + @Test + void testAfterSingletonsInstantiatedWithDuplicate() { + singleton.afterSingletonsInstantiated(); + var exception = assertThrows(FatalBeanException.class, singleton::afterSingletonsInstantiated); + assertTrue(exception.getCause() instanceof DuplicateJobException); + } + + @Test + void testUnregisterOnDestroy() throws Exception { + singleton.afterSingletonsInstantiated(); + singleton.destroy(); + assertEquals("[]", jobRegistry.getJobNames().toString()); + } + + @Test + void testExecutionWithApplicationContext() throws Exception { + var context = new ClassPathXmlApplicationContext("test-context-with-smart-initializing-singleton.xml", + getClass()); + var registry = context.getBean("registry", JobRegistry.class); + Collection jobNames = registry.getJobNames(); + String[] names = context.getBeanNamesForType(JobSupport.class); + int count = names.length; + // Each concrete bean of type JobConfiguration is registered... + assertEquals(count, jobNames.size()); + // N.B. there is a failure / wonky mode where a parent bean is given an + // explicit name or beanName (using property setter): in this case then + // child beans will have the same name and will be re-registered (and + // override, if the registry supports that). + assertNotNull(registry.getJob("test-job")); + assertEquals(context.getBean("test-job-with-name"), registry.getJob("foo")); + assertEquals(context.getBean("test-job-with-bean-name"), registry.getJob("bar")); + assertEquals(context.getBean("test-job-with-parent-and-name"), registry.getJob("spam")); + assertEquals(context.getBean("test-job-with-parent-and-bean-name"), registry.getJob("bucket")); + assertEquals(context.getBean("test-job-with-concrete-parent"), registry.getJob("maps")); + assertEquals(context.getBean("test-job-with-concrete-parent-and-name"), registry.getJob("oof")); + assertEquals(context.getBean("test-job-with-concrete-parent-and-bean-name"), registry.getJob("rab")); + } + +} diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/test-context-with-smart-initializing-singleton.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/test-context-with-smart-initializing-singleton.xml new file mode 100644 index 0000000000..64ae6eed68 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/test-context-with-smart-initializing-singleton.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 31fad2f03749ddcad89f231c4272073743e106a2 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 6 Feb 2024 15:21:47 +0100 Subject: [PATCH 009/152] Refine contribution #4521 - Update reference documentation - Minor test updates Signed-off-by: Fabrice Bibonne --- ...JobRegistrySmartInitializingSingleton.java | 4 +- ...gistrySmartInitializingSingletonTests.java | 16 +++++--- .../ROOT/pages/job/advanced-meta-data.adoc | 40 +++++++++++++++++-- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java index 9e4bbb3a4f..ede418cf23 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingleton.java @@ -113,8 +113,8 @@ public void afterPropertiesSet() throws Exception { } /** - * Unregister all the {@link Job} instances that were registered by this post - * processor. + * Unregister all the {@link Job} instances that were registered by this smart + * initializing singleton. */ @Override public void destroy() throws Exception { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingletonTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingletonTests.java index 7738ee2d4f..f6db1e0187 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingletonTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/JobRegistrySmartInitializingSingletonTests.java @@ -29,6 +29,7 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -37,6 +38,7 @@ /** * @author Henning Pöttker + * @author Mahmoud Ben Hassine */ class JobRegistrySmartInitializingSingletonTests { @@ -59,34 +61,38 @@ void setUp() { void testInitializationFails() { singleton.setJobRegistry(null); var exception = assertThrows(IllegalStateException.class, singleton::afterPropertiesSet); - assertTrue(exception.getMessage().contains("JobRegistry")); + assertEquals("JobRegistry must not be null", exception.getMessage()); } @Test void testAfterSingletonsInstantiated() { singleton.afterSingletonsInstantiated(); - assertEquals("[foo]", jobRegistry.getJobNames().toString()); + Collection jobNames = jobRegistry.getJobNames(); + assertEquals(1, jobNames.size()); + assertEquals("foo", jobNames.iterator().next()); } @Test void testAfterSingletonsInstantiatedWithGroupName() { singleton.setGroupName("jobs"); singleton.afterSingletonsInstantiated(); - assertEquals("[jobs.foo]", jobRegistry.getJobNames().toString()); + Collection jobNames = jobRegistry.getJobNames(); + assertEquals(1, jobNames.size()); + assertEquals("jobs.foo", jobNames.iterator().next()); } @Test void testAfterSingletonsInstantiatedWithDuplicate() { singleton.afterSingletonsInstantiated(); var exception = assertThrows(FatalBeanException.class, singleton::afterSingletonsInstantiated); - assertTrue(exception.getCause() instanceof DuplicateJobException); + assertInstanceOf(DuplicateJobException.class, exception.getCause()); } @Test void testUnregisterOnDestroy() throws Exception { singleton.afterSingletonsInstantiated(); singleton.destroy(); - assertEquals("[]", jobRegistry.getJobNames().toString()); + assertTrue(jobRegistry.getJobNames().isEmpty()); } @Test diff --git a/spring-batch-docs/modules/ROOT/pages/job/advanced-meta-data.adoc b/spring-batch-docs/modules/ROOT/pages/job/advanced-meta-data.adoc index 94fc236f5c..bd41a5d941 100644 --- a/spring-batch-docs/modules/ROOT/pages/job/advanced-meta-data.adoc +++ b/spring-batch-docs/modules/ROOT/pages/job/advanced-meta-data.adoc @@ -173,9 +173,9 @@ The following example shows how to include a `JobRegistry` for a job defined in ==== -You can populate a `JobRegistry` in either of two ways: by using -a bean post processor or by using a registrar lifecycle component. The coming -sections describe these two mechanisms. +You can populate a `JobRegistry` in one of the following ways: by using +a bean post processor, or by using a smart initializing singleton or by using +a registrar lifecycle component. The coming sections describe these mechanisms. [[jobregistrybeanpostprocessor]] === JobRegistryBeanPostProcessor @@ -224,6 +224,40 @@ there to also be registered automatically. As of version 5.1, the `@EnableBatchProcessing` annotation automatically registers a `jobRegistryBeanPostProcessor` bean in the application context. +[[jobregistrysmartinitializingsingleton]] +=== JobRegistrySmartInitializingSingleton + +This is a `SmartInitializingSingleton` that registers all singleton jobs within the job registry. + +[tabs] +==== +Java:: ++ +The following example shows how to define a `JobRegistrySmartInitializingSingleton` in Java: ++ +.Java Configuration +[source, java] +---- +@Bean +public JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton(JobRegistry jobRegistry) { + return new JobRegistrySmartInitializingSingleton(jobRegistry); +} +---- + +XML:: ++ +The following example shows how to define a `JobRegistrySmartInitializingSingleton` in XML: ++ +.XML Configuration +[source, xml] +---- + + + +---- + +==== + [[automaticjobregistrar]] === AutomaticJobRegistrar From 6788c964ae0cd34273d50bad47a9ff801dbce2d7 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 9 Feb 2024 10:16:25 +0100 Subject: [PATCH 010/152] Update antora-extensions to v1.8.2 Issue #4534 Signed-off-by: Fabrice Bibonne --- spring-batch-docs/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index 2dee8bc5d2..0768fac6c7 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -26,7 +26,7 @@ @antora/atlas-extension@1.0.0-alpha.1 @antora/collector-extension@1.0.0-alpha.3 @asciidoctor/tabs@1.0.0-beta.3 - @springio/antora-extensions@1.7.0 + @springio/antora-extensions@1.8.2 @springio/asciidoctor-extensions@1.0.0-alpha.9 From 1962c7ca3f50fa65350f3c59ff4326124a45b7fa Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 9 Feb 2024 10:20:04 +0100 Subject: [PATCH 011/152] Exclude slf4j-api transitive dependency from sqlite-jdbc Signed-off-by: Fabrice Bibonne --- spring-batch-core/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 3de563e6e4..af28f85e8d 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -199,6 +199,12 @@ sqlite-jdbc ${sqlite.version} test + + + org.slf4j + slf4j-api + + com.h2database From f1ad1129a3e3941166f89710c6294b06fe54f70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B8rge=20Andersen?= Date: Fri, 5 Jan 2024 16:45:10 +0100 Subject: [PATCH 012/152] Fix javadoc referencing Long when it should be Double Resolves #4526 Signed-off-by: Fabrice Bibonne --- .../main/java/org/springframework/batch/core/JobParameters.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java index 36cc3a1d44..8f001e84c7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java @@ -141,7 +141,7 @@ public String getString(String key, @Nullable String defaultValue) { } /** - * Typesafe getter for the {@link Long} represented by the provided key. + * Typesafe getter for the {@link Double} represented by the provided key. * @param key The key for which to get a value. * @return The {@link Double} value or {@code null} if the key is absent. */ From ca5959bcf585d4d8f3fb38ddb9bd077630be5405 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Thu, 30 Nov 2023 10:43:00 +0100 Subject: [PATCH 013/152] Check dirty flag of step execution context before update in inner loop Signed-off-by: Fabrice Bibonne --- .../springframework/batch/core/step/tasklet/TaskletStep.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java index 8b2cf1e1b8..aaf192beb2 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java @@ -425,7 +425,9 @@ public RepeatStatus doInTransaction(TransactionStatus status) { try { // Going to attempt a commit. If it fails this flag will // stay false and we can use that later. - getJobRepository().updateExecutionContext(stepExecution); + if (stepExecution.getExecutionContext().isDirty()) { + getJobRepository().updateExecutionContext(stepExecution); + } stepExecution.incrementCommitCount(); if (logger.isDebugEnabled()) { logger.debug("Saving step execution before commit: " + stepExecution); From 0fe08264a495857acb2a35b3b7101e8c0a08a225 Mon Sep 17 00:00:00 2001 From: Taeik Lim Date: Tue, 6 Feb 2024 13:21:07 +0900 Subject: [PATCH 014/152] Remove static imports in production code Signed-off-by: Taeik Lim Signed-off-by: Fabrice Bibonne --- .../core/listener/AbstractListenerFactoryBean.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java index c4b6bd9f7c..18c9e4cf2e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/AbstractListenerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 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. @@ -35,9 +35,6 @@ import org.springframework.core.Ordered; import org.springframework.util.Assert; -import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerByAnnotation; -import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerForInterface; - /** * {@link FactoryBean} implementation that builds a listener based on the various * lifecycle methods or annotations that are provided. There are three possible ways of @@ -61,6 +58,7 @@ * * @author Lucas Ward * @author Dan Garrette + * @author Taeik Lim * @since 2.0 * @see ListenerMetaData */ @@ -98,8 +96,8 @@ public Object getObject() { Set invokers = new HashSet<>(); MethodInvoker invoker; - invoker = getMethodInvokerForInterface(metaData.getListenerInterface(), metaData.getMethodName(), delegate, - metaData.getParamTypes()); + invoker = MethodInvokerUtils.getMethodInvokerForInterface(metaData.getListenerInterface(), + metaData.getMethodName(), delegate, metaData.getParamTypes()); if (invoker != null) { invokers.add(invoker); } @@ -111,7 +109,8 @@ public Object getObject() { } if (metaData.getAnnotation() != null) { - invoker = getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate, metaData.getParamTypes()); + invoker = MethodInvokerUtils.getMethodInvokerByAnnotation(metaData.getAnnotation(), delegate, + metaData.getParamTypes()); if (invoker != null) { invokers.add(invoker); synthetic = true; From 058d66c4c34dc9e1a0c067bcd71f376be82e68a8 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 14 Feb 2024 15:01:59 +0100 Subject: [PATCH 015/152] Fix exception when parsing empty values in DefaultJobParametersConverter Resolves #4505 Signed-off-by: Fabrice Bibonne --- .../DefaultJobParametersConverter.java | 8 ++++++-- .../DefaultJobParametersConverterTests.java | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/DefaultJobParametersConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/DefaultJobParametersConverter.java index 522175f62c..a9f671ca56 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/converter/DefaultJobParametersConverter.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/converter/DefaultJobParametersConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -169,7 +169,11 @@ protected JobParameter decode(String encodedJobParameter) { } private String parseValue(String encodedJobParameter) { - return StringUtils.commaDelimitedListToStringArray(encodedJobParameter)[0]; + String[] tokens = StringUtils.commaDelimitedListToStringArray(encodedJobParameter); + if (tokens.length == 0) { + return ""; + } + return tokens[0]; } private Class parseType(String encodedJobParameter) { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/converter/DefaultJobParametersConverterTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/converter/DefaultJobParametersConverterTests.java index e413162a41..d39b9ad5eb 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/converter/DefaultJobParametersConverterTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/converter/DefaultJobParametersConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; +import org.springframework.batch.core.JobParameter; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.JobParametersBuilder; import org.springframework.util.StringUtils; @@ -129,6 +130,22 @@ void testGetParametersWithBogusLong() { } } + @Test + void testGetParametersWithEmptyValue() { + // given + String[] args = new String[] { "parameter=" }; + + // when + JobParameters jobParameters = factory.getJobParameters(StringUtils.splitArrayElementsIntoProperties(args, "=")); + + // then + assertEquals(1, jobParameters.getParameters().size()); + JobParameter parameter = jobParameters.getParameters().get("parameter"); + assertEquals("", parameter.getValue()); + assertEquals(String.class, parameter.getType()); + assertTrue(parameter.isIdentifying()); + } + @Test void testGetParametersWithDoubleValueDeclaredAsLong() { From 9f29b90d9f733c78a2cdeb23ec55970fa3717d85 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Tue, 21 Nov 2023 23:31:13 +0100 Subject: [PATCH 016/152] Let SimpleJobRepository#deleteJobInstance delete corresponding step executions Resolves #4382 Signed-off-by: Fabrice Bibonne --- .../support/SimpleJobRepository.java | 4 +- .../SimpleJobRepositoryIntegrationTests.java | 63 ++++++++----------- 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java index 44f8bf6eca..e98752c987 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/SimpleJobRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -328,7 +328,7 @@ public void deleteJobExecution(JobExecution jobExecution) { @Override public void deleteJobInstance(JobInstance jobInstance) { - List jobExecutions = this.jobExecutionDao.findJobExecutions(jobInstance); + List jobExecutions = findJobExecutions(jobInstance); for (JobExecution jobExecution : jobExecutions) { deleteJobExecution(jobExecution); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryIntegrationTests.java index 53c33b178e..eb81dbd246 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/SimpleJobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2022 the original author or authors. + * Copyright 2008-2024 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. @@ -32,12 +32,13 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; -import java.util.Arrays; +import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.fail; /** * Repository tests using JDBC DAOs (rather than mocks). @@ -152,11 +153,7 @@ void testGetStepExecutionCountAndLastStepExecution() throws Exception { @Transactional @Test void testSaveExecutionContext() throws Exception { - ExecutionContext ctx = new ExecutionContext() { - { - putLong("crashedPosition", 7); - } - }; + ExecutionContext ctx = new ExecutionContext(Map.of("crashedPosition", 7)); JobExecution jobExec = jobRepository.createJobExecution(job.getName(), jobParameters); jobExec.setStartTime(LocalDateTime.now()); jobExec.setExecutionContext(ctx); @@ -169,11 +166,6 @@ void testSaveExecutionContext() throws Exception { StepExecution retrievedStepExec = jobRepository.getLastStepExecution(jobExec.getJobInstance(), step.getName()); assertEquals(stepExec, retrievedStepExec); assertEquals(ctx, retrievedStepExec.getExecutionContext()); - - // JobExecution retrievedJobExec = - // jobRepository.getLastJobExecution(jobExec.getJobInstance()); - // assertEquals(jobExec, retrievedJobExec); - // assertEquals(ctx, retrievedJobExec.getExecutionContext()); } /* @@ -205,7 +197,7 @@ void testGetLastJobExecution() throws Exception { jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters); StepExecution stepExecution = new StepExecution("step1", jobExecution); jobRepository.add(stepExecution); - jobExecution.addStepExecutions(Arrays.asList(stepExecution)); + jobExecution.addStepExecutions(List.of(stepExecution)); assertEquals(jobExecution, jobRepository.getLastJobExecution(job.getName(), jobParameters)); assertEquals(stepExecution, jobExecution.getStepExecutions().iterator().next()); } @@ -233,42 +225,41 @@ void testReExecuteWithSameJobParameters() throws Exception { */ @Transactional @Test - public void testReExecuteWithSameJobParametersWhenRunning() throws Exception { + void testReExecuteWithSameJobParametersWhenRunning() throws Exception { JobParameters jobParameters = new JobParametersBuilder().addString("stringKey", "stringValue") .toJobParameters(); // jobExecution with status STARTING JobExecution jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters); - try { - jobRepository.createJobExecution(job.getName(), jobParameters); - fail(); - } - catch (JobExecutionAlreadyRunningException e) { - // expected - } + assertThrows(JobExecutionAlreadyRunningException.class, + () -> jobRepository.createJobExecution(job.getName(), jobParameters)); // jobExecution with status STARTED jobExecution.setStatus(BatchStatus.STARTED); jobExecution.setStartTime(LocalDateTime.now()); jobRepository.update(jobExecution); - try { - jobRepository.createJobExecution(job.getName(), jobParameters); - fail(); - } - catch (JobExecutionAlreadyRunningException e) { - // expected - } + assertThrows(JobExecutionAlreadyRunningException.class, + () -> jobRepository.createJobExecution(job.getName(), jobParameters)); // jobExecution with status STOPPING jobExecution.setStatus(BatchStatus.STOPPING); jobRepository.update(jobExecution); - try { - jobRepository.createJobExecution(job.getName(), jobParameters); - fail(); - } - catch (JobExecutionAlreadyRunningException e) { - // expected - } + assertThrows(JobExecutionAlreadyRunningException.class, + () -> jobRepository.createJobExecution(job.getName(), jobParameters)); + } + + @Transactional + @Test + void testDeleteJobInstance() throws Exception { + var jobParameters = new JobParametersBuilder().addString("foo", "bar").toJobParameters(); + var jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters); + var stepExecution = new StepExecution("step", jobExecution); + jobRepository.add(stepExecution); + + jobRepository.deleteJobInstance(jobExecution.getJobInstance()); + + assertEquals(0, jobRepository.findJobInstancesByName(job.getName(), 0, 1).size()); + assertNull(jobRepository.getLastJobExecution(job.getName(), jobParameters)); } } From d8ee739ef54f0a433693f8a0f1e7ec12d6aca9ca Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 19 Feb 2024 14:16:43 +0100 Subject: [PATCH 017/152] Fix deprecations in MongoPagingItemReader Resolves #4552 Signed-off-by: Fabrice Bibonne --- .../item/data/MongoPagingItemReader.java | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoPagingItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoPagingItemReader.java index 442d6956e2..5c2278cacc 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoPagingItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoPagingItemReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -15,8 +15,14 @@ */ package org.springframework.batch.item.data; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemReader; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.Query; import org.springframework.util.ClassUtils; @@ -70,4 +76,69 @@ public MongoPagingItemReader() { setName(ClassUtils.getShortName(MongoPagingItemReader.class)); } + @Override + public void setTemplate(MongoOperations template) { + super.setTemplate(template); + } + + @Override + public void setQuery(Query query) { + super.setQuery(query); + } + + @Override + public void setQuery(String queryString) { + super.setQuery(queryString); + } + + @Override + public void setTargetType(Class type) { + super.setTargetType(type); + } + + @Override + public void setParameterValues(List parameterValues) { + super.setParameterValues(parameterValues); + } + + @Override + public void setFields(String fields) { + super.setFields(fields); + } + + @Override + public void setSort(Map sorts) { + super.setSort(sorts); + } + + @Override + public void setCollection(String collection) { + super.setCollection(collection); + } + + @Override + public void setHint(String hint) { + super.setHint(hint); + } + + @Override + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + } + + @Override + protected Iterator doPageRead() { + return super.doPageRead(); + } + + @Override + protected String replacePlaceholders(String input, List values) { + return super.replacePlaceholders(input, values); + } + + @Override + protected Sort convertToSort(Map sorts) { + return super.convertToSort(sorts); + } + } From 2fba53250a801c498c69228e5119cdc01510b29c Mon Sep 17 00:00:00 2001 From: Ilpyo-Yang Date: Mon, 23 Oct 2023 23:32:12 +0900 Subject: [PATCH 018/152] Add AbstractTaskletStepBuilder copy constructor This commit includes tests for the copy constructor of AbstractTaskletStepBuilder and for the faultTolerant method, specifically after taskExecutor has been set. Signed-off-by: Fabrice Bibonne --- .../builder/AbstractTaskletStepBuilder.java | 18 +++ .../core/step/builder/SimpleStepBuilder.java | 1 - .../test/AbstractTaskletStepBuilderTests.java | 114 ++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 spring-batch-test/src/test/java/org/springframework/batch/test/AbstractTaskletStepBuilderTests.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java index 05d9f13906..36ce5918a6 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java @@ -49,6 +49,7 @@ * @author Dave Syer * @author Michael Minella * @author Mahmoud Ben Hassine + * @author Ilpyo Yang * @since 2.2 * @param the type of builder represented */ @@ -74,6 +75,23 @@ public AbstractTaskletStepBuilder(StepBuilderHelper parent) { super(parent); } + /** + * Create a new builder initialized with any properties in the parent. The parent is + * copied, so it can be re-used. + * @param parent a parent helper containing common step properties + */ + public AbstractTaskletStepBuilder(AbstractTaskletStepBuilder parent) { + super(parent); + this.chunkListeners = parent.chunkListeners; + this.stepOperations = parent.stepOperations; + this.transactionManager = parent.transactionManager; + this.transactionAttribute = parent.transactionAttribute; + this.streams.addAll(parent.streams); + this.exceptionHandler = parent.exceptionHandler; + this.throttleLimit = parent.throttleLimit; + this.taskExecutor = parent.taskExecutor; + } + protected abstract Tasklet createTasklet(); /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java index aa51c34e54..088b17ca5f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java @@ -114,7 +114,6 @@ protected SimpleStepBuilder(SimpleStepBuilder parent) { this.itemListeners = parent.itemListeners; this.readerTransactionalQueue = parent.readerTransactionalQueue; this.meterRegistry = parent.meterRegistry; - this.transactionManager(parent.getTransactionManager()); } public FaultTolerantStepBuilder faultTolerant() { diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractTaskletStepBuilderTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractTaskletStepBuilderTests.java new file mode 100644 index 0000000000..465d6f5f5b --- /dev/null +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractTaskletStepBuilderTests.java @@ -0,0 +1,114 @@ +package org.springframework.batch.test; +/* + * Copyright 2020-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.AbstractTaskletStepBuilder; +import org.springframework.batch.core.step.builder.SimpleStepBuilder; +import org.springframework.batch.core.step.builder.StepBuilderHelper; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.ItemWriter; +import org.springframework.batch.test.context.SpringBatchTest; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +/** + * Test cases for verifying the {@link AbstractTaskletStepBuilder} and faultTolerant() functionality. + * + * @author Ilpyo Yang + */ +@SpringBatchTest +@SpringJUnitConfig +public class AbstractTaskletStepBuilderTests { + private final JobRepository jobRepository = mock(JobRepository.class); + private final int chunkSize = 10; + private final ItemReader itemReader = mock(ItemReader.class); + private final ItemProcessor itemProcessor = mock(ItemProcessor.class); + private final ItemWriter itemWriter = mock(ItemWriter.class); + private final SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); + SimpleStepBuilder simpleStepBuilder; + + private T accessPrivateField(Object o, String fieldName) throws ReflectiveOperationException { + Field field = o.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(o); + } + + private T accessSuperClassPrivateField(Object o, String fieldName) throws ReflectiveOperationException { + Field field = o.getClass().getSuperclass().getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(o); + } + + @BeforeEach + void set(){ + StepBuilderHelper stepBuilderHelper = new StepBuilderHelper("test", jobRepository) { + @Override + protected StepBuilderHelper self() { + return null; + } + }; + simpleStepBuilder = new SimpleStepBuilder(stepBuilderHelper); + simpleStepBuilder.chunk(chunkSize); + simpleStepBuilder.reader(itemReader); + simpleStepBuilder.processor(itemProcessor); + simpleStepBuilder.writer(itemWriter); + } + + @Test + void copyConstractorTest() throws ReflectiveOperationException { + Constructor constructor = SimpleStepBuilder.class.getDeclaredConstructor(SimpleStepBuilder.class); + constructor.setAccessible(true); + SimpleStepBuilder copySimpleStepBuilder = constructor.newInstance(simpleStepBuilder); + + int copyChunkSize = accessPrivateField(copySimpleStepBuilder, "chunkSize"); + ItemReader copyItemReader = accessPrivateField(copySimpleStepBuilder, "reader"); + ItemProcessor copyItemProcessor = accessPrivateField(copySimpleStepBuilder, "processor"); + ItemWriter copyItemWriter = accessPrivateField(copySimpleStepBuilder, "writer"); + + assertEquals(chunkSize, copyChunkSize); + assertEquals(itemReader, copyItemReader); + assertEquals(itemProcessor, copyItemProcessor); + assertEquals(itemWriter, copyItemWriter); + } + + @Test + void faultTolerantMethodTest() throws ReflectiveOperationException { + simpleStepBuilder.taskExecutor(taskExecutor); // The task executor is set before faultTolerant() + simpleStepBuilder.faultTolerant(); + + int afterChunkSize = accessPrivateField(simpleStepBuilder, "chunkSize"); + ItemReader afterItemReader = accessPrivateField(simpleStepBuilder, "reader"); + ItemProcessor afterItemProcessor = accessPrivateField(simpleStepBuilder, "processor"); + ItemWriter afterItemWriter = accessPrivateField(simpleStepBuilder, "writer"); + TaskExecutor afterTaskExecutor = accessSuperClassPrivateField(simpleStepBuilder, "taskExecutor"); + + assertEquals(chunkSize, afterChunkSize); + assertEquals(itemReader, afterItemReader); + assertEquals(itemProcessor, afterItemProcessor); + assertEquals(itemWriter, afterItemWriter); + assertEquals(taskExecutor, afterTaskExecutor); + } +} From bfa15162bab19ec6e36bcda924aa4a41e7169139 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 19 Feb 2024 15:23:50 +0100 Subject: [PATCH 019/152] Refine contribution #4471 - Update tests - Move test class to the core module Signed-off-by: Fabrice Bibonne --- .../AbstractTaskletStepBuilderTests.java | 88 ++++++++++++++ .../test/AbstractTaskletStepBuilderTests.java | 114 ------------------ 2 files changed, 88 insertions(+), 114 deletions(-) create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilderTests.java delete mode 100644 spring-batch-test/src/test/java/org/springframework/batch/test/AbstractTaskletStepBuilderTests.java diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilderTests.java new file mode 100644 index 0000000000..6cd6f2374e --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilderTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.step.builder; + +import org.junit.jupiter.api.Test; + +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.tasklet.TaskletStep; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.batch.item.ItemReader; +import org.springframework.batch.item.ItemWriter; +import org.springframework.batch.repeat.support.TaskExecutorRepeatTemplate; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.transaction.PlatformTransactionManager; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.mockito.Mockito.mock; + +/** + * Test cases for verifying the {@link AbstractTaskletStepBuilder} and faultTolerant() + * functionality. + * + * Issue: https://github.com/spring-projects/spring-batch/issues/4438 + * + * @author Ilpyo Yang + * @author Mahmoud Ben Hassine + */ +public class AbstractTaskletStepBuilderTests { + + private final JobRepository jobRepository = mock(JobRepository.class); + + private final PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class); + + private final int chunkSize = 10; + + private final ItemReader itemReader = mock(ItemReader.class); + + private final ItemProcessor itemProcessor = mock(ItemProcessor.class); + + private final ItemWriter itemWriter = mock(ItemWriter.class); + + private final SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); + + @Test + void testSetTaskExecutorBeforeFaultTolerant() { + TaskletStep step = new StepBuilder("step-name", jobRepository) + .chunk(chunkSize, transactionManager) + .taskExecutor(taskExecutor) + .reader(itemReader) + .processor(itemProcessor) + .writer(itemWriter) + .faultTolerant() + .build(); + + Object stepOperations = ReflectionTestUtils.getField(step, "stepOperations"); + assertInstanceOf(TaskExecutorRepeatTemplate.class, stepOperations); + } + + @Test + void testSetTaskExecutorAfterFaultTolerant() { + TaskletStep step = new StepBuilder("step-name", jobRepository) + .chunk(chunkSize, transactionManager) + .reader(itemReader) + .processor(itemProcessor) + .writer(itemWriter) + .faultTolerant() + .taskExecutor(taskExecutor) + .build(); + + Object stepOperations = ReflectionTestUtils.getField(step, "stepOperations"); + assertInstanceOf(TaskExecutorRepeatTemplate.class, stepOperations); + } + +} diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractTaskletStepBuilderTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractTaskletStepBuilderTests.java deleted file mode 100644 index 465d6f5f5b..0000000000 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/AbstractTaskletStepBuilderTests.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.springframework.batch.test; -/* - * Copyright 2020-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.step.builder.AbstractTaskletStepBuilder; -import org.springframework.batch.core.step.builder.SimpleStepBuilder; -import org.springframework.batch.core.step.builder.StepBuilderHelper; -import org.springframework.batch.item.ItemProcessor; -import org.springframework.batch.item.ItemReader; -import org.springframework.batch.item.ItemWriter; -import org.springframework.batch.test.context.SpringBatchTest; -import org.springframework.core.task.SimpleAsyncTaskExecutor; -import org.springframework.core.task.TaskExecutor; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -/** - * Test cases for verifying the {@link AbstractTaskletStepBuilder} and faultTolerant() functionality. - * - * @author Ilpyo Yang - */ -@SpringBatchTest -@SpringJUnitConfig -public class AbstractTaskletStepBuilderTests { - private final JobRepository jobRepository = mock(JobRepository.class); - private final int chunkSize = 10; - private final ItemReader itemReader = mock(ItemReader.class); - private final ItemProcessor itemProcessor = mock(ItemProcessor.class); - private final ItemWriter itemWriter = mock(ItemWriter.class); - private final SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(); - SimpleStepBuilder simpleStepBuilder; - - private T accessPrivateField(Object o, String fieldName) throws ReflectiveOperationException { - Field field = o.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - return (T) field.get(o); - } - - private T accessSuperClassPrivateField(Object o, String fieldName) throws ReflectiveOperationException { - Field field = o.getClass().getSuperclass().getDeclaredField(fieldName); - field.setAccessible(true); - return (T) field.get(o); - } - - @BeforeEach - void set(){ - StepBuilderHelper stepBuilderHelper = new StepBuilderHelper("test", jobRepository) { - @Override - protected StepBuilderHelper self() { - return null; - } - }; - simpleStepBuilder = new SimpleStepBuilder(stepBuilderHelper); - simpleStepBuilder.chunk(chunkSize); - simpleStepBuilder.reader(itemReader); - simpleStepBuilder.processor(itemProcessor); - simpleStepBuilder.writer(itemWriter); - } - - @Test - void copyConstractorTest() throws ReflectiveOperationException { - Constructor constructor = SimpleStepBuilder.class.getDeclaredConstructor(SimpleStepBuilder.class); - constructor.setAccessible(true); - SimpleStepBuilder copySimpleStepBuilder = constructor.newInstance(simpleStepBuilder); - - int copyChunkSize = accessPrivateField(copySimpleStepBuilder, "chunkSize"); - ItemReader copyItemReader = accessPrivateField(copySimpleStepBuilder, "reader"); - ItemProcessor copyItemProcessor = accessPrivateField(copySimpleStepBuilder, "processor"); - ItemWriter copyItemWriter = accessPrivateField(copySimpleStepBuilder, "writer"); - - assertEquals(chunkSize, copyChunkSize); - assertEquals(itemReader, copyItemReader); - assertEquals(itemProcessor, copyItemProcessor); - assertEquals(itemWriter, copyItemWriter); - } - - @Test - void faultTolerantMethodTest() throws ReflectiveOperationException { - simpleStepBuilder.taskExecutor(taskExecutor); // The task executor is set before faultTolerant() - simpleStepBuilder.faultTolerant(); - - int afterChunkSize = accessPrivateField(simpleStepBuilder, "chunkSize"); - ItemReader afterItemReader = accessPrivateField(simpleStepBuilder, "reader"); - ItemProcessor afterItemProcessor = accessPrivateField(simpleStepBuilder, "processor"); - ItemWriter afterItemWriter = accessPrivateField(simpleStepBuilder, "writer"); - TaskExecutor afterTaskExecutor = accessSuperClassPrivateField(simpleStepBuilder, "taskExecutor"); - - assertEquals(chunkSize, afterChunkSize); - assertEquals(itemReader, afterItemReader); - assertEquals(itemProcessor, afterItemProcessor); - assertEquals(itemWriter, afterItemWriter); - assertEquals(taskExecutor, afterTaskExecutor); - } -} From 2bed698d28eb25168ad75fe66f8c6d5276afb109 Mon Sep 17 00:00:00 2001 From: Mario Petrovski Date: Wed, 28 Feb 2024 13:58:38 +0100 Subject: [PATCH 020/152] Update deprecated code that uses StepExecutionListenerSupport Closes #4538 Signed-off-by: Fabrice Bibonne --- .../modules/ROOT/pages/step/controlling-flow.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/step/controlling-flow.adoc b/spring-batch-docs/modules/ROOT/pages/step/controlling-flow.adoc index 7d3e70ab23..03670bc31b 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/controlling-flow.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/controlling-flow.adoc @@ -294,14 +294,14 @@ the condition of the execution having skipped records, as the following example [source, java] ---- -public class SkipCheckingListener extends StepExecutionListenerSupport { +public class SkipCheckingListener implements StepExecutionListener { + @Override public ExitStatus afterStep(StepExecution stepExecution) { String exitCode = stepExecution.getExitStatus().getExitCode(); if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) && - stepExecution.getSkipCount() > 0) { + stepExecution.getSkipCount() > 0) { return new ExitStatus("COMPLETED WITH SKIPS"); - } - else { + } else { return null; } } From 65b85fc2ee4e1eea0a777b0a56036b48af4be203 Mon Sep 17 00:00:00 2001 From: Taeik Lim Date: Tue, 6 Feb 2024 13:00:15 +0900 Subject: [PATCH 021/152] Make util classes to follow style guide Signed-off-by: Taeik Lim Issue #4545 Signed-off-by: Fabrice Bibonne --- .../batch/core/configuration/xml/BeanDefinitionUtils.java | 8 ++++++-- .../batch/core/configuration/xml/CoreNamespaceUtils.java | 8 ++++++-- .../batch/item/database/JdbcParameterUtils.java | 8 ++++++-- .../batch/item/database/support/SqlPagingQueryUtils.java | 8 ++++++-- .../org/springframework/batch/item/util/FileUtils.java | 5 +++-- .../springframework/batch/support/MethodInvokerUtils.java | 8 ++++++-- .../springframework/batch/support/ReflectionUtils.java | 5 +++-- .../batch/test/ExecutionContextTestUtils.java | 8 ++++++-- .../org/springframework/batch/test/JobScopeTestUtils.java | 8 ++++++-- .../springframework/batch/test/StepScopeTestUtils.java | 8 ++++++-- 10 files changed, 54 insertions(+), 20 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/BeanDefinitionUtils.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/BeanDefinitionUtils.java index 336bd43961..f2711b24b1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/BeanDefinitionUtils.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/BeanDefinitionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 the original author or authors. + * Copyright 2006-2024 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. @@ -21,9 +21,13 @@ /** * @author Dan Garrette + * @author Taeik Lim * @since 2.0.1 */ -public class BeanDefinitionUtils { +public abstract class BeanDefinitionUtils { + + private BeanDefinitionUtils() { + } /** * @param beanName a bean definition name diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java index 7b3f25581b..c538d17723 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/CoreNamespaceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -37,8 +37,12 @@ * @author Thomas Risberg * @author Michael Minella * @author Mahmoud Ben Hassine + * @author Taeik Lim */ -public class CoreNamespaceUtils { +public abstract class CoreNamespaceUtils { + + private CoreNamespaceUtils() { + } private static final String STEP_SCOPE_PROCESSOR_BEAN_NAME = "org.springframework.batch.core.scope.internalStepScope"; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcParameterUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcParameterUtils.java index 9dff9b026b..92b49280b6 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcParameterUtils.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcParameterUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -28,9 +28,13 @@ * @author Thomas Risberg * @author Juergen Hoeller * @author Marten Deinum + * @author Taeik Lim * @since 2.0 */ -public class JdbcParameterUtils { +public abstract class JdbcParameterUtils { + + private JdbcParameterUtils() { + } /** * Count the occurrences of the character placeholder in an SQL string diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java index f332f92fc3..29e1ee2a52 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -31,9 +31,13 @@ * @author Dave Syer * @author Michael Minella * @author Mahmoud Ben Hassine + * @author Taeik Lim * @since 2.0 */ -public class SqlPagingQueryUtils { +public abstract class SqlPagingQueryUtils { + + private SqlPagingQueryUtils() { + } /** * Generate SQL query string using a LIMIT clause diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/util/FileUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/util/FileUtils.java index d7a8370727..1b82ae1634 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/util/FileUtils.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/util/FileUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2021 the original author or authors. + * Copyright 2006-2024 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. @@ -27,8 +27,9 @@ * * @author Peter Zozom * @author Mahmoud Ben Hassine + * @author Taeik Lim */ -public final class FileUtils { +public abstract class FileUtils { // forbids instantiation private FileUtils() { diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java index 3d26d717b7..b824b36aea 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -34,9 +34,13 @@ * * @author Lucas Ward * @author Mahmoud Ben Hassine + * @author Taeik Lim * @since 2.0 */ -public class MethodInvokerUtils { +public abstract class MethodInvokerUtils { + + private MethodInvokerUtils() { + } /** * Create a {@link MethodInvoker} using the provided method name to search. diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/ReflectionUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/ReflectionUtils.java index f1fc0ecf73..055274af23 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/ReflectionUtils.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/ReflectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2023 the original author or authors. + * Copyright 2014-2024 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. @@ -29,9 +29,10 @@ * * @author Michael Minella * @author Mahmoud Ben Hassine + * @author Taeik Lim * @since 2.2.6 */ -public class ReflectionUtils { +public abstract class ReflectionUtils { private ReflectionUtils() { } diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/ExecutionContextTestUtils.java b/spring-batch-test/src/main/java/org/springframework/batch/test/ExecutionContextTestUtils.java index 315750b7eb..9d143d80b6 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/ExecutionContextTestUtils.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/ExecutionContextTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2018 the original author or authors. + * Copyright 2006-2024 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. @@ -30,10 +30,14 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine + * @author Taeik Lim * @since 2.1.4 * */ -public class ExecutionContextTestUtils { +public abstract class ExecutionContextTestUtils { + + private ExecutionContextTestUtils() { + } @SuppressWarnings("unchecked") @Nullable diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/JobScopeTestUtils.java b/spring-batch-test/src/main/java/org/springframework/batch/test/JobScopeTestUtils.java index 989512bb46..8c0d4391a0 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/JobScopeTestUtils.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/JobScopeTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -29,8 +29,12 @@ * @author Dave Syer * @author Jimmy Praet * @author Mahmoud Ben Hassine + * @author Taeik Lim */ -public class JobScopeTestUtils { +public abstract class JobScopeTestUtils { + + private JobScopeTestUtils() { + } public static T doInJobScope(JobExecution jobExecution, Callable callable) throws Exception { try { diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestUtils.java b/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestUtils.java index 1365d5c0ee..93e26151f2 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestUtils.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/StepScopeTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2010 the original author or authors. + * Copyright 2006-2024 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. @@ -27,9 +27,13 @@ * test case that happen to be step scoped in the application context. * * @author Dave Syer + * @author Taeik Lim * */ -public class StepScopeTestUtils { +public abstract class StepScopeTestUtils { + + private StepScopeTestUtils() { + } public static T doInStepScope(StepExecution stepExecution, Callable callable) throws Exception { try { From 9fa21d2992ff77cdcb65902f70e1ff542567c615 Mon Sep 17 00:00:00 2001 From: Yejin Choi <63734765+chldppwls12@users.noreply.github.com> Date: Thu, 15 Feb 2024 13:11:26 +0900 Subject: [PATCH 022/152] Fix incorrect code example in documentation Issue #4550 Signed-off-by: Fabrice Bibonne --- spring-batch-docs/modules/ROOT/pages/step/tasklet.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/step/tasklet.adoc b/spring-batch-docs/modules/ROOT/pages/step/tasklet.adoc index 2613e34878..7ad23b8dae 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/tasklet.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/tasklet.adoc @@ -122,7 +122,7 @@ public class FileDeletingTasklet implements Tasklet, InitializingBean { public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { File dir = directory.getFile(); - Assert.state(dir.isDirectory()); + Assert.state(dir.isDirectory(), "The resource must be a directory"); File[] files = dir.listFiles(); for (int i = 0; i < files.length; i++) { @@ -140,7 +140,7 @@ public class FileDeletingTasklet implements Tasklet, InitializingBean { } public void afterPropertiesSet() throws Exception { - Assert.state(directory != null, "directory must be set"); + Assert.state(directory != null, "Directory must be set"); } } ---- From 0c83e4aa2ad6218a70a22401e1e634ac1a8ea024 Mon Sep 17 00:00:00 2001 From: Baljit Singh Date: Thu, 22 Feb 2024 10:16:26 -0500 Subject: [PATCH 023/152] Avoid collection copying Signed-off-by: Fabrice Bibonne --- .../core/launch/support/CommandLineJobRunner.java | 3 ++- .../java/org/springframework/batch/item/Chunk.java | 10 +++++----- .../batch/item/data/MongoItemWriter.java | 4 ++-- .../batch/item/data/Neo4jItemWriter.java | 2 +- .../batch/item/data/RepositoryItemWriter.java | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java index 469ff15622..34bdf928b0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/CommandLineJobRunner.java @@ -53,6 +53,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** @@ -405,7 +406,7 @@ private List getJobExecutionsWithStatusGreaterThan(String jobIdent for (JobInstance jobInstance : lastInstances) { List jobExecutions = jobExplorer.getJobExecutions(jobInstance); - if (jobExecutions == null || jobExecutions.isEmpty()) { + if (CollectionUtils.isEmpty(jobExecutions)) { continue; } for (JobExecution jobExecution : jobExecutions) { diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/Chunk.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/Chunk.java index dd39b70e76..d772294c8e 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/Chunk.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/Chunk.java @@ -38,9 +38,9 @@ */ public class Chunk implements Iterable, Serializable { - private List items = new ArrayList<>(); + private final List items = new ArrayList<>(); - private List> skips = new ArrayList<>(); + private final List> skips = new ArrayList<>(); private final List errors = new ArrayList<>(); @@ -67,10 +67,10 @@ public Chunk(List items) { public Chunk(List items, List> skips) { super(); if (items != null) { - this.items = new ArrayList<>(items); + this.items.addAll(items); } if (skips != null) { - this.skips = new ArrayList<>(skips); + this.skips.addAll(skips); } } @@ -103,7 +103,7 @@ public void clear() { * @return a copy of the items to be processed as an unmodifiable list */ public List getItems() { - return List.copyOf(items); + return Collections.unmodifiableList(items); } /** diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemWriter.java index 9ea598c5d4..0cb21451e7 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemWriter.java @@ -188,7 +188,7 @@ public void write(Chunk chunk) throws Exception { * @param chunk the chunk of items to be persisted. */ protected void doWrite(Chunk chunk) { - if (!CollectionUtils.isEmpty(chunk.getItems())) { + if (!chunk.isEmpty()) { switch (this.mode) { case INSERT -> insert(chunk); case REMOVE -> remove(chunk); @@ -263,7 +263,7 @@ private Chunk getCurrentBuffer() { public void beforeCommit(boolean readOnly) { Chunk chunk = (Chunk) TransactionSynchronizationManager.getResource(bufferKey); - if (!CollectionUtils.isEmpty(chunk.getItems())) { + if (!chunk.isEmpty()) { if (!readOnly) { doWrite(chunk); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemWriter.java index 40cfbdb4cc..68d717d1d9 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemWriter.java @@ -89,7 +89,7 @@ public void afterPropertiesSet() throws Exception { */ @Override public void write(Chunk chunk) throws Exception { - if (!CollectionUtils.isEmpty(chunk.getItems())) { + if (!chunk.isEmpty()) { doWrite(chunk); } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemWriter.java index eb23f7800a..2065bf0d5a 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemWriter.java @@ -93,7 +93,7 @@ public void setRepository(CrudRepository repository) { */ @Override public void write(Chunk chunk) throws Exception { - if (!CollectionUtils.isEmpty(chunk.getItems())) { + if (!chunk.isEmpty()) { doWrite(chunk); } } From 9fa108498ca59b1eec071eefbf26d659d12f5f04 Mon Sep 17 00:00:00 2001 From: hwan33 Date: Sat, 18 Nov 2023 20:35:59 +0900 Subject: [PATCH 024/152] Fix misleading documentation regarding the ItemWriteListener Resolves #4400 Signed-off-by: Fabrice Bibonne --- .../chunk-oriented-processing/intercepting-execution.adoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc index bdb7f57b61..142cc7772a 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc @@ -207,9 +207,10 @@ public interface ItemWriteListener extends StepListener { ---- The `beforeWrite` method is called before `write` on the `ItemWriter` and is handed the -list of items that is written. The `afterWrite` method is called after the item has been -successfully written. If there was an error while writing, the `onWriteError` method is -called. The exception encountered and the item that was attempted to be written are +list of items that is written. The `afterWrite` method is called after the items have been +successfully written, but before committing the transaction associated with the chunk's processing. +If there was an error while writing, the `onWriteError` method is called. +The exception encountered and the item that was attempted to be written are provided, so that they can be logged. The annotations corresponding to this interface are: From 347937a16c0014376bc7b8a3b4dc2b183ddfac8d Mon Sep 17 00:00:00 2001 From: Mustafa Yanar Date: Mon, 4 Mar 2024 22:07:58 +0300 Subject: [PATCH 025/152] Fix output chunk end property in ChunkProcessor implementations Resolves #4560 Signed-off-by: Fabrice Bibonne --- .../step/item/FaultTolerantChunkProcessor.java | 6 ++++-- .../batch/core/step/item/SimpleChunkProcessor.java | 5 ++++- .../item/FaultTolerantChunkProcessorTests.java | 13 ++++++++++++- .../core/step/item/SimpleChunkProcessorTests.java | 14 +++++++++++++- .../java/org/springframework/batch/item/Chunk.java | 9 ++++++++- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java index f1eb7321f5..ecb797111c 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -306,7 +306,9 @@ else if (shouldSkip(itemProcessSkipPolicy, e, contribution.getStepSkipCount())) break; } } - + if (inputs.isEnd()) { + outputs.setEnd(); + } return outputs; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.java index 813b6eb403..101945bb22 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/SimpleChunkProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -340,6 +340,9 @@ protected Chunk transform(StepContribution contribution, Chunk inputs) thr iterator.remove(); } } + if (inputs.isEnd()) { + outputs.setEnd(); + } return outputs; } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessorTests.java index 5070b277a4..d30e06917e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2023 the original author or authors. + * Copyright 2008-2024 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. @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import java.util.ArrayList; @@ -97,6 +98,16 @@ public String process(String item) throws Exception { assertEquals(1, contribution.getFilterCount()); } + @Test + void testTransformChunkEnd() throws Exception { + Chunk inputs = new Chunk<>(Arrays.asList("1", "2")); + inputs.setEnd(); + processor.initializeUserData(inputs); + Chunk outputs = processor.transform(contribution, inputs); + assertEquals(Arrays.asList("1", "2"), outputs.getItems()); + assertTrue(outputs.isEnd()); + } + @Test void testFilterCountOnSkip() throws Exception { processor.setProcessSkipPolicy(new AlwaysSkipItemSkipPolicy()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProcessorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProcessorTests.java index e9a7e0e678..5ebcb49ced 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProcessorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleChunkProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2023 the original author or authors. + * Copyright 2008-2024 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. @@ -16,6 +16,7 @@ package org.springframework.batch.core.step.item; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.Arrays; @@ -76,4 +77,15 @@ void testProcess() throws Exception { assertEquals(2, contribution.getWriteCount()); } + @Test + void testTransform() throws Exception { + Chunk inputs = new Chunk<>(); + inputs.add("foo"); + inputs.add("bar"); + inputs.setEnd(); + Chunk outputs = processor.transform(contribution, inputs); + assertEquals(Arrays.asList("foo", "bar"), outputs.getItems()); + assertTrue(outputs.isEnd()); + } + } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/Chunk.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/Chunk.java index d772294c8e..52895ca79d 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/Chunk.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/Chunk.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -154,6 +154,13 @@ public int size() { /** * Flag to indicate if the source data is exhausted. + * + *

+ * Note: This may return false if the last chunk has the same number of items as the + * configured commit interval. Consequently, in such cases,there will be a last empty + * chunk that won't be processed. It is recommended to consider this behavior when + * utilizing this method. + *

* @return true if there is no more data to process */ public boolean isEnd() { From 7c59cd60d0b2bfce680e4120fc89cb7690025d3d Mon Sep 17 00:00:00 2001 From: Robert McNees Date: Tue, 30 Jan 2024 15:02:38 -0500 Subject: [PATCH 026/152] Change DefaultStateTransitionComparator ordering to match logical flow execution Resolves #4527 Signed-off-by: Fabrice Bibonne --- .../DefaultStateTransitionComparator.java | 28 ++++++----- .../core/job/flow/support/SimpleFlow.java | 4 +- ...DefaultStateTransitionComparatorTests.java | 50 +++++++++---------- 3 files changed, 42 insertions(+), 40 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparator.java index c49d569422..53015ae8ce 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 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. @@ -20,8 +20,10 @@ import java.util.Comparator; /** - * Sorts by ascending specificity of pattern, based on counting wildcards (with * taking - * precedence over ?). Hence * > foo* > ??? > fo? > foo. + * Sorts by descending specificity of pattern, based on counting wildcards (with ? being + * considered more specific than *). This means that more specific patterns will be + * considered greater than less specific patterns. Hence foo > fo? > ??? > foo* + * > * * * For more complex comparisons, any string containing at least one * token will be * considered more generic than any string that has no * token. If both strings have at @@ -39,8 +41,8 @@ * * If the strings contain neither * nor ? tokens then alphabetic comparison will be used. * - * Hence * > foo* > *f* > *foo* > ??? > ?o? > foo?? > bar?? > fo? - * > foo > bar + * Hence bar > foo > fo? > bar?? > foo?? > ?0? > ??? > *foo* > *f* + * > foo* > * * * @see Comparator * @author Michael Minella @@ -61,31 +63,31 @@ public int compare(StateTransition arg0, StateTransition arg1) { int arg0AsteriskCount = StringUtils.countOccurrencesOf(arg0Pattern, "*"); int arg1AsteriskCount = StringUtils.countOccurrencesOf(arg1Pattern, "*"); if (arg0AsteriskCount > 0 && arg1AsteriskCount == 0) { - return 1; + return -1; } if (arg0AsteriskCount == 0 && arg1AsteriskCount > 0) { - return -1; + return 1; } if (arg0AsteriskCount > 0 && arg1AsteriskCount > 0) { if (arg0AsteriskCount < arg1AsteriskCount) { - return 1; + return -1; } if (arg0AsteriskCount > arg1AsteriskCount) { - return -1; + return 1; } } int arg0WildcardCount = StringUtils.countOccurrencesOf(arg0Pattern, "?"); int arg1WildcardCount = StringUtils.countOccurrencesOf(arg1Pattern, "?"); if (arg0WildcardCount > arg1WildcardCount) { - return 1; + return -1; } if (arg0WildcardCount < arg1WildcardCount) { - return -1; + return 1; } if (arg0Pattern.length() != arg1Pattern.length() && (arg0AsteriskCount > 0 || arg0WildcardCount > 0)) { - return Integer.compare(arg1Pattern.length(), arg0Pattern.length()); + return Integer.compare(arg0Pattern.length(), arg1Pattern.length()); } - return arg0.getPattern().compareTo(arg1Pattern); + return arg1.getPattern().compareTo(arg0Pattern); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java index f66ce004c2..1818d017fd 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/SimpleFlow.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -313,7 +313,7 @@ private void initializeTransitions() { set = new LinkedHashSet<>(); } else { - set = new TreeSet<>(stateTransitionComparator); + set = new TreeSet<>(stateTransitionComparator).descendingSet(); } transitionMap.put(name, set); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparatorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparatorTests.java index d71e4813b2..45e323f6c4 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparatorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/DefaultStateTransitionComparatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2024 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. @@ -39,96 +39,96 @@ void testSimpleOrderingEqual() { void testSimpleOrderingMoreGeneral() { StateTransition generic = StateTransition.createStateTransition(state, "CONTIN???LE", "start"); StateTransition specific = StateTransition.createStateTransition(state, "CONTINUABLE", "start"); - assertEquals(1, comparator.compare(generic, specific)); - assertEquals(-1, comparator.compare(specific, generic)); + assertEquals(1, comparator.compare(specific, generic)); + assertEquals(-1, comparator.compare(generic, specific)); } @Test void testSimpleOrderingMostGeneral() { StateTransition generic = StateTransition.createStateTransition(state, "*", "start"); StateTransition specific = StateTransition.createStateTransition(state, "CONTINUABLE", "start"); - assertEquals(1, comparator.compare(generic, specific)); - assertEquals(-1, comparator.compare(specific, generic)); + assertEquals(1, comparator.compare(specific, generic)); + assertEquals(-1, comparator.compare(generic, specific)); } @Test void testSubstringAndWildcard() { StateTransition generic = StateTransition.createStateTransition(state, "CONTIN*", "start"); StateTransition specific = StateTransition.createStateTransition(state, "CONTINUABLE", "start"); - assertEquals(1, comparator.compare(generic, specific)); - assertEquals(-1, comparator.compare(specific, generic)); + assertEquals(1, comparator.compare(specific, generic)); + assertEquals(-1, comparator.compare(generic, specific)); } @Test void testSimpleOrderingMostToNextGeneral() { StateTransition generic = StateTransition.createStateTransition(state, "*", "start"); StateTransition specific = StateTransition.createStateTransition(state, "C?", "start"); - assertEquals(1, comparator.compare(generic, specific)); - assertEquals(-1, comparator.compare(specific, generic)); + assertEquals(1, comparator.compare(specific, generic)); + assertEquals(-1, comparator.compare(generic, specific)); } @Test void testSimpleOrderingAdjacent() { StateTransition generic = StateTransition.createStateTransition(state, "CON*", "start"); StateTransition specific = StateTransition.createStateTransition(state, "CON?", "start"); - assertEquals(1, comparator.compare(generic, specific)); - assertEquals(-1, comparator.compare(specific, generic)); + assertEquals(1, comparator.compare(specific, generic)); + assertEquals(-1, comparator.compare(generic, specific)); } @Test void testOrderByNumberOfGenericWildcards() { StateTransition generic = StateTransition.createStateTransition(state, "*", "start"); StateTransition specific = StateTransition.createStateTransition(state, "**", "start"); - assertEquals(1, comparator.compare(generic, specific)); - assertEquals(-1, comparator.compare(specific, generic)); + assertEquals(1, comparator.compare(specific, generic)); + assertEquals(-1, comparator.compare(generic, specific)); } @Test void testOrderByNumberOfSpecificWildcards() { StateTransition generic = StateTransition.createStateTransition(state, "CONTI??ABLE", "start"); StateTransition specific = StateTransition.createStateTransition(state, "CONTI?UABLE", "start"); - assertEquals(1, comparator.compare(generic, specific)); - assertEquals(-1, comparator.compare(specific, generic)); + assertEquals(1, comparator.compare(specific, generic)); + assertEquals(-1, comparator.compare(generic, specific)); } @Test void testOrderByLengthWithAsteriskEquality() { StateTransition generic = StateTransition.createStateTransition(state, "CON*", "start"); StateTransition specific = StateTransition.createStateTransition(state, "CONTINUABLE*", "start"); - assertEquals(1, comparator.compare(generic, specific)); - assertEquals(-1, comparator.compare(specific, generic)); + assertEquals(1, comparator.compare(specific, generic)); + assertEquals(-1, comparator.compare(generic, specific)); } @Test void testOrderByLengthWithWildcardEquality() { StateTransition generic = StateTransition.createStateTransition(state, "CON??", "start"); StateTransition specific = StateTransition.createStateTransition(state, "CONTINUABLE??", "start"); - assertEquals(1, comparator.compare(generic, specific)); - assertEquals(-1, comparator.compare(specific, generic)); + assertEquals(1, comparator.compare(specific, generic)); + assertEquals(-1, comparator.compare(generic, specific)); } @Test void testOrderByAlphaWithAsteriskEquality() { StateTransition generic = StateTransition.createStateTransition(state, "DOG**", "start"); StateTransition specific = StateTransition.createStateTransition(state, "CAT**", "start"); - assertEquals(1, comparator.compare(generic, specific)); - assertEquals(-1, comparator.compare(specific, generic)); + assertEquals(1, comparator.compare(specific, generic)); + assertEquals(-1, comparator.compare(generic, specific)); } @Test void testOrderByAlphaWithWildcardEquality() { StateTransition generic = StateTransition.createStateTransition(state, "DOG??", "start"); StateTransition specific = StateTransition.createStateTransition(state, "CAT??", "start"); - assertEquals(1, comparator.compare(generic, specific)); - assertEquals(-1, comparator.compare(specific, generic)); + assertEquals(1, comparator.compare(specific, generic)); + assertEquals(-1, comparator.compare(generic, specific)); } @Test void testPriorityOrderingWithAlphabeticComparison() { StateTransition generic = StateTransition.createStateTransition(state, "DOG", "start"); StateTransition specific = StateTransition.createStateTransition(state, "CAT", "start"); - assertEquals(1, comparator.compare(generic, specific)); - assertEquals(-1, comparator.compare(specific, generic)); + assertEquals(1, comparator.compare(specific, generic)); + assertEquals(-1, comparator.compare(generic, specific)); } } From 153bcc7061b215dd6f12a5fa73c1baef999bb23d Mon Sep 17 00:00:00 2001 From: injae-kim Date: Wed, 13 Mar 2024 01:10:12 +0900 Subject: [PATCH 027/152] Fix SystemCommandTasklet to propagate error when exit status is failed Fixes #4483 Signed-off-by: Fabrice Bibonne --- .../core/step/tasklet/SystemCommandTasklet.java | 14 +++++++++++--- .../SystemCommandTaskletIntegrationTests.java | 7 +++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java index b35a228a4d..4499279e8e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SystemCommandTasklet.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -60,6 +60,7 @@ * @author Robert Kasanicky * @author Will Schipp * @author Mahmoud Ben Hassine + * @author Injae Kim */ public class SystemCommandTasklet implements StepExecutionListener, StoppableTasklet, InitializingBean { @@ -121,8 +122,15 @@ public RepeatStatus execute(StepContribution contribution, ChunkContext chunkCon } if (systemCommandTask.isDone()) { - contribution.setExitStatus(systemProcessExitCodeMapper.getExitStatus(systemCommandTask.get())); - return RepeatStatus.FINISHED; + Integer exitCode = systemCommandTask.get(); + ExitStatus exitStatus = systemProcessExitCodeMapper.getExitStatus(exitCode); + contribution.setExitStatus(exitStatus); + if (ExitStatus.FAILED.equals(exitStatus)) { + throw new SystemCommandException("Execution of system command failed with exit code " + exitCode); + } + else { + return RepeatStatus.FINISHED; + } } else if (System.currentTimeMillis() - t0 > timeout) { systemCommandTask.cancel(interruptOnCancel); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/SystemCommandTaskletIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/SystemCommandTaskletIntegrationTests.java index 006d9ed877..b703d91ee3 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/SystemCommandTaskletIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/SystemCommandTaskletIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2023 the original author or authors. + * Copyright 2008-2024 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. @@ -323,9 +323,8 @@ public void testExecuteWithFailedCommandRunnerMockExecution() throws Exception { tasklet.setCommand(command); tasklet.afterPropertiesSet(); - RepeatStatus exitStatus = tasklet.execute(stepContribution, null); - - assertEquals(RepeatStatus.FINISHED, exitStatus); + Exception exception = assertThrows(SystemCommandException.class, () -> tasklet.execute(stepContribution, null)); + assertTrue(exception.getMessage().contains("failed with exit code")); assertEquals(ExitStatus.FAILED, stepContribution.getExitStatus()); } From 4ee2cf455068afeb226f424f46f5e5ae9276d1f9 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 22 Mar 2024 15:40:52 +0100 Subject: [PATCH 028/152] Improve the process of job registration This commit changes the way of populating the default job registry from using a BeanPostProcessor to using a JobRegistrySmartInitializingSingleton. This change resolves warnings about beans being eagerly injected into currently created BeanPostProcessors and prevents lifecycle issues about early bean initializations. It also deprecates JobRegistryBeanPostProcessor in favor of JobRegistrySmartInitializingSingleton. Resolves #4547 Signed-off-by: Fabrice Bibonne --- .../annotation/BatchRegistrar.java | 20 +++++++------ .../annotation/EnableBatchProcessing.java | 8 ++--- .../support/DefaultBatchConfiguration.java | 30 ++++++++++++------- .../support/JobRegistryBeanPostProcessor.java | 3 ++ .../annotation/BatchRegistrarTests.java | 13 ++++---- .../DefaultBatchConfigurationTests.java | 21 ++++++------- .../ROOT/pages/job/advanced-meta-data.adoc | 7 ++++- 7 files changed, 62 insertions(+), 40 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java index 25d31a319e..5a680aaa31 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 the original author or authors. + * Copyright 2022-2024 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. @@ -22,7 +22,7 @@ import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; import org.springframework.batch.core.configuration.support.DefaultJobLoader; -import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor; +import org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton; import org.springframework.batch.core.configuration.support.MapJobRegistry; import org.springframework.batch.core.explore.support.JobExplorerFactoryBean; import org.springframework.batch.core.launch.support.JobOperatorFactoryBean; @@ -63,7 +63,7 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B registerJobExplorer(registry, batchAnnotation); registerJobLauncher(registry, batchAnnotation); registerJobRegistry(registry); - registerJobRegistryBeanPostProcessor(registry); + registerJobRegistrySmartInitializingSingleton(registry); registerJobOperator(registry, batchAnnotation); registerAutomaticJobRegistrar(registry, batchAnnotation); watch.stop(); @@ -225,17 +225,19 @@ private void registerJobRegistry(BeanDefinitionRegistry registry) { registry.registerBeanDefinition("jobRegistry", beanDefinition); } - private void registerJobRegistryBeanPostProcessor(BeanDefinitionRegistry registry) { - if (registry.containsBeanDefinition("jobRegistryBeanPostProcessor")) { - LOGGER.info("Bean jobRegistryBeanPostProcessor already defined in the application context, skipping" - + " the registration of a jobRegistryBeanPostProcessor"); + private void registerJobRegistrySmartInitializingSingleton(BeanDefinitionRegistry registry) { + if (registry.containsBeanDefinition("jobRegistrySmartInitializingSingleton")) { + LOGGER + .info("Bean jobRegistrySmartInitializingSingleton already defined in the application context, skipping" + + " the registration of a jobRegistrySmartInitializingSingleton"); return; } BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder - .genericBeanDefinition(JobRegistryBeanPostProcessor.class); + .genericBeanDefinition(JobRegistrySmartInitializingSingleton.class); beanDefinitionBuilder.addPropertyReference("jobRegistry", "jobRegistry"); - registry.registerBeanDefinition("jobRegistryBeanPostProcessor", beanDefinitionBuilder.getBeanDefinition()); + registry.registerBeanDefinition("jobRegistrySmartInitializingSingleton", + beanDefinitionBuilder.getBeanDefinition()); } private void registerJobOperator(BeanDefinitionRegistry registry, EnableBatchProcessing batchAnnotation) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java index 301d160b62..c4fb3be8d7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -93,9 +93,9 @@ * "jobOperator" of type * {@link org.springframework.batch.core.launch.support.SimpleJobOperator}) *
  • a - * {@link org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor} - * (bean name "jobRegistryBeanPostProcessor" of type - * {@link org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor})
  • + * {@link org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton} + * (bean name "jobRegistrySmartInitializingSingleton" of type + * {@link org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton}) * * * If the configuration is specified as modular=true, the context also diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java index 4789ccc264..17cf0e391a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java @@ -251,30 +251,40 @@ public JobOperator jobOperator(JobRepository jobRepository, JobExplorer jobExplo * @return a {@link JobRegistryBeanPostProcessor} * @throws BatchConfigurationException if unable to register the bean * @since 5.1 - * @deprecated Use {@link #jobRegistryBeanPostProcessor(JobRegistry)} instead + * @deprecated Use {@link #jobRegistrySmartInitializingSingleton(JobRegistry)} instead */ @Deprecated(forRemoval = true) public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor() throws BatchConfigurationException { - return jobRegistryBeanPostProcessor(jobRegistry()); + JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor(); + jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry()); + try { + jobRegistryBeanPostProcessor.afterPropertiesSet(); + return jobRegistryBeanPostProcessor; + } + catch (Exception e) { + throw new BatchConfigurationException("Unable to configure the default job registry BeanPostProcessor", e); + } } /** - * Defines a {@link JobRegistryBeanPostProcessor} bean. - * @return a {@link JobRegistryBeanPostProcessor} bean + * Define a {@link JobRegistrySmartInitializingSingleton} bean. + * @param jobRegistry the job registry to populate * @throws BatchConfigurationException if unable to register the bean + * @return a bean of type {@link JobRegistrySmartInitializingSingleton} * @since 5.2 */ @Bean - public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) + public JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton(JobRegistry jobRegistry) throws BatchConfigurationException { - JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor(); - jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry); + JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton = new JobRegistrySmartInitializingSingleton(); + jobRegistrySmartInitializingSingleton.setJobRegistry(jobRegistry); try { - jobRegistryBeanPostProcessor.afterPropertiesSet(); - return jobRegistryBeanPostProcessor; + jobRegistrySmartInitializingSingleton.afterPropertiesSet(); + return jobRegistrySmartInitializingSingleton; } catch (Exception e) { - throw new BatchConfigurationException("Unable to configure the default job registry BeanPostProcessor", e); + throw new BatchConfigurationException( + "Unable to configure the default job registry SmartInitializingSingleton", e); } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java index 7b66601fb6..4afe2597cc 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java @@ -45,10 +45,13 @@ * recommended in cases where this class may cause early bean initializations. You must * include at most one of either of them as a bean. * + * @deprecated since 5.2 in favor of {@link JobRegistrySmartInitializingSingleton}. + * * @author Dave Syer * @author Mahmoud Ben Hassine * */ +@Deprecated(since = "5.2") public class JobRegistryBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, InitializingBean, DisposableBean { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java index f26b80d3e8..f44881c110 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 the original author or authors. + * Copyright 2022-2024 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. @@ -27,7 +27,7 @@ import org.springframework.batch.core.DefaultJobKeyGenerator; import org.springframework.batch.core.JobKeyGenerator; import org.springframework.batch.core.configuration.JobRegistry; -import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor; +import org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.JobOperator; @@ -79,7 +79,7 @@ void testConfigurationWithUserDefinedBeans() { Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobLauncher.class)).isMock()); Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobRegistry.class)).isMock()); Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobOperator.class)).isMock()); - Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobRegistryBeanPostProcessor.class)).isMock()); + Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobRegistrySmartInitializingSingleton.class)).isMock()); } @Test @@ -162,7 +162,8 @@ void testDefaultInfrastructureBeansRegistration() { JobExplorer jobExplorer = context.getBean(JobExplorer.class); JobRegistry jobRegistry = context.getBean(JobRegistry.class); JobOperator jobOperator = context.getBean(JobOperator.class); - JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = context.getBean(JobRegistryBeanPostProcessor.class); + JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton = context + .getBean(JobRegistrySmartInitializingSingleton.class); // then Assertions.assertNotNull(jobLauncher); @@ -170,7 +171,7 @@ void testDefaultInfrastructureBeansRegistration() { Assertions.assertNotNull(jobExplorer); Assertions.assertNotNull(jobRegistry); Assertions.assertNotNull(jobOperator); - Assertions.assertNotNull(jobRegistryBeanPostProcessor); + Assertions.assertNotNull(jobRegistrySmartInitializingSingleton); } @Test @@ -249,7 +250,7 @@ public JobOperator jobOperator() { } @Bean - public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor() { + public JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton() { return Mockito.mock(); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java index d80dd21715..b660c40b83 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/DefaultBatchConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 the original author or authors. + * Copyright 2022-2024 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. @@ -85,9 +85,9 @@ void testConfigurationWithCustomInfrastructureBean() { Assertions.assertEquals(1, jobRepositories.size()); JobRepository jobRepository = jobRepositories.entrySet().iterator().next().getValue(); Assertions.assertInstanceOf(DummyJobRepository.class, jobRepository); - Map jobRegistryBeanPostProcessorMap = context - .getBeansOfType(JobRegistryBeanPostProcessor.class); - Assertions.assertEquals(1, jobRegistryBeanPostProcessorMap.size()); + Map jobRegistrySmartInitializingSingletonMap = context + .getBeansOfType(JobRegistrySmartInitializingSingleton.class); + Assertions.assertEquals(1, jobRegistrySmartInitializingSingletonMap.size()); } @Test @@ -101,7 +101,8 @@ void testDefaultInfrastructureBeansRegistration() { JobExplorer jobExplorer = context.getBean(JobExplorer.class); JobRegistry jobRegistry = context.getBean(JobRegistry.class); JobOperator jobOperator = context.getBean(JobOperator.class); - JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = context.getBean(JobRegistryBeanPostProcessor.class); + JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton = context + .getBean(JobRegistrySmartInitializingSingleton.class); // then Assertions.assertNotNull(jobLauncher); @@ -109,7 +110,7 @@ void testDefaultInfrastructureBeansRegistration() { Assertions.assertNotNull(jobExplorer); Assertions.assertNotNull(jobRegistry); Assertions.assertNotNull(jobOperator); - Assertions.assertNotNull(jobRegistryBeanPostProcessor); + Assertions.assertNotNull(jobRegistrySmartInitializingSingleton); } @Configuration @@ -161,10 +162,10 @@ public JobRepository jobRepository() { } @Bean - public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) { - JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor(); - postProcessor.setJobRegistry(jobRegistry); - return postProcessor; + public JobRegistrySmartInitializingSingleton jobRegistrySmartInitializingSingleton(JobRegistry jobRegistry) { + JobRegistrySmartInitializingSingleton smartInitializingSingleton = new JobRegistrySmartInitializingSingleton(); + smartInitializingSingleton.setJobRegistry(jobRegistry); + return smartInitializingSingleton; } } diff --git a/spring-batch-docs/modules/ROOT/pages/job/advanced-meta-data.adoc b/spring-batch-docs/modules/ROOT/pages/job/advanced-meta-data.adoc index bd41a5d941..bfa7ff3d1a 100644 --- a/spring-batch-docs/modules/ROOT/pages/job/advanced-meta-data.adoc +++ b/spring-batch-docs/modules/ROOT/pages/job/advanced-meta-data.adoc @@ -222,7 +222,12 @@ example has been given an `id` so that it can be included in child contexts (for example, as a parent bean definition) and cause all jobs created there to also be registered automatically. -As of version 5.1, the `@EnableBatchProcessing` annotation automatically registers a `jobRegistryBeanPostProcessor` bean in the application context. +[WARNING] +.Deprecation +==== +As of version 5.2, the `JobRegistryBeanPostProcessor` class is deprecated in favor of +`JobRegistrySmartInitializingSingleton`, see xref:#jobregistrysmartinitializingsingleton[JobRegistrySmartInitializingSingleton]. +==== [[jobregistrysmartinitializingsingleton]] === JobRegistrySmartInitializingSingleton From 6cf8343a23315a45923d33a79a0c98d176b07eaa Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 22 Mar 2024 17:39:08 +0100 Subject: [PATCH 029/152] Fix code formatting Signed-off-by: Fabrice Bibonne --- .../configuration/support/JobRegistryBeanPostProcessor.java | 1 - .../core/configuration/annotation/BatchRegistrarTests.java | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java index 4afe2597cc..1f6ba7acfa 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/JobRegistryBeanPostProcessor.java @@ -46,7 +46,6 @@ * include at most one of either of them as a bean. * * @deprecated since 5.2 in favor of {@link JobRegistrySmartInitializingSingleton}. - * * @author Dave Syer * @author Mahmoud Ben Hassine * diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java index f44881c110..eb5fc3f2ba 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java @@ -79,7 +79,8 @@ void testConfigurationWithUserDefinedBeans() { Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobLauncher.class)).isMock()); Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobRegistry.class)).isMock()); Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobOperator.class)).isMock()); - Assertions.assertTrue(Mockito.mockingDetails(context.getBean(JobRegistrySmartInitializingSingleton.class)).isMock()); + Assertions + .assertTrue(Mockito.mockingDetails(context.getBean(JobRegistrySmartInitializingSingleton.class)).isMock()); } @Test From 11f074e707ea07cddfbd9ef963915ae433e54db3 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 25 Mar 2024 10:04:01 +0100 Subject: [PATCH 030/152] Fix test Signed-off-by: Fabrice Bibonne --- ...ySQLJdbcJobRepositoryIntegrationTests.java | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MySQLJdbcJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MySQLJdbcJobRepositoryIntegrationTests.java index 8936d89023..67009a0465 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MySQLJdbcJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/MySQLJdbcJobRepositoryIntegrationTests.java @@ -34,14 +34,10 @@ import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; -import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor; -import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.JobOperator; -import org.springframework.batch.core.launch.support.SimpleJobOperator; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.beans.factory.annotation.Autowired; @@ -151,24 +147,6 @@ public Job job(JobRepository jobRepository, PlatformTransactionManager transacti .build(); } - @Bean - public JobOperator jobOperator(JobLauncher jobLauncher, JobRegistry jobRegistry, JobExplorer jobExplorer, - JobRepository jobRepository) { - SimpleJobOperator jobOperator = new SimpleJobOperator(); - jobOperator.setJobExplorer(jobExplorer); - jobOperator.setJobLauncher(jobLauncher); - jobOperator.setJobRegistry(jobRegistry); - jobOperator.setJobRepository(jobRepository); - return jobOperator; - } - - @Bean - public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) { - JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor(); - jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry); - return jobRegistryBeanPostProcessor; - } - @Bean public ConfigurableConversionService conversionService() { DefaultConversionService conversionService = new DefaultConversionService(); From e31e0e1a8f06d8fa247216c0b6987b83728c8a19 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 25 Mar 2024 11:26:51 +0100 Subject: [PATCH 031/152] Add native runtime hints for remote partitioning types Resolves #4564 Signed-off-by: Fabrice Bibonne --- .../integration/aot/IntegrationRuntimeHints.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/aot/IntegrationRuntimeHints.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/aot/IntegrationRuntimeHints.java index 72674495fb..5f42caf800 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/aot/IntegrationRuntimeHints.java +++ b/spring-batch-integration/src/main/java/org/springframework/batch/integration/aot/IntegrationRuntimeHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 the original author or authors. + * Copyright 2023-2024 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. @@ -20,6 +20,9 @@ import org.springframework.aot.hint.RuntimeHintsRegistrar; import org.springframework.batch.integration.chunk.ChunkRequest; import org.springframework.batch.integration.chunk.ChunkResponse; +import org.springframework.batch.integration.partition.MessageChannelPartitionHandler; +import org.springframework.batch.integration.partition.StepExecutionRequest; +import org.springframework.batch.integration.partition.StepExecutionRequestHandler; /** * AOT hints for Spring Batch integration module. @@ -32,12 +35,16 @@ public class IntegrationRuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, ClassLoader classLoader) { // reflection hints - hints.reflection().registerType(ChunkRequest.class, MemberCategory.values()); - hints.reflection().registerType(ChunkResponse.class, MemberCategory.values()); + MemberCategory[] memberCategories = MemberCategory.values(); + hints.reflection().registerType(ChunkRequest.class, memberCategories); + hints.reflection().registerType(ChunkResponse.class, memberCategories); + hints.reflection().registerType(StepExecutionRequestHandler.class, memberCategories); + hints.reflection().registerType(MessageChannelPartitionHandler.class, memberCategories); // serialization hints hints.serialization().registerType(ChunkRequest.class); hints.serialization().registerType(ChunkResponse.class); + hints.serialization().registerType(StepExecutionRequest.class); } } From ee4616593e67c4b27a78055c6662b21644eeb21c Mon Sep 17 00:00:00 2001 From: jinwoo-Bae Date: Sat, 2 Dec 2023 05:06:04 +0900 Subject: [PATCH 032/152] Add support for query hints in JPA Item Readers Enhanced `JpaCursorItemReader`, `JpaCursorItemReaderBuilder`, `JpaPagingItemReader`, and `JpaPagingItemReaderBuilder` with query hints configuration. The inclusion of query hints in both cursor and paging item readers improves query execution strategies, optimizing performance for complex data retrieval scenarios. Resolves #4479 Signed-off-by: Fabrice Bibonne --- .../item/database/JpaCursorItemReader.java | 20 ++++++++++++++++++- .../item/database/JpaPagingItemReader.java | 20 ++++++++++++++++++- .../builder/JpaCursorItemReaderBuilder.java | 20 ++++++++++++++++++- .../builder/JpaPagingItemReaderBuilder.java | 19 +++++++++++++++++- 4 files changed, 75 insertions(+), 4 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JpaCursorItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JpaCursorItemReader.java index 89324c5c57..aafdc63eed 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JpaCursorItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JpaCursorItemReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 the original author or authors. + * Copyright 2020-2024 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. @@ -43,6 +43,7 @@ * The implementation is not thread-safe. * * @author Mahmoud Ben Hassine + * @author Jinwoo Bae * @param type of items to read * @since 4.3 */ @@ -58,6 +59,8 @@ public class JpaCursorItemReader extends AbstractItemCountingItemStreamItemRe private Map parameterValues; + private Map hintValues; + private Iterator iterator; /** @@ -100,6 +103,17 @@ public void setParameterValues(Map parameterValues) { this.parameterValues = parameterValues; } + /** + * Set the query hint values for the JPA query. Query hints can be used to give + * instructions to the JPA provider. + * @param hintValues a map where each key is the name of the hint, and the + * corresponding value is the hint's value. + * @since 5.2 + */ + public void setHintValues(Map hintValues) { + this.hintValues = hintValues; + } + @Override public void afterPropertiesSet() throws Exception { Assert.state(this.entityManagerFactory != null, "EntityManagerFactory is required"); @@ -123,6 +137,10 @@ protected void doOpen() throws Exception { if (this.parameterValues != null) { this.parameterValues.forEach(query::setParameter); } + if (this.hintValues != null) { + this.hintValues.forEach(query::setHint); + } + this.iterator = query.getResultStream().iterator(); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JpaPagingItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JpaPagingItemReader.java index 1d33e9a6f8..d99d3c9245 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JpaPagingItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JpaPagingItemReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -80,6 +80,7 @@ * @author Dave Syer * @author Will Schipp * @author Mahmoud Ben Hassine + * @author Jinwoo Bae * @since 2.0 */ public class JpaPagingItemReader extends AbstractPagingItemReader { @@ -96,6 +97,8 @@ public class JpaPagingItemReader extends AbstractPagingItemReader { private Map parameterValues; + private Map hintValues; + private boolean transacted = true;// default value public JpaPagingItemReader() { @@ -128,6 +131,17 @@ public void setParameterValues(Map parameterValues) { this.parameterValues = parameterValues; } + /** + * Set the query hint values for the JPA query. Query hints can be used to give + * instructions to the JPA provider. + * @param hintValues a map where each key is the name of the hint, and the + * corresponding value is the hint's value. + * @since 5.2 + */ + public void setHintValues(Map hintValues) { + this.hintValues = hintValues; + } + /** * By default (true) the EntityTransaction will be started and committed around the * read. Can be overridden (false) in cases where the JPA implementation doesn't @@ -202,6 +216,10 @@ protected void doReadPage() { } } + if (this.hintValues != null) { + this.hintValues.forEach(query::setHint); + } + if (results == null) { results = new CopyOnWriteArrayList<>(); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JpaCursorItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JpaCursorItemReaderBuilder.java index 5a1c874fbf..571a5b0a4f 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JpaCursorItemReaderBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JpaCursorItemReaderBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 the original author or authors. + * Copyright 2020-2024 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. @@ -30,6 +30,7 @@ * Builder for {@link JpaCursorItemReader}. * * @author Mahmoud Ben Hassine + * @author Jinwoo Bae * @since 4.3 */ public class JpaCursorItemReaderBuilder { @@ -42,6 +43,8 @@ public class JpaCursorItemReaderBuilder { private Map parameterValues; + private Map hintValues; + private boolean saveState = true; private String name; @@ -112,6 +115,19 @@ public JpaCursorItemReaderBuilder parameterValues(Map paramet return this; } + /** + * A map of hint values to be set on the query. The key of the map is the name of the + * hint to be applied, with the value being the specific setting for that hint. + * @param hintValues map of query hints + * @return this instance for method chaining + * @see JpaCursorItemReader#setHintValues(Map) + * @since 5.2 + */ + public JpaCursorItemReaderBuilder hintValues(Map hintValues) { + this.hintValues = hintValues; + return this; + } + /** * A query provider. This should be set only if {@link #queryString(String)} have not * been set. @@ -169,10 +185,12 @@ public JpaCursorItemReader build() { reader.setQueryProvider(this.queryProvider); reader.setQueryString(this.queryString); reader.setParameterValues(this.parameterValues); + reader.setHintValues(this.hintValues); reader.setCurrentItemCount(this.currentItemCount); reader.setMaxItemCount(this.maxItemCount); reader.setSaveState(this.saveState); reader.setName(this.name); + return reader; } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JpaPagingItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JpaPagingItemReaderBuilder.java index adf62a5d87..0bb2a85c46 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JpaPagingItemReaderBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JpaPagingItemReaderBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021 the original author or authors. + * Copyright 2017-2024 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. @@ -27,6 +27,7 @@ * * @author Michael Minella * @author Glenn Renfro + * @author Jinwoo Bae * @since 4.0 */ @@ -38,6 +39,8 @@ public class JpaPagingItemReaderBuilder { private Map parameterValues; + private Map hintValues; + private boolean transacted = true; private String queryString; @@ -129,6 +132,19 @@ public JpaPagingItemReaderBuilder parameterValues(Map paramet return this; } + /** + * A map of hint values to be set on the query. The key of the map is the name of the + * hint to be applied, with the value being the specific setting for that hint. + * @param hintValues map of query hints + * @return this instance for method chaining + * @see JpaPagingItemReader#setHintValues(Map) + * @since 5.2 + */ + public JpaPagingItemReaderBuilder hintValues(Map hintValues) { + this.hintValues = hintValues; + return this; + } + /** * A query provider. This should be set only if {@link #queryString(String)} have not * been set. @@ -204,6 +220,7 @@ public JpaPagingItemReader build() { reader.setQueryString(this.queryString); reader.setPageSize(this.pageSize); reader.setParameterValues(this.parameterValues); + reader.setHintValues(this.hintValues); reader.setEntityManagerFactory(this.entityManagerFactory); reader.setQueryProvider(this.queryProvider); reader.setTransacted(this.transacted); From 9b92c4132fa52a61aa5b932f3127141270c178a8 Mon Sep 17 00:00:00 2001 From: Fabio Molignoni Date: Tue, 2 Apr 2024 20:37:20 +0200 Subject: [PATCH 033/152] Set default ignoreWarnings to true in JdbcCursorItemReaderBuilder Resolves #4570 Signed-off-by: Fabrice Bibonne --- .../database/builder/JdbcCursorItemReaderBuilder.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java index a747228fa0..980ae932c6 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 the original author or authors. + * Copyright 2016-2024 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. @@ -37,6 +37,7 @@ * @author Mahmoud Ben Hassine * @author Ankur Trapasiya * @author Parikshit Dutta + * @author Fabio Molignoni * @since 4.0 */ public class JdbcCursorItemReaderBuilder { @@ -49,7 +50,7 @@ public class JdbcCursorItemReaderBuilder { private int queryTimeout = AbstractCursorItemReader.VALUE_NOT_SET; - private boolean ignoreWarnings; + private boolean ignoreWarnings = true; private boolean verifyCursorPosition = true; @@ -172,6 +173,11 @@ public JdbcCursorItemReaderBuilder queryTimeout(int queryTimeout) { return this; } + /** + * Set whether SQLWarnings should be ignored (only logged) or exception should be + * thrown. Defaults to {@code true}. + * @param ignoreWarnings if {@code true}, warnings are ignored + */ public JdbcCursorItemReaderBuilder ignoreWarnings(boolean ignoreWarnings) { this.ignoreWarnings = ignoreWarnings; From d67f4bd2f4456ca08c0f8c6df07b12ad5b528a27 Mon Sep 17 00:00:00 2001 From: Jimmy Praet Date: Sat, 2 Mar 2024 22:41:51 +0100 Subject: [PATCH 034/152] Implement jumpToItem in JsonItemReader Issue #4557 Signed-off-by: Fabrice Bibonne --- .../batch/item/json/GsonJsonObjectReader.java | 10 +++++++- .../item/json/JacksonJsonObjectReader.java | 12 +++++++++- .../batch/item/json/JsonItemReader.java | 8 ++++++- .../batch/item/json/JsonObjectReader.java | 18 ++++++++++++++- .../json/JsonItemReaderFunctionalTests.java | 23 ++++++++++++++++++- 5 files changed, 66 insertions(+), 5 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/GsonJsonObjectReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/GsonJsonObjectReader.java index 48787430f1..c1b49fb4bd 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/GsonJsonObjectReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/GsonJsonObjectReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2024 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. @@ -37,6 +37,7 @@ * * @param type of the target object * @author Mahmoud Ben Hassine + * @author Jimmy Praet * @since 4.1 */ public class GsonJsonObjectReader implements JsonObjectReader { @@ -102,4 +103,11 @@ public void close() throws Exception { this.jsonReader.close(); } + @Override + public void jumpToItem(int itemIndex) throws Exception { + for (int i = 0; i < itemIndex; i++) { + this.jsonReader.skipValue(); + } + } + } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/JacksonJsonObjectReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/JacksonJsonObjectReader.java index 04d7a7b970..df1879240c 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/JacksonJsonObjectReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/JacksonJsonObjectReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2021 the original author or authors. + * Copyright 2018-2024 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. @@ -34,6 +34,7 @@ * * @param type of the target object * @author Mahmoud Ben Hassine + * @author Jimmy Praet * @since 4.1 */ public class JacksonJsonObjectReader implements JsonObjectReader { @@ -98,4 +99,13 @@ public void close() throws Exception { this.jsonParser.close(); } + @Override + public void jumpToItem(int itemIndex) throws Exception { + for (int i = 0; i < itemIndex; i++) { + if (this.jsonParser.nextToken() == JsonToken.START_OBJECT) { + this.jsonParser.skipChildren(); + } + } + } + } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/JsonItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/JsonItemReader.java index c39f9886ea..a7fdc830f1 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/JsonItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/JsonItemReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 the original author or authors. + * Copyright 2018-2024 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. @@ -47,6 +47,7 @@ * * @param the type of json objects to read * @author Mahmoud Ben Hassine + * @author Jimmy Praet * @since 4.1 */ public class JsonItemReader extends AbstractItemCountingItemStreamItemReader @@ -136,4 +137,9 @@ protected void doClose() throws Exception { this.jsonObjectReader.close(); } + @Override + protected void jumpToItem(int itemIndex) throws Exception { + this.jsonObjectReader.jumpToItem(itemIndex); + } + } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/JsonObjectReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/JsonObjectReader.java index 5793d2e092..d143b71c8d 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/JsonObjectReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/json/JsonObjectReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2018-2024 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. @@ -25,6 +25,7 @@ * * @param type of the target object * @author Mahmoud Ben Hassine + * @author Jimmy Praet * @since 4.1 */ public interface JsonObjectReader { @@ -54,4 +55,19 @@ default void close() throws Exception { } + /** + * Move to the given item index. Implementations should override this method if there + * is a more efficient way of moving to given index than re-reading the input using + * {@link #read()}. + * @param itemIndex index of item (0 based) to jump to. + * @throws Exception Allows implementations to throw checked exceptions for + * interpretation by the framework + * @since 5.2 + */ + default void jumpToItem(int itemIndex) throws Exception { + for (int i = 0; i < itemIndex; i++) { + read(); + } + } + } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/JsonItemReaderFunctionalTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/JsonItemReaderFunctionalTests.java index 665356f1c1..7b6561f45f 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/JsonItemReaderFunctionalTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/json/JsonItemReaderFunctionalTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2018-2022 the original author or authors. + * Copyright 2018-2024 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. @@ -130,4 +130,25 @@ void testInvalidResourceContent() { assertTrue(getJsonParsingException().isInstance(expectedException.getCause())); } + @Test + void testJumpToItem() throws Exception { + // given + JsonItemReader itemReader = new JsonItemReaderBuilder().jsonObjectReader(getJsonObjectReader()) + .resource(new ClassPathResource("org/springframework/batch/item/json/trades.json")) + .name("tradeJsonItemReader") + .build(); + itemReader.open(new ExecutionContext()); + + // when + itemReader.jumpToItem(3); + + // then + Trade trade = itemReader.read(); + assertNotNull(trade); + assertEquals("100", trade.getIsin()); + assertEquals("barfoo", trade.getCustomer()); + assertEquals(new BigDecimal("1.8"), trade.getPrice()); + assertEquals(4, trade.getQuantity()); + } + } From 0609fe455acb81bb7c7f180808a36e29cab22001 Mon Sep 17 00:00:00 2001 From: CNJingo Date: Fri, 22 Dec 2023 19:25:24 +0900 Subject: [PATCH 035/152] Set step end time before calling StepExecutionListener#afterStep Resolves #3846 Signed-off-by: Fabrice Bibonne --- .../batch/core/step/AbstractStep.java | 19 ++++--- .../batch/core/step/AbstractStepTests.java | 57 +++++++++++++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java index 9ac094dc4d..c2339b95df 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -63,6 +63,7 @@ * @author Michael Minella * @author Chris Schaefer * @author Mahmoud Ben Hassine + * @author Jinwoo Bae */ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAware { @@ -261,7 +262,13 @@ public final void execute(StepExecution stepExecution) } } finally { - + stepExecution.setEndTime(LocalDateTime.now()); + Duration stepExecutionDuration = BatchMetrics.calculateDuration(stepExecution.getStartTime(), + stepExecution.getEndTime()); + if (logger.isInfoEnabled()) { + logger.info("Step: [" + stepExecution.getStepName() + "] executed in " + + BatchMetrics.formatDuration(stepExecutionDuration)); + } try { // Update the step execution to the latest known value so the // listeners can act on it @@ -287,14 +294,8 @@ public final void execute(StepExecution stepExecution) name, stepExecution.getJobExecution().getJobInstance().getJobName()), e); } stopObservation(stepExecution, observation); - stepExecution.setEndTime(LocalDateTime.now()); stepExecution.setExitStatus(exitStatus); - Duration stepExecutionDuration = BatchMetrics.calculateDuration(stepExecution.getStartTime(), - stepExecution.getEndTime()); - if (logger.isInfoEnabled()) { - logger.info("Step: [" + stepExecution.getStepName() + "] executed in " - + BatchMetrics.formatDuration(stepExecutionDuration)); - } + try { getJobRepository().update(stepExecution); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java new file mode 100644 index 0000000000..56e11f5b79 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java @@ -0,0 +1,57 @@ +package org.springframework.batch.core.step; + +import org.junit.jupiter.api.Test; +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.core.StepExecutionListener; +import org.springframework.batch.core.repository.JobRepository; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link AbstractStep}. + */ +class AbstractStepTests { + + private AbstractStep tested; + + + StepExecution execution = new StepExecution("foo", + new JobExecution(new JobInstance(1L, "bar"), new JobParameters())); + + @Test + void testSetEndTime() throws Exception { + tested = new AbstractStep() { + @Override + protected void doExecute(StepExecution stepExecution) { + } + }; + + JobRepository jobRepository = mock(); + + final List stepEndTime = new ArrayList<>(); + + tested.setStepExecutionListeners(new StepExecutionListener[] { new StepExecutionListener() { + @Override + public ExitStatus afterStep(StepExecution stepExecution) { + stepEndTime.add(stepExecution.getEndTime()); + return ExitStatus.COMPLETED; + } + } }); + + tested.setJobRepository(jobRepository); + tested.execute(execution); + + assertEquals(1, stepEndTime.size()); + assertNotNull(stepEndTime.get(0)); + } +} From c59f9a20b3ee86dc15a477e603a2e3ab50e58fb6 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 18 Apr 2024 16:23:06 +0200 Subject: [PATCH 036/152] Refine contribution #4522 * Update tests Signed-off-by: Fabrice Bibonne --- .../batch/core/step/AbstractStepTests.java | 77 ++++++++++++------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java index 56e11f5b79..8d761fa197 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/AbstractStepTests.java @@ -1,6 +1,24 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.batch.core.step; +import java.time.LocalDateTime; + import org.junit.jupiter.api.Test; + import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInstance; @@ -9,11 +27,6 @@ import org.springframework.batch.core.StepExecutionListener; import org.springframework.batch.core.repository.JobRepository; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.mock; @@ -22,36 +35,42 @@ */ class AbstractStepTests { - private AbstractStep tested; + @Test + void testEndTimeInListener() throws Exception { + // given + StepExecution execution = new StepExecution("step", + new JobExecution(new JobInstance(1L, "job"), new JobParameters())); + AbstractStep tested = new AbstractStep() { + @Override + protected void doExecute(StepExecution stepExecution) { + } + }; + JobRepository jobRepository = mock(); + Listener stepListener = new Listener(); + tested.setStepExecutionListeners(new StepExecutionListener[] { stepListener }); + tested.setJobRepository(jobRepository); + // when + tested.execute(execution); - StepExecution execution = new StepExecution("foo", - new JobExecution(new JobInstance(1L, "bar"), new JobParameters())); + // then + assertNotNull(stepListener.getStepEndTime()); + } - @Test - void testSetEndTime() throws Exception { - tested = new AbstractStep() { - @Override - protected void doExecute(StepExecution stepExecution) { - } - }; + static class Listener implements StepExecutionListener { - JobRepository jobRepository = mock(); + private LocalDateTime stepEndTime; - final List stepEndTime = new ArrayList<>(); + @Override + public ExitStatus afterStep(StepExecution stepExecution) { + this.stepEndTime = stepExecution.getEndTime(); + return ExitStatus.COMPLETED; + } - tested.setStepExecutionListeners(new StepExecutionListener[] { new StepExecutionListener() { - @Override - public ExitStatus afterStep(StepExecution stepExecution) { - stepEndTime.add(stepExecution.getEndTime()); - return ExitStatus.COMPLETED; - } - } }); + public LocalDateTime getStepEndTime() { + return this.stepEndTime; + } - tested.setJobRepository(jobRepository); - tested.execute(execution); + } - assertEquals(1, stepEndTime.size()); - assertNotNull(stepEndTime.get(0)); - } } From d59c23b5e6ea668a073cd6fe0e1913805b9a61f1 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 22 Apr 2024 13:06:28 +0200 Subject: [PATCH 037/152] Update dependencies Signed-off-by: Fabrice Bibonne --- pom.xml | 92 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/pom.xml b/pom.xml index a328cc16b3..499860ad0d 100644 --- a/pom.xml +++ b/pom.xml @@ -61,73 +61,73 @@ 17 - 6.1.4-SNAPSHOT - 2.0.5-SNAPSHOT - 6.2.2-SNAPSHOT - 1.12.3-SNAPSHOT + 6.2.0-SNAPSHOT + 2.0.6-SNAPSHOT + 6.3.0-SNAPSHOT + 1.12.6-SNAPSHOT - 3.2.3-SNAPSHOT - 3.2.3-SNAPSHOT - 3.2.3-SNAPSHOT - 4.2.3-SNAPSHOT - 3.1.2-SNAPSHOT - 3.1.2-SNAPSHOT - 3.2.2-SNAPSHOT + 3.3.0-SNAPSHOT + 3.3.0-SNAPSHOT + 3.3.0-SNAPSHOT + 4.3.0-SNAPSHOT + 3.2.0-SNAPSHOT + 3.1.5-SNAPSHOT + 3.2.4-SNAPSHOT - 2.15.3 + 2.17.0 1.11.3 2.10.1 - 6.3.1.Final + 6.5.0.CR2 2.1.1 - 2.1.2 + 2.1.3 3.1.0 3.0.2 3.1.0 - 4.0.8 - 4.11.1 - 5.10.1 + 4.0.10 + 5.0.1 + 5.10.2 3.0.2 - 1.2.3-SNAPSHOT + 1.2.6-SNAPSHOT 1.4.20 4.13.2 ${junit-jupiter.version} 2.2 - 3.24.2 - 5.7.0 + 3.25.3 + 5.11.0 2.9.1 - 2.15.0 - 2.11.0 - 2.0.9 + 2.16.1 + 2.12.0 + 2.0.12 2.7.2 2.2.224 - 3.44.0.0 + 3.45.3.0 10.16.1.1 2.18.13 - 2.31.2 - 4.0.4 - 2.22.0 + 2.33.0 + 4.0.5 + 2.23.1 8.0.1.Final 5.0.1 4.0.2 2.0.1 - 4.0.1 - 2.0.2 - 6.5.1 - 1.9.20.1 - 8.2.0 - 3.3.0 - 42.7.0 - 11.5.8.0 - 19.21.0.0 + 4.0.2 + 2.0.3 + 6.6.2 + 1.9.22 + 8.3.0 + 3.3.3 + 42.7.3 + 11.5.9.0 + 19.22.0.0 11.2.3.jre17 1.3.1 - 1.19.3 + 1.19.7 1.5.1 @@ -140,16 +140,16 @@ 0.0.4 - 3.11.0 - 3.1.2 - 3.1.2 - 3.6.0 - 3.3.0 - 0.8.10 - 1.5.0 + 3.13.0 + 3.2.5 + 3.2.5 + 3.6.3 + 3.3.1 + 0.8.12 + 1.6.0 3.1.1 - 3.6.0 - 3.3.0 + 3.7.1 + 3.4.1 0.0.39 From 8578bb919dca782bc308ecb1b85c307d661a42f3 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Thu, 28 Dec 2023 02:12:11 +0100 Subject: [PATCH 038/152] Fix SimpleBinaryBufferedReaderFactory for longer endings Resolves #811 Signed-off-by: Fabrice Bibonne --- .../SimpleBinaryBufferedReaderFactory.java | 19 ++++++++------- ...impleBinaryBufferedReaderFactoryTests.java | 23 +++++++++++++++---- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactory.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactory.java index dba352efff..6b8fede984 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactory.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -123,25 +123,24 @@ private boolean isEndOfLine(StringBuilder buffer, StringBuilder candidate, int n } char c = (char) next; - if (ending.charAt(0) == c || candidate.length() > 0) { + if (ending.charAt(0) == c || !candidate.isEmpty()) { candidate.append(c); } - - if (candidate.length() == 0) { + else { buffer.append(c); return false; } - boolean end = ending.equals(candidate.toString()); - if (end) { + if (ending.contentEquals(candidate)) { candidate.delete(0, candidate.length()); + return true; } - else if (candidate.length() >= ending.length()) { - buffer.append(candidate); - candidate.delete(0, candidate.length()); + while (!ending.startsWith(candidate.toString())) { + buffer.append(candidate.charAt(0)); + candidate.delete(0, 1); } - return end; + return false; } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactoryTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactoryTests.java index 0a814af5aa..202fcc6476 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactoryTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/SimpleBinaryBufferedReaderFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -21,6 +21,8 @@ import java.io.BufferedReader; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.core.io.ByteArrayResource; /** @@ -75,16 +77,27 @@ void testCreateWithLineEndingAtEnd() throws Exception { assertNull(reader.readLine()); } - @Test - void testCreateWithFalseLineEnding() throws Exception { + @ParameterizedTest + @ValueSource(strings = { "||", "|||" }) + void testCreateWithFalseLineEnding(String lineEnding) throws Exception { SimpleBinaryBufferedReaderFactory factory = new SimpleBinaryBufferedReaderFactory(); - factory.setLineEnding("||"); + factory.setLineEnding(lineEnding); @SuppressWarnings("resource") - BufferedReader reader = factory.create(new ByteArrayResource("a|b||".getBytes()), "UTF-8"); + BufferedReader reader = factory.create(new ByteArrayResource(("a|b" + lineEnding).getBytes()), "UTF-8"); assertEquals("a|b", reader.readLine()); assertNull(reader.readLine()); } + @Test + void testCreateWithFalseMixedCharacterLineEnding() throws Exception { + SimpleBinaryBufferedReaderFactory factory = new SimpleBinaryBufferedReaderFactory(); + factory.setLineEnding("#@"); + @SuppressWarnings("resource") + BufferedReader reader = factory.create(new ByteArrayResource(("a##@").getBytes()), "UTF-8"); + assertEquals("a#", reader.readLine()); + assertNull(reader.readLine()); + } + @Test void testCreateWithIncompleteLineEnding() throws Exception { SimpleBinaryBufferedReaderFactory factory = new SimpleBinaryBufferedReaderFactory(); From a8e0510f2c52ac586e774dfc21fe5a29533363b2 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 23 Apr 2024 21:21:37 +0200 Subject: [PATCH 039/152] Upgrade micrometer to 1.13.0-SNAPSHOT Signed-off-by: Fabrice Bibonne --- pom.xml | 4 ++-- spring-batch-samples/pom.xml | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 499860ad0d..027103f934 100644 --- a/pom.xml +++ b/pom.xml @@ -64,7 +64,7 @@ 6.2.0-SNAPSHOT 2.0.6-SNAPSHOT 6.3.0-SNAPSHOT - 1.12.6-SNAPSHOT + 1.13.0-SNAPSHOT 3.3.0-SNAPSHOT @@ -92,7 +92,7 @@ 3.0.2 - 1.2.6-SNAPSHOT + 1.3.0-SNAPSHOT 1.4.20 4.13.2 diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 90e14a1e2d..49c229c025 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -52,9 +52,11 @@ spring-context-support ${spring-framework.version} + io.micrometer - micrometer-registry-prometheus + micrometer-registry-prometheus-simpleclient ${micrometer.version} From c53a3d6a4c5bce62bde5cbbd0a86fc9c652a0d0c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 24 Apr 2024 12:11:35 +0200 Subject: [PATCH 040/152] Fix command syntax in documentation deployment scripts Signed-off-by: Fabrice Bibonne --- .github/workflows/continuous-integration.yml | 2 +- .github/workflows/documentation-upload.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index ef81b3d739..3100a95115 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -53,5 +53,5 @@ jobs: working-directory: spring-batch-docs/target run: | unzip spring-batch-$PROJECT_VERSION-javadocs.zip - ssh -i $HOME/.ssh/key $DOCS_USERNAME@$DOCS_HOST cd $DOCS_PATH && mkdir -p $PROJECT_VERSION/api + ssh -i $HOME/.ssh/key $DOCS_USERNAME@$DOCS_HOST "cd $DOCS_PATH && mkdir -p $PROJECT_VERSION/api" scp -i $HOME/.ssh/key -r api $DOCS_USERNAME@$DOCS_HOST:$DOCS_PATH/$PROJECT_VERSION/api diff --git a/.github/workflows/documentation-upload.yml b/.github/workflows/documentation-upload.yml index 7f72b7bf1a..2f099ef046 100644 --- a/.github/workflows/documentation-upload.yml +++ b/.github/workflows/documentation-upload.yml @@ -55,7 +55,7 @@ jobs: working-directory: spring-batch-docs/target run: | unzip spring-batch-$RELEASE_VERSION-javadocs.zip - ssh -i $HOME/.ssh/key $DOCS_USERNAME@$DOCS_HOST cd $DOCS_PATH && mkdir -p $RELEASE_VERSION/api + ssh -i $HOME/.ssh/key $DOCS_USERNAME@$DOCS_HOST "cd $DOCS_PATH && mkdir -p $RELEASE_VERSION/api" scp -i $HOME/.ssh/key -r api $DOCS_USERNAME@$DOCS_HOST:$DOCS_PATH/$RELEASE_VERSION/api unzip spring-batch-$RELEASE_VERSION-schemas.zip From 195b54e3eb9cad4d1bfb7f7d6f633232ee6d1f56 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 24 Apr 2024 15:02:33 +0200 Subject: [PATCH 041/152] Remove deprecated APIs scheduled for removal in v5.2 Resolves #4398 Signed-off-by: Fabrice Bibonne --- .../batch/core/JobParameters.java | 22 +- .../batch/core/JobParametersBuilder.java | 16 +- .../annotation/JobBuilderFactory.java | 58 ---- .../annotation/StepBuilderFactory.java | 59 ---- .../batch/core/launch/JobOperator.java | 20 +- .../launch/support/SimpleJobLauncher.java | 238 --------------- .../launch/support/SimpleJobOperator.java | 10 +- .../support/TaskExecutorJobLauncher.java | 179 ++++++++++- .../JobRegistryBackgroundJobRunnerTests.java | 73 ----- .../modules/ROOT/pages/appendix.adoc | 7 - .../pages/readers-and-writers/database.adoc | 83 ----- .../item-reader-writer-implementations.adoc | 25 -- .../batch/item/ItemStreamSupport.java | 35 +-- .../database/HibernateCursorItemReader.java | 205 ------------- .../database/HibernateItemReaderHelper.java | 228 -------------- .../item/database/HibernateItemWriter.java | 124 -------- .../database/HibernatePagingItemReader.java | 166 ---------- .../HibernateCursorItemReaderBuilder.java | 288 ------------------ .../builder/HibernateItemWriterBuilder.java | 78 ----- .../HibernatePagingItemReaderBuilder.java | 260 ---------------- .../orm/AbstractHibernateQueryProvider.java | 71 ----- .../orm/AbstractJpaQueryProvider.java | 6 +- .../orm/HibernateNativeQueryProvider.java | 76 ----- .../database/orm/HibernateQueryProvider.java | 76 ----- .../batch/support/annotation/Classifier.java | 41 --- ...rnateCursorItemReaderIntegrationTests.java | 68 ----- .../HibernateCursorItemReaderCommonTests.java | 65 ---- ...rnateCursorItemReaderIntegrationTests.java | 47 --- ...rItemReaderNamedQueryIntegrationTests.java | 31 -- ...ItemReaderNativeQueryIntegrationTests.java | 44 --- ...rItemReaderParametersIntegrationTests.java | 39 --- ...sorItemReaderStatefulIntegrationTests.java | 63 ---- ...derStatefulNamedQueryIntegrationTests.java | 30 -- ...rProjectionItemReaderIntegrationTests.java | 88 ------ .../HibernateItemReaderHelperTests.java | 71 ----- .../database/HibernateItemWriterTests.java | 127 -------- ...rnatePagingItemReaderIntegrationTests.java | 68 ----- ...HibernateCursorItemReaderBuilderTests.java | 243 --------------- .../HibernateItemWriterBuilderTests.java | 108 ------- ...HibernatePagingItemReaderBuilderTests.java | 229 -------------- ...teNativeQueryProviderIntegrationTests.java | 97 ------ .../HibernateNativeQueryProviderTests.java | 82 ----- .../batch/integration/step/package-info.java | 25 -- .../batch/integration/step/DelegateStep.java | 5 +- ...ibernateAwareCustomerCreditItemWriter.java | 72 ----- .../trade/internal/HibernateCreditDao.java | 102 ------- .../jmx}/JobRegistryBackgroundJobRunner.java | 4 +- .../quartz/quartz-job-launcher-context.xml | 2 +- .../samples/misc/jmx/RemoteLauncherTests.java | 3 +- .../batch/test/AssertFile.java | 92 ------ .../batch/test/DataSourceInitializer.java | 206 ------------- .../batch/test/MetaDataInstanceFactory.java | 20 +- .../batch/test/AssertFileTests.java | 112 ------- .../test/MetaDataInstanceFactoryTests.java | 8 +- 54 files changed, 183 insertions(+), 4412 deletions(-) delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/JobBuilderFactory.java delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepBuilderFactory.java delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java delete mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunnerTests.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernateCursorItemReader.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernateItemReaderHelper.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernateItemWriter.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernatePagingItemReader.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/HibernateCursorItemReaderBuilder.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/HibernateItemWriterBuilder.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/HibernatePagingItemReaderBuilder.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/AbstractHibernateQueryProvider.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/HibernateNativeQueryProvider.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/HibernateQueryProvider.java delete mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/support/annotation/Classifier.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/AbstractHibernateCursorItemReaderIntegrationTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderCommonTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderIntegrationTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderNamedQueryIntegrationTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderNativeQueryIntegrationTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderParametersIntegrationTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderStatefulIntegrationTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderStatefulNamedQueryIntegrationTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorProjectionItemReaderIntegrationTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateItemReaderHelperTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateItemWriterTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernatePagingItemReaderIntegrationTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/HibernateCursorItemReaderBuilderTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/HibernateItemWriterBuilderTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/HibernatePagingItemReaderBuilderTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HibernateNativeQueryProviderIntegrationTests.java delete mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HibernateNativeQueryProviderTests.java delete mode 100644 spring-batch-integration/src/main/java/org/springframework/batch/integration/step/package-info.java rename spring-batch-integration/src/{main => test}/java/org/springframework/batch/integration/step/DelegateStep.java (92%) delete mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/internal/HibernateAwareCustomerCreditItemWriter.java delete mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/internal/HibernateCreditDao.java rename {spring-batch-core/src/main/java/org/springframework/batch/core/launch/support => spring-batch-samples/src/main/java/org/springframework/batch/samples/misc/jmx}/JobRegistryBackgroundJobRunner.java (98%) delete mode 100644 spring-batch-test/src/main/java/org/springframework/batch/test/AssertFile.java delete mode 100755 spring-batch-test/src/main/java/org/springframework/batch/test/DataSourceInitializer.java delete mode 100644 spring-batch-test/src/test/java/org/springframework/batch/test/AssertFileTests.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java index 8f001e84c7..8a22407d9b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -378,24 +378,4 @@ public String toString() { return new StringBuilder("{").append(String.join(",", parameters)).append("}").toString(); } - /** - * @return The {@link Properties} that contain the key and values for the - * {@link JobParameter} objects. - * @deprecated since 5.0, scheduled for removal in 5.2. Use - * {@link org.springframework.batch.core.converter.JobParametersConverter#getProperties(JobParameters)} - * - */ - @Deprecated(since = "5.0", forRemoval = true) - public Properties toProperties() { - Properties props = new Properties(); - - for (Map.Entry> param : parameters.entrySet()) { - if (param.getValue() != null) { - props.put(param.getKey(), Objects.toString(param.getValue().toString(), "")); - } - } - - return props; - } - } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java index 3450f4894a..c4af64719f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -256,20 +256,6 @@ public JobParameters toJobParameters() { return new JobParameters(this.parameterMap); } - /** - * Add a new {@link JobParameter} for the given key. - * @param key The parameter accessor. - * @param jobParameter The runtime parameter. - * @return a reference to this object. - * @deprecated since 5.0, scheduled for removal in 5.2. Use {@link #addJobParameter}. - */ - @Deprecated(since = "5.0", forRemoval = true) - public JobParametersBuilder addParameter(String key, JobParameter jobParameter) { - Assert.notNull(jobParameter, "JobParameter must not be null"); - this.parameterMap.put(key, jobParameter); - return this; - } - /** * Add a new {@link JobParameter} for the given key. * @param key The parameter accessor. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/JobBuilderFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/JobBuilderFactory.java deleted file mode 100644 index df3b411326..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/JobBuilderFactory.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.core.configuration.annotation; - -import org.springframework.batch.core.job.builder.JobBuilder; -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.util.Assert; - -/** - * Convenient factory for a {@link JobBuilder} that sets the {@link JobRepository} - * automatically. - * - * @author Dave Syer - * @author Mahmoud Ben Hassine - * @author Jinho Han - * @deprecated Deprecated as of v5.0 and scheduled for removal in v5.2 in favor of using - * the {@link JobBuilder}. - * - */ -@Deprecated(since = "5.0.0", forRemoval = true) -public class JobBuilderFactory { - - private final JobRepository jobRepository; - - /** - * @param jobRepository The {@link JobRepository} to be used by the builder factory. - * Must not be {@code null}. - */ - public JobBuilderFactory(JobRepository jobRepository) { - Assert.notNull(jobRepository, "JobRepository must not be null"); - this.jobRepository = jobRepository; - } - - /** - * Creates a job builder and initializes its job repository. Note that, if the builder - * is used to create a @Bean definition, the name of the job and the bean name - * might be different. - * @param name the name of the job - * @return a job builder - */ - public JobBuilder get(String name) { - return new JobBuilder(name, this.jobRepository); - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepBuilderFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepBuilderFactory.java deleted file mode 100644 index 2354f154ee..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/StepBuilderFactory.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2012-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.core.configuration.annotation; - -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.step.builder.StepBuilder; -import org.springframework.util.Assert; - -/** - * Convenient factory for a {@link StepBuilder} which sets the {@link JobRepository} - * automatically. - * - * @author Dave Syer - * @author Mahmoud Ben Hassine - * @author Jinho Han - * @deprecated Deprecated as of v5.0 and scheduled for removal in v5.2 in favor of using - * the {@link StepBuilder}. - * - */ -@Deprecated(since = "5.0.0", forRemoval = true) -public class StepBuilderFactory { - - private final JobRepository jobRepository; - - /** - * Constructor for the {@link StepBuilderFactory}. - * @param jobRepository The {@link JobRepository} to be used by the builder factory. - * Must not be {@code null}. - */ - public StepBuilderFactory(JobRepository jobRepository) { - Assert.notNull(jobRepository, "JobRepository must not be null"); - this.jobRepository = jobRepository; - } - - /** - * Creates a step builder and initializes its job repository. Note that, if the - * builder is used to create a @Bean definition, the name of the step and the bean - * name might be different. - * @param name the name of the step - * @return a step builder - */ - public StepBuilder get(String name) { - return new StepBuilder(name, this.jobRepository); - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobOperator.java index 768947859d..a56947412b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobOperator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobOperator.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -101,24 +101,6 @@ default JobInstance getJobInstance(String jobName, JobParameters jobParameters) */ String getParameters(long executionId) throws NoSuchJobExecutionException; - /** - * Start a new instance of a job with the parameters specified. - * @param jobName the name of the {@link Job} to launch - * @param parameters the parameters to launch it with (new line separated key=value - * pairs) - * @return the id of the {@link JobExecution} that is launched - * @throws NoSuchJobException if there is no {@link Job} with the specified name - * @throws JobInstanceAlreadyExistsException if a job instance with this name and - * parameters already exists - * @throws JobParametersInvalidException thrown if any of the job parameters are - * invalid. - * @deprecated use {@link #start(String, Properties)} instead. Will be removed in - * v5.2. - */ - @Deprecated(since = "5.0.1", forRemoval = true) - Long start(String jobName, String parameters) - throws NoSuchJobException, JobInstanceAlreadyExistsException, JobParametersInvalidException; - /** * Start a new instance of a job with the parameters specified. * @param jobName the name of the {@link Job} to launch diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java deleted file mode 100644 index 6369c092b3..0000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright 2006-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.core.launch.support; - -import java.time.Duration; - -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Metrics; -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.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobInstance; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersInvalidException; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.observability.BatchMetrics; -import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; -import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.repository.JobRestartException; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.core.task.SyncTaskExecutor; -import org.springframework.core.task.TaskExecutor; -import org.springframework.core.task.TaskRejectedException; -import org.springframework.util.Assert; - -/** - * Simple implementation of the {@link JobLauncher} interface. The Spring Core - * {@link TaskExecutor} interface is used to launch a {@link Job}. This means that the - * type of executor set is very important. If a {@link SyncTaskExecutor} is used, then the - * job will be processed within the same thread that called the launcher. - * Care should be taken to ensure any users of this class understand fully whether or not - * the implementation of TaskExecutor used will start tasks synchronously or - * asynchronously. The default setting uses a synchronous task executor. - *

    - * There is only one required dependency of this Launcher, a {@link JobRepository}. The - * JobRepository is used to obtain a valid JobExecution. The Repository must be used - * because the provided {@link Job} could be a restart of an existing {@link JobInstance}, - * and only the Repository can reliably recreate it. - * - * @author Lucas Ward - * @author Dave Syer - * @author Will Schipp - * @author Michael Minella - * @author Mahmoud Ben Hassine - * @since 1.0 - * @see JobRepository - * @see TaskExecutor - * @deprecated Since v5.0.0 for removal in v5.2.0. Use {@link TaskExecutorJobLauncher}. - */ -@Deprecated(since = "5.0.0", forRemoval = true) -public class SimpleJobLauncher implements JobLauncher, InitializingBean { - - protected static final Log logger = LogFactory.getLog(SimpleJobLauncher.class); - - private JobRepository jobRepository; - - private TaskExecutor taskExecutor; - - private MeterRegistry meterRegistry = Metrics.globalRegistry; - - private Counter jobLaunchCount; // NoopCounter is still incubating - - /** - * Run the provided job with the given {@link JobParameters}. The - * {@link JobParameters} will be used to determine if this is an execution of an - * existing job instance, or if a new one should be created. - * @param job the job to be run. - * @param jobParameters the {@link JobParameters} for this particular execution. - * @return the {@link JobExecution} if it returns synchronously. If the implementation - * is asynchronous, the status might well be unknown. - * @throws JobExecutionAlreadyRunningException if the JobInstance already exists and - * has an execution already running. - * @throws JobRestartException if the execution would be a re-start, but a re-start is - * either not allowed or not needed. - * @throws JobInstanceAlreadyCompleteException if this instance has already completed - * successfully - * @throws JobParametersInvalidException thrown if jobParameters is invalid. - */ - @Override - public JobExecution run(final Job job, final JobParameters jobParameters) - throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, - JobParametersInvalidException { - - Assert.notNull(job, "The Job must not be null."); - Assert.notNull(jobParameters, "The JobParameters must not be null."); - if (this.jobLaunchCount != null) { - this.jobLaunchCount.increment(); - } - - final JobExecution jobExecution; - JobExecution lastExecution = jobRepository.getLastJobExecution(job.getName(), jobParameters); - if (lastExecution != null) { - if (!job.isRestartable()) { - throw new JobRestartException("JobInstance already exists and is not restartable"); - } - /* - * validate here if it has stepExecutions that are UNKNOWN, STARTING, STARTED - * and STOPPING retrieve the previous execution and check - */ - for (StepExecution execution : lastExecution.getStepExecutions()) { - BatchStatus status = execution.getStatus(); - if (status.isRunning()) { - throw new JobExecutionAlreadyRunningException( - "A job execution for this job is already running: " + lastExecution); - } - else if (status == BatchStatus.UNKNOWN) { - throw new JobRestartException( - "Cannot restart step [" + execution.getStepName() + "] from UNKNOWN status. " - + "The last execution ended with a failure that could not be rolled back, " - + "so it may be dangerous to proceed. Manual intervention is probably necessary."); - } - } - } - - // Check the validity of the parameters before doing creating anything - // in the repository... - job.getJobParametersValidator().validate(jobParameters); - - /* - * There is a very small probability that a non-restartable job can be restarted, - * but only if another process or thread manages to launch and fail a job - * execution for this instance between the last assertion and the next method - * returning successfully. - */ - jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters); - - try { - taskExecutor.execute(new Runnable() { - - @Override - public void run() { - try { - if (logger.isInfoEnabled()) { - logger.info("Job: [" + job + "] launched with the following parameters: [" + jobParameters - + "]"); - } - job.execute(jobExecution); - if (logger.isInfoEnabled()) { - Duration jobExecutionDuration = BatchMetrics.calculateDuration(jobExecution.getStartTime(), - jobExecution.getEndTime()); - logger.info("Job: [" + job + "] completed with the following parameters: [" + jobParameters - + "] and the following status: [" + jobExecution.getStatus() + "]" - + (jobExecutionDuration == null ? "" - : " in " + BatchMetrics.formatDuration(jobExecutionDuration))); - } - } - catch (Throwable t) { - if (logger.isInfoEnabled()) { - logger.info("Job: [" + job - + "] failed unexpectedly and fatally with the following parameters: [" - + jobParameters + "]", t); - } - rethrow(t); - } - } - - private void rethrow(Throwable t) { - if (t instanceof RuntimeException) { - throw (RuntimeException) t; - } - else if (t instanceof Error) { - throw (Error) t; - } - throw new IllegalStateException(t); - } - }); - } - catch (TaskRejectedException e) { - jobExecution.upgradeStatus(BatchStatus.FAILED); - if (jobExecution.getExitStatus().equals(ExitStatus.UNKNOWN)) { - jobExecution.setExitStatus(ExitStatus.FAILED.addExitDescription(e)); - } - jobRepository.update(jobExecution); - } - - return jobExecution; - } - - /** - * Set the JobRepository. - * @param jobRepository instance of {@link JobRepository}. - */ - public void setJobRepository(JobRepository jobRepository) { - this.jobRepository = jobRepository; - } - - /** - * Set the TaskExecutor. (Optional) - * @param taskExecutor instance of {@link TaskExecutor}. - */ - public void setTaskExecutor(TaskExecutor taskExecutor) { - this.taskExecutor = taskExecutor; - } - - /** - * Set the meter registry to use for metrics. Defaults to - * {@link Metrics#globalRegistry}. - * @param meterRegistry the meter registry - * @since 5.0 - */ - public void setMeterRegistry(MeterRegistry meterRegistry) { - this.meterRegistry = meterRegistry; - } - - /** - * Ensure the required dependencies of a {@link JobRepository} have been set. - */ - @Override - public void afterPropertiesSet() throws Exception { - Assert.state(jobRepository != null, "A JobRepository has not been set."); - if (taskExecutor == null) { - logger.info("No TaskExecutor has been set, defaulting to synchronous executor."); - taskExecutor = new SyncTaskExecutor(); - } - this.jobLaunchCount = BatchMetrics.createCounter(this.meterRegistry, "job.launch.count", "Job launch count"); - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java index f700e35e50..059e769960 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobOperator.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -253,14 +253,6 @@ public Long restart(long executionId) throws JobInstanceAlreadyCompleteException } - @Override - @Deprecated(since = "5.0.1", forRemoval = true) - public Long start(String jobName, String parameters) - throws NoSuchJobException, JobInstanceAlreadyExistsException, JobParametersInvalidException { - Properties properties = PropertiesConverter.stringToProperties(parameters); - return start(jobName, properties); - } - @Override public Long start(String jobName, Properties parameters) throws NoSuchJobException, JobInstanceAlreadyExistsException, JobParametersInvalidException { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobLauncher.java index fe003d3d44..aab3443cd5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobLauncher.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/TaskExecutorJobLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2023 the original author or authors. + * Copyright 2022-2024 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. @@ -15,18 +15,33 @@ */ package org.springframework.batch.core.launch.support; +import java.time.Duration; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Metrics; +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.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.JobParametersInvalidException; +import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.observability.BatchMetrics; import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.beans.factory.InitializingBean; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; +import org.springframework.core.task.TaskRejectedException; +import org.springframework.util.Assert; /** * Implementation of the {@link JobLauncher} interface based on a {@link TaskExecutor}. @@ -51,27 +66,171 @@ * @see JobRepository * @see TaskExecutor */ -public class TaskExecutorJobLauncher extends SimpleJobLauncher { +public class TaskExecutorJobLauncher implements JobLauncher, InitializingBean { + + protected static final Log logger = LogFactory.getLog(TaskExecutorJobLauncher.class); + + private JobRepository jobRepository; + + private TaskExecutor taskExecutor; + private MeterRegistry meterRegistry = Metrics.globalRegistry; + + private Counter jobLaunchCount; // NoopCounter is still incubating + + /** + * Run the provided job with the given {@link JobParameters}. The + * {@link JobParameters} will be used to determine if this is an execution of an + * existing job instance, or if a new one should be created. + * @param job the job to be run. + * @param jobParameters the {@link JobParameters} for this particular execution. + * @return the {@link JobExecution} if it returns synchronously. If the implementation + * is asynchronous, the status might well be unknown. + * @throws JobExecutionAlreadyRunningException if the JobInstance already exists and + * has an execution already running. + * @throws JobRestartException if the execution would be a re-start, but a re-start is + * either not allowed or not needed. + * @throws JobInstanceAlreadyCompleteException if this instance has already completed + * successfully + * @throws JobParametersInvalidException thrown if jobParameters is invalid. + */ @Override - public JobExecution run(Job job, JobParameters jobParameters) throws JobExecutionAlreadyRunningException, - JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException { - return super.run(job, jobParameters); + public JobExecution run(final Job job, final JobParameters jobParameters) + throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, + JobParametersInvalidException { + + Assert.notNull(job, "The Job must not be null."); + Assert.notNull(jobParameters, "The JobParameters must not be null."); + if (this.jobLaunchCount != null) { + this.jobLaunchCount.increment(); + } + + final JobExecution jobExecution; + JobExecution lastExecution = jobRepository.getLastJobExecution(job.getName(), jobParameters); + if (lastExecution != null) { + if (!job.isRestartable()) { + throw new JobRestartException("JobInstance already exists and is not restartable"); + } + /* + * validate here if it has stepExecutions that are UNKNOWN, STARTING, STARTED + * and STOPPING retrieve the previous execution and check + */ + for (StepExecution execution : lastExecution.getStepExecutions()) { + BatchStatus status = execution.getStatus(); + if (status.isRunning()) { + throw new JobExecutionAlreadyRunningException( + "A job execution for this job is already running: " + lastExecution); + } + else if (status == BatchStatus.UNKNOWN) { + throw new JobRestartException( + "Cannot restart step [" + execution.getStepName() + "] from UNKNOWN status. " + + "The last execution ended with a failure that could not be rolled back, " + + "so it may be dangerous to proceed. Manual intervention is probably necessary."); + } + } + } + + // Check the validity of the parameters before doing creating anything + // in the repository... + job.getJobParametersValidator().validate(jobParameters); + + /* + * There is a very small probability that a non-restartable job can be restarted, + * but only if another process or thread manages to launch and fail a job + * execution for this instance between the last assertion and the next method + * returning successfully. + */ + jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters); + + try { + taskExecutor.execute(new Runnable() { + + @Override + public void run() { + try { + if (logger.isInfoEnabled()) { + logger.info("Job: [" + job + "] launched with the following parameters: [" + jobParameters + + "]"); + } + job.execute(jobExecution); + if (logger.isInfoEnabled()) { + Duration jobExecutionDuration = BatchMetrics.calculateDuration(jobExecution.getStartTime(), + jobExecution.getEndTime()); + logger.info("Job: [" + job + "] completed with the following parameters: [" + jobParameters + + "] and the following status: [" + jobExecution.getStatus() + "]" + + (jobExecutionDuration == null ? "" + : " in " + BatchMetrics.formatDuration(jobExecutionDuration))); + } + } + catch (Throwable t) { + if (logger.isInfoEnabled()) { + logger.info("Job: [" + job + + "] failed unexpectedly and fatally with the following parameters: [" + + jobParameters + "]", t); + } + rethrow(t); + } + } + + private void rethrow(Throwable t) { + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + else if (t instanceof Error) { + throw (Error) t; + } + throw new IllegalStateException(t); + } + }); + } + catch (TaskRejectedException e) { + jobExecution.upgradeStatus(BatchStatus.FAILED); + if (jobExecution.getExitStatus().equals(ExitStatus.UNKNOWN)) { + jobExecution.setExitStatus(ExitStatus.FAILED.addExitDescription(e)); + } + jobRepository.update(jobExecution); + } + + return jobExecution; } - @Override + /** + * Set the JobRepository. + * @param jobRepository instance of {@link JobRepository}. + */ public void setJobRepository(JobRepository jobRepository) { - super.setJobRepository(jobRepository); + this.jobRepository = jobRepository; } - @Override + /** + * Set the TaskExecutor. (Optional) + * @param taskExecutor instance of {@link TaskExecutor}. + */ public void setTaskExecutor(TaskExecutor taskExecutor) { - super.setTaskExecutor(taskExecutor); + this.taskExecutor = taskExecutor; + } + + /** + * Set the meter registry to use for metrics. Defaults to + * {@link Metrics#globalRegistry}. + * @param meterRegistry the meter registry + * @since 5.0 + */ + public void setMeterRegistry(MeterRegistry meterRegistry) { + this.meterRegistry = meterRegistry; } + /** + * Ensure the required dependencies of a {@link JobRepository} have been set. + */ @Override public void afterPropertiesSet() throws Exception { - super.afterPropertiesSet(); + Assert.state(jobRepository != null, "A JobRepository has not been set."); + if (taskExecutor == null) { + logger.info("No TaskExecutor has been set, defaulting to synchronous executor."); + taskExecutor = new SyncTaskExecutor(); + } + this.jobLaunchCount = BatchMetrics.createCounter(this.meterRegistry, "job.launch.count", "Job launch count"); } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunnerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunnerTests.java deleted file mode 100644 index 6643bd60e6..0000000000 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunnerTests.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2006-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.core.launch.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.util.ClassUtils; - -/** - * @author Dave Syer - * - */ -class JobRegistryBackgroundJobRunnerTests { - - /** - * Test method for - * {@link org.springframework.batch.core.launch.support.JobRegistryBackgroundJobRunner#main(java.lang.String[])}. - */ - @Test - void testMain() throws Exception { - JobRegistryBackgroundJobRunner.main( - ClassUtils.addResourcePathToPackagePath(getClass(), "test-environment-with-registry.xml"), - ClassUtils.addResourcePathToPackagePath(getClass(), "job.xml")); - assertEquals(0, JobRegistryBackgroundJobRunner.getErrors().size()); - } - - @Test - void testMainWithAutoRegister() throws Exception { - JobRegistryBackgroundJobRunner.main( - ClassUtils.addResourcePathToPackagePath(getClass(), - "test-environment-with-registry-and-auto-register.xml"), - ClassUtils.addResourcePathToPackagePath(getClass(), "job.xml")); - assertEquals(0, JobRegistryBackgroundJobRunner.getErrors().size()); - } - - @Test - void testMainWithJobLoader() throws Exception { - JobRegistryBackgroundJobRunner.main( - ClassUtils.addResourcePathToPackagePath(getClass(), "test-environment-with-loader.xml"), - ClassUtils.addResourcePathToPackagePath(getClass(), "job.xml")); - assertEquals(0, JobRegistryBackgroundJobRunner.getErrors().size()); - } - - @BeforeEach - void setUp() { - JobRegistryBackgroundJobRunner.getErrors().clear(); - System.setProperty(JobRegistryBackgroundJobRunner.EMBEDDED, ""); - } - - @AfterEach - void tearDown() { - System.clearProperty(JobRegistryBackgroundJobRunner.EMBEDDED); - JobRegistryBackgroundJobRunner.getErrors().clear(); - JobRegistryBackgroundJobRunner.stop(); - } - -} diff --git a/spring-batch-docs/modules/ROOT/pages/appendix.adoc b/spring-batch-docs/modules/ROOT/pages/appendix.adoc index 12de632126..14c09d122c 100644 --- a/spring-batch-docs/modules/ROOT/pages/appendix.adoc +++ b/spring-batch-docs/modules/ROOT/pages/appendix.adoc @@ -36,9 +36,6 @@ It can be configured to read messages from multiple partitions of the same topic This reader stores message offsets in the execution context to support restart capabilities.|No |`FlatFileItemReader`|Reads from a flat file. Includes `ItemStream` and `Skippable` functionality. See link:readersAndWriters.html#flatFileItemReader["`FlatFileItemReader`"].|No -|`HibernateCursorItemReader`|Reads from a cursor based on an HQL query. See - link:readersAndWriters.html#cursorBasedItemReaders[`Cursor-based ItemReaders`].|No -|`HibernatePagingItemReader`|Reads from a paginated HQL query.|Yes |`ItemReaderAdapter`|Adapts any class to the `ItemReader` interface.|Yes |`JdbcCursorItemReader`|Reads from a database cursor over JDBC. See @@ -89,10 +86,6 @@ This reader stores message offsets in the execution context to support restart c in an injected `List` of `ItemWriter` objects.|Yes |`FlatFileItemWriter`|Writes to a flat file. Includes `ItemStream` and Skippable functionality. See link:readersAndWriters.html#flatFileItemWriter["`FlatFileItemWriter`"].|No -|`HibernateItemWriter`|This item writer is Hibernate-session aware - and handles some transaction-related work that a non-"`hibernate-aware`" - item writer would not need to know about and then delegates - to another item writer to do the actual writing.|Yes |`ItemWriterAdapter`|Adapts any class to the `ItemWriter` interface.|Yes |`JdbcBatchItemWriter`|Uses batching features from a diff --git a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/database.adoc b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/database.adoc index e7764b55ec..a962357913 100644 --- a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/database.adoc +++ b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/database.adoc @@ -211,89 +211,6 @@ step processing. To use this feature, you need a database that supports this and driver supporting JDBC 3.0 or later. Defaults to `false`. |=============== -[[HibernateCursorItemReader]] -=== `HibernateCursorItemReader` - -Just as normal Spring users make important decisions about whether or not to use ORM -solutions, which affect whether or not they use a `JdbcTemplate` or a -`HibernateTemplate`, Spring Batch users have the same options. -`HibernateCursorItemReader` is the Hibernate implementation of the cursor technique. -Hibernate's usage in batch has been fairly controversial. This has largely been because -Hibernate was originally developed to support online application styles. However, that -does not mean it cannot be used for batch processing. The easiest approach for solving -this problem is to use a `StatelessSession` rather than a standard session. This removes -all of the caching and dirty checking Hibernate employs and that can cause issues in a -batch scenario. For more information on the differences between stateless and normal -hibernate sessions, refer to the documentation of your specific hibernate release. The -`HibernateCursorItemReader` lets you declare an HQL statement and pass in a -`SessionFactory`, which will pass back one item per call to read in the same basic -fashion as the `JdbcCursorItemReader`. The following example configuration uses the same -'customer credit' example as the JDBC reader: - -[source, java] ----- -HibernateCursorItemReader itemReader = new HibernateCursorItemReader(); -itemReader.setQueryString("from CustomerCredit"); -//For simplicity sake, assume sessionFactory already obtained. -itemReader.setSessionFactory(sessionFactory); -itemReader.setUseStatelessSession(true); -int counter = 0; -ExecutionContext executionContext = new ExecutionContext(); -itemReader.open(executionContext); -Object customerCredit = new Object(); -while(customerCredit != null){ - customerCredit = itemReader.read(); - counter++; -} -itemReader.close(); ----- - -This configured `ItemReader` returns `CustomerCredit` objects in the exact same manner -as described by the `JdbcCursorItemReader`, assuming hibernate mapping files have been -created correctly for the `Customer` table. The 'useStatelessSession' property defaults -to true but has been added here to draw attention to the ability to switch it on or off. -It is also worth noting that the fetch size of the underlying cursor can be set with the -`setFetchSize` property. As with `JdbcCursorItemReader`, configuration is -straightforward. - - -[tabs] -==== -Java:: -+ -The following example shows how to inject a Hibernate `ItemReader` in Java: -+ -.Java Configuration -[source, java] ----- -@Bean -public HibernateCursorItemReader itemReader(SessionFactory sessionFactory) { - return new HibernateCursorItemReaderBuilder() - .name("creditReader") - .sessionFactory(sessionFactory) - .queryString("from CustomerCredit") - .build(); -} ----- - -XML:: -+ -The following example shows how to inject a Hibernate `ItemReader` in XML: -+ -.XML Configuration -[source, xml] ----- - - - - ----- - -==== - - - [[StoredProcedureItemReader]] === `StoredProcedureItemReader` diff --git a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/item-reader-writer-implementations.adoc b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/item-reader-writer-implementations.adoc index 4b1a0d31eb..628b2b85b7 100644 --- a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/item-reader-writer-implementations.adoc +++ b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/item-reader-writer-implementations.adoc @@ -163,8 +163,6 @@ Spring Batch offers the following database readers: * xref:readers-and-writers/item-reader-writer-implementations.adoc#Neo4jItemReader[`Neo4jItemReader`] * xref:readers-and-writers/item-reader-writer-implementations.adoc#mongoItemReader[`MongoItemReader`] -* xref:readers-and-writers/item-reader-writer-implementations.adoc#hibernateCursorItemReader[`HibernateCursorItemReader`] -* xref:readers-and-writers/item-reader-writer-implementations.adoc#hibernatePagingItemReader[`HibernatePagingItemReader`] * xref:readers-and-writers/item-reader-writer-implementations.adoc#repositoryItemReader[`RepositoryItemReader`] [[Neo4jItemReader]] @@ -179,22 +177,6 @@ The `MongoItemReader` is an `ItemReader` that reads documents from MongoDB by us paging technique. Spring Batch provides a `MongoItemReaderBuilder` to construct an instance of the `MongoItemReader`. -[[hibernateCursorItemReader]] -=== `HibernateCursorItemReader` -The `HibernateCursorItemReader` is an `ItemStreamReader` for reading database records -built on top of Hibernate. It executes the HQL query and then, when initialized, iterates -over the result set as the `read()` method is called, successively returning an object -corresponding to the current row. Spring Batch provides a -`HibernateCursorItemReaderBuilder` to construct an instance of the -`HibernateCursorItemReader`. - -[[hibernatePagingItemReader]] -=== `HibernatePagingItemReader` -The `HibernatePagingItemReader` is an `ItemReader` for reading database records built on -top of Hibernate and reading only up to a fixed number of items at a time. Spring Batch -provides a `HibernatePagingItemReaderBuilder` to construct an instance of the -`HibernatePagingItemReader`. - [[repositoryItemReader]] === `RepositoryItemReader` The `RepositoryItemReader` is an `ItemReader` that reads records by using a @@ -208,7 +190,6 @@ Spring Batch offers the following database writers: * xref:readers-and-writers/item-reader-writer-implementations.adoc#neo4jItemWriter[`Neo4jItemWriter`] * xref:readers-and-writers/item-reader-writer-implementations.adoc#mongoItemWriter[`MongoItemWriter`] * xref:readers-and-writers/item-reader-writer-implementations.adoc#repositoryItemWriter[`RepositoryItemWriter`] -* xref:readers-and-writers/item-reader-writer-implementations.adoc#hibernateItemWriter[`HibernateItemWriter`] * xref:readers-and-writers/item-reader-writer-implementations.adoc#jdbcBatchItemWriter[`JdbcBatchItemWriter`] * xref:readers-and-writers/item-reader-writer-implementations.adoc#jpaItemWriter[`JpaItemWriter`] @@ -230,12 +211,6 @@ The `RepositoryItemWriter` is an `ItemWriter` wrapper for a `CrudRepository` fro Data. Spring Batch provides a `RepositoryItemWriterBuilder` to construct an instance of the `RepositoryItemWriter`. -[[hibernateItemWriter]] -=== `HibernateItemWriter` -The `HibernateItemWriter` is an `ItemWriter` that uses a Hibernate session to save or -update entities that are not part of the current Hibernate session. Spring Batch provides -a `HibernateItemWriterBuilder` to construct an instance of the `HibernateItemWriter`. - [[jdbcBatchItemWriter]] === `JdbcBatchItemWriter` The `JdbcBatchItemWriter` is an `ItemWriter` that uses the batching features from diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ItemStreamSupport.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ItemStreamSupport.java index 6d72ffa4ac..86446bc0ca 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ItemStreamSupport.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/ItemStreamSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -29,39 +29,6 @@ public abstract class ItemStreamSupport implements ItemStream { private final ExecutionContextUserSupport executionContextUserSupport = new ExecutionContextUserSupport(); - /** - * No-op. - * @see org.springframework.batch.item.ItemStream#close() - * @deprecated since 5.0 in favor of {@link ItemStream#close()}. Scheduled for removal - * in 5.2. - */ - @Deprecated(since = "5.0", forRemoval = true) - @Override - public void close() { - } - - /** - * No-op. - * @see org.springframework.batch.item.ItemStream#open(ExecutionContext) - * @deprecated since 5.0 in favor of {@link ItemStream#open(ExecutionContext)} ()}. - * Scheduled for removal in 5.2. - */ - @Override - @Deprecated(since = "5.0", forRemoval = true) - public void open(ExecutionContext executionContext) { - } - - /** - * Return empty {@link ExecutionContext}. - * @see org.springframework.batch.item.ItemStream#update(ExecutionContext) - * @deprecated since 5.0 in favor of {@link ItemStream#update(ExecutionContext)} ()}. - * Scheduled for removal in 5.2. - */ - @Override - @Deprecated(since = "5.0", forRemoval = true) - public void update(ExecutionContext executionContext) { - } - /** * The name of the component which will be used as a stem for keys in the * {@link ExecutionContext}. Subclasses should provide a default value, e.g. the short diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernateCursorItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernateCursorItemReader.java deleted file mode 100644 index ea1bba0348..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernateCursorItemReader.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2006-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import java.util.Map; - -import org.hibernate.ScrollableResults; -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.StatelessSession; -import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.item.ItemStreamReader; -import org.springframework.batch.item.ItemStreamException; -import org.springframework.batch.item.database.orm.HibernateQueryProvider; -import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -/** - * {@link ItemStreamReader} for reading database records built on top of Hibernate. It - * executes the HQL query when initialized iterates over the result set as {@link #read()} - * method is called, returning an object corresponding to current row. The query can be - * set directly using {@link #setQueryString(String)}, a named query can be used by - * {@link #setQueryName(String)}, or a query provider strategy can be supplied via - * {@link #setQueryProvider(HibernateQueryProvider)}. - * - *

    - * The reader can be configured to use either {@link StatelessSession} sufficient for - * simple mappings without the need to cascade to associated objects or standard hibernate - * {@link Session} for more advanced mappings or when caching is desired. When stateful - * session is used it will be cleared in the {@link #update(ExecutionContext)} method - * without being flushed (no data modifications are expected). - *

    - * - * The implementation is not thread-safe. - * - * @author Robert Kasanicky - * @author Dave Syer - * @author Mahmoud Ben Hassine - * @deprecated since 5.0 for removal in 5.2. Use the {@link JpaCursorItemReader} instead. - */ -@Deprecated(since = "5.0", forRemoval = true) -public class HibernateCursorItemReader extends AbstractItemCountingItemStreamItemReader - implements InitializingBean { - - private final HibernateItemReaderHelper helper = new HibernateItemReaderHelper<>(); - - public HibernateCursorItemReader() { - setName(ClassUtils.getShortName(HibernateCursorItemReader.class)); - } - - private ScrollableResults cursor; - - private boolean initialized = false; - - private int fetchSize; - - private Map parameterValues; - - @Override - public void afterPropertiesSet() throws Exception { - Assert.state(fetchSize >= 0, "fetchSize must not be negative"); - helper.afterPropertiesSet(); - } - - /** - * The parameter values to apply to a query (map of name:value). - * @param parameterValues the parameter values to set - */ - public void setParameterValues(Map parameterValues) { - this.parameterValues = parameterValues; - } - - /** - * A query name for an externalized query. Either this or the { - * {@link #setQueryString(String) query string} or the { - * {@link #setQueryProvider(HibernateQueryProvider) query provider} should be set. - * @param queryName name of a hibernate named query - */ - public void setQueryName(String queryName) { - helper.setQueryName(queryName); - } - - /** - * Fetch size used internally by Hibernate to limit amount of data fetched from - * database per round trip. - * @param fetchSize the fetch size to pass down to Hibernate - */ - public void setFetchSize(int fetchSize) { - this.fetchSize = fetchSize; - } - - /** - * A query provider. Either this or the {{@link #setQueryString(String) query string} - * or the {{@link #setQueryName(String) query name} should be set. - * @param queryProvider Hibernate query provider - */ - public void setQueryProvider(HibernateQueryProvider queryProvider) { - helper.setQueryProvider(queryProvider); - } - - /** - * A query string in HQL. Either this or the { - * {@link #setQueryProvider(HibernateQueryProvider) query provider} or the { - * {@link #setQueryName(String) query name} should be set. - * @param queryString HQL query string - */ - public void setQueryString(String queryString) { - helper.setQueryString(queryString); - } - - /** - * The Hibernate SessionFactory to use the create a session. - * @param sessionFactory the {@link SessionFactory} to set - */ - public void setSessionFactory(SessionFactory sessionFactory) { - helper.setSessionFactory(sessionFactory); - } - - /** - * Can be set only in uninitialized state. - * @param useStatelessSession true to use {@link StatelessSession} - * false to use standard hibernate {@link Session} - */ - public void setUseStatelessSession(boolean useStatelessSession) { - helper.setUseStatelessSession(useStatelessSession); - } - - @Nullable - @Override - protected T doRead() throws Exception { - if (cursor.next()) { - return cursor.get(); - } - return null; - } - - /** - * Open hibernate session and create a forward-only cursor for the query. - */ - @Override - protected void doOpen() throws Exception { - Assert.state(!initialized, "Cannot open an already opened ItemReader, call close first"); - cursor = helper.getForwardOnlyCursor(fetchSize, parameterValues); - initialized = true; - } - - /** - * Update the context and clear the session if stateful. - * @param executionContext the current {@link ExecutionContext} - * @throws ItemStreamException if there is a problem - */ - @Override - public void update(ExecutionContext executionContext) throws ItemStreamException { - super.update(executionContext); - helper.clear(); - } - - /** - * Wind forward through the result set to the item requested. Also clears the session - * every now and then (if stateful) to avoid memory problems. The frequency of session - * clearing is the larger of the fetch size (if set) and 100. - * @param itemIndex the first item to read - * @throws Exception if there is a problem - * @see AbstractItemCountingItemStreamItemReader#jumpToItem(int) - */ - @Override - protected void jumpToItem(int itemIndex) throws Exception { - int flushSize = Math.max(fetchSize, 100); - helper.jumpToItem(cursor, itemIndex, flushSize); - } - - /** - * Close the cursor and hibernate session. - */ - @Override - protected void doClose() throws Exception { - - if (initialized) { - if (cursor != null) { - cursor.close(); - } - - helper.close(); - } - - initialized = false; - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernateItemReaderHelper.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernateItemReaderHelper.java deleted file mode 100644 index 6911572c44..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernateItemReaderHelper.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2006-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import java.util.Collection; -import java.util.Map; - -import org.hibernate.query.Query; -import org.hibernate.ScrollMode; -import org.hibernate.ScrollableResults; -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.StatelessSession; - -import org.springframework.batch.item.database.orm.HibernateQueryProvider; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * Internal shared state helper for hibernate readers managing sessions and queries. - * - * @author Dave Syer - * @author Mahmoud Ben Hassine - * @author June Young. Park - * @deprecated since 5.0 for removal in 5.2. Use the JPA item readers instead. - */ -@Deprecated(since = "5.0", forRemoval = true) -public class HibernateItemReaderHelper implements InitializingBean { - - private SessionFactory sessionFactory; - - private String queryString = ""; - - private String queryName = ""; - - private HibernateQueryProvider queryProvider; - - private boolean useStatelessSession = true; - - private StatelessSession statelessSession; - - private Session statefulSession; - - /** - * @param queryName name of a hibernate named query - */ - public void setQueryName(String queryName) { - this.queryName = queryName; - } - - /** - * @param queryString HQL query string - */ - public void setQueryString(String queryString) { - this.queryString = queryString; - } - - /** - * @param queryProvider Hibernate query provider - */ - public void setQueryProvider(HibernateQueryProvider queryProvider) { - this.queryProvider = queryProvider; - } - - /** - * Can be set only in uninitialized state. - * @param useStatelessSession true to use {@link StatelessSession} - * false to use standard hibernate {@link Session} - */ - public void setUseStatelessSession(boolean useStatelessSession) { - Assert.state(statefulSession == null && statelessSession == null, - "The useStatelessSession flag can only be set before a session is initialized."); - this.useStatelessSession = useStatelessSession; - } - - /** - * @param sessionFactory hibernate session factory - */ - public void setSessionFactory(SessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - } - - @Override - public void afterPropertiesSet() throws Exception { - - Assert.state(sessionFactory != null, "A SessionFactory must be provided"); - - if (queryProvider == null) { - Assert.state(sessionFactory != null, "session factory must be set"); - Assert.state(StringUtils.hasText(queryString) ^ StringUtils.hasText(queryName), - "queryString or queryName must be set"); - } - } - - /** - * Get a cursor over all of the results, with the forward-only flag set. - * @param fetchSize the fetch size to use retrieving the results - * @param parameterValues the parameter values to use (or null if none). - * @return a forward-only {@link ScrollableResults} - */ - public ScrollableResults getForwardOnlyCursor(int fetchSize, Map parameterValues) { - Query query = createQuery(); - if (!CollectionUtils.isEmpty(parameterValues)) { - query.setProperties(parameterValues); - } - return query.setFetchSize(fetchSize).scroll(ScrollMode.FORWARD_ONLY); - } - - /** - * Open appropriate type of hibernate session and create the query. - * @return a Hibernate Query - */ - public Query createQuery() { - - if (useStatelessSession) { - if (statelessSession == null) { - statelessSession = sessionFactory.openStatelessSession(); - } - if (queryProvider != null) { - queryProvider.setStatelessSession(statelessSession); - } - else { - if (StringUtils.hasText(queryName)) { - return statelessSession.getNamedQuery(queryName); - } - else { - return statelessSession.createQuery(queryString); - } - } - } - else { - if (statefulSession == null) { - statefulSession = sessionFactory.openSession(); - } - if (queryProvider != null) { - queryProvider.setSession(statefulSession); - } - else { - if (StringUtils.hasText(queryName)) { - return statefulSession.getNamedQuery(queryName); - } - else { - return statefulSession.createQuery(queryString); - } - } - } - - // If queryProvider is set use it to create a query - return queryProvider.createQuery(); - - } - - /** - * Scroll through the results up to the item specified. - * @param cursor the results to scroll over - * @param itemIndex index to scroll to - * @param flushInterval the number of items to scroll past before flushing - */ - public void jumpToItem(ScrollableResults cursor, int itemIndex, int flushInterval) { - for (int i = 0; i < itemIndex; i++) { - cursor.next(); - if (i % flushInterval == 0 && !useStatelessSession) { - statefulSession.clear(); // Clears in-memory cache - } - } - } - - /** - * Close the open session (stateful or otherwise). - */ - public void close() { - if (statelessSession != null) { - statelessSession.close(); - statelessSession = null; - } - if (statefulSession != null) { - statefulSession.close(); - statefulSession = null; - } - } - - /** - * Read a page of data, clearing the existing session (if necessary) first, and - * creating a new session before executing the query. - * @param page the page to read (starting at 0) - * @param pageSize the size of the page or maximum number of items to read - * @param fetchSize the fetch size to use - * @param parameterValues the parameter values to use (if any, otherwise null) - * @return a collection of items - */ - public Collection readPage(int page, int pageSize, int fetchSize, - Map parameterValues) { - - clear(); - - Query query = createQuery(); - if (!CollectionUtils.isEmpty(parameterValues)) { - query.setProperties(parameterValues); - } - return query.setFetchSize(fetchSize).setFirstResult(page * pageSize).setMaxResults(pageSize).list(); - - } - - /** - * Clear the session if stateful. - */ - public void clear() { - if (statefulSession != null) { - statefulSession.clear(); - } - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernateItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernateItemWriter.java deleted file mode 100644 index 144c72fdd6..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernateItemWriter.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2006-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.context.spi.CurrentSessionContext; - -import org.springframework.batch.item.Chunk; -import org.springframework.batch.item.ItemWriter; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; - -/** - * {@link ItemWriter} that uses a Hibernate session to save or update entities that are - * not part of the current Hibernate session. It will also flush the session after writing - * (i.e. at chunk boundaries if used in a Spring Batch TaskletStep). It will also clear - * the session on write default (see {@link #setClearSession(boolean) clearSession} - * property).
    - *
    - * - * The writer is thread-safe once properties are set (normal singleton behavior) if a - * {@link CurrentSessionContext} that uses only one session per thread is used. - * - * @author Dave Syer - * @author Thomas Risberg - * @author Michael Minella - * @author Mahmoud Ben Hassine - * @deprecated since 5.0 for removal in 5.2. Use the {@link JpaItemWriter} instead. - * - */ -@Deprecated(since = "5.0", forRemoval = true) -public class HibernateItemWriter implements ItemWriter, InitializingBean { - - protected static final Log logger = LogFactory.getLog(HibernateItemWriter.class); - - private SessionFactory sessionFactory; - - private boolean clearSession = true; - - /** - * Flag to indicate that the session should be cleared and flushed at the end of the - * write (default true). - * @param clearSession the flag value to set - */ - public void setClearSession(boolean clearSession) { - this.clearSession = clearSession; - } - - /** - * Set the Hibernate SessionFactory to be used internally. - * @param sessionFactory session factory to be used by the writer - */ - public void setSessionFactory(SessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - } - - /** - * Check mandatory properties - there must be a sessionFactory. - */ - @Override - public void afterPropertiesSet() { - Assert.state(sessionFactory != null, "SessionFactory must be provided"); - } - - /** - * Save or update any entities not in the current hibernate session and then flush the - * hibernate session. - * - * @see org.springframework.batch.item.ItemWriter#write(Chunk) - */ - @Override - public void write(Chunk items) { - doWrite(sessionFactory, items); - sessionFactory.getCurrentSession().flush(); - if (clearSession) { - sessionFactory.getCurrentSession().clear(); - } - } - - /** - * Do perform the actual write operation using Hibernate's API. This can be overridden - * in a subclass if necessary. - * @param sessionFactory Hibernate SessionFactory to be used - * @param items the list of items to use for the write - */ - protected void doWrite(SessionFactory sessionFactory, Chunk items) { - if (logger.isDebugEnabled()) { - logger.debug("Writing to Hibernate with " + items.size() + " items."); - } - - Session currentSession = sessionFactory.getCurrentSession(); - - if (!items.isEmpty()) { - long saveOrUpdateCount = 0; - for (T item : items) { - if (!currentSession.contains(item)) { - currentSession.saveOrUpdate(item); - saveOrUpdateCount++; - } - } - if (logger.isDebugEnabled()) { - logger.debug(saveOrUpdateCount + " entities saved/updated."); - logger.debug((items.size() - saveOrUpdateCount) + " entities found in session."); - } - } - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernatePagingItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernatePagingItemReader.java deleted file mode 100644 index 41c50e2674..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/HibernatePagingItemReader.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2006-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import java.util.Map; -import java.util.concurrent.CopyOnWriteArrayList; - -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.StatelessSession; -import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.item.ItemReader; -import org.springframework.batch.item.database.orm.HibernateQueryProvider; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -/** - * {@link ItemReader} for reading database records built on top of Hibernate and reading - * only up to a fixed number of items at a time. It executes an HQL query when initialized - * is paged as the {@link #read()} method is called. The query can be set directly using - * {@link #setQueryString(String)}, a named query can be used by - * {@link #setQueryName(String)}, or a query provider strategy can be supplied via - * {@link #setQueryProvider(HibernateQueryProvider)}. - * - *

    - * The reader can be configured to use either {@link StatelessSession} sufficient for - * simple mappings without the need to cascade to associated objects or standard hibernate - * {@link Session} for more advanced mappings or when caching is desired. When stateful - * session is used it will be cleared in the {@link #update(ExecutionContext)} method - * without being flushed (no data modifications are expected). - *

    - * - *

    - * The implementation is thread-safe in between calls to {@link #open(ExecutionContext)}, - * but remember to use saveState=false if used in a multi-threaded client (no - * restart available). - *

    - * - * @author Dave Syer - * @author Mahmoud Ben Hassine - * @since 2.1 - * @deprecated since 5.0 for removal in 5.2. Use the {@link JpaPagingItemReader} instead. - */ -@Deprecated(since = "5.0", forRemoval = true) -public class HibernatePagingItemReader extends AbstractPagingItemReader implements InitializingBean { - - private final HibernateItemReaderHelper helper = new HibernateItemReaderHelper<>(); - - private Map parameterValues; - - private int fetchSize; - - public HibernatePagingItemReader() { - setName(ClassUtils.getShortName(HibernatePagingItemReader.class)); - } - - /** - * The parameter values to apply to a query (map of name:value). - * @param parameterValues the parameter values to set - */ - public void setParameterValues(Map parameterValues) { - this.parameterValues = parameterValues; - } - - /** - * A query name for an externalized query. Either this or the { - * {@link #setQueryString(String) query string} or the { - * {@link #setQueryProvider(HibernateQueryProvider) query provider} should be set. - * @param queryName name of a hibernate named query - */ - public void setQueryName(String queryName) { - helper.setQueryName(queryName); - } - - /** - * Fetch size used internally by Hibernate to limit amount of data fetched from - * database per round trip. - * @param fetchSize the fetch size to pass down to Hibernate - */ - public void setFetchSize(int fetchSize) { - this.fetchSize = fetchSize; - } - - /** - * A query provider. Either this or the {{@link #setQueryString(String) query string} - * or the {{@link #setQueryName(String) query name} should be set. - * @param queryProvider Hibernate query provider - */ - public void setQueryProvider(HibernateQueryProvider queryProvider) { - helper.setQueryProvider(queryProvider); - } - - /** - * A query string in HQL. Either this or the { - * {@link #setQueryProvider(HibernateQueryProvider) query provider} or the { - * {@link #setQueryName(String) query name} should be set. - * @param queryString HQL query string - */ - public void setQueryString(String queryString) { - helper.setQueryString(queryString); - } - - /** - * The Hibernate SessionFactory to use the create a session. - * @param sessionFactory the {@link SessionFactory} to set - */ - public void setSessionFactory(SessionFactory sessionFactory) { - helper.setSessionFactory(sessionFactory); - } - - /** - * Can be set only in uninitialized state. - * @param useStatelessSession true to use {@link StatelessSession} - * false to use standard hibernate {@link Session} - */ - public void setUseStatelessSession(boolean useStatelessSession) { - helper.setUseStatelessSession(useStatelessSession); - } - - @Override - public void afterPropertiesSet() throws Exception { - super.afterPropertiesSet(); - Assert.state(fetchSize >= 0, "fetchSize must not be negative"); - helper.afterPropertiesSet(); - } - - @Override - protected void doOpen() throws Exception { - super.doOpen(); - } - - @Override - protected void doReadPage() { - - if (results == null) { - results = new CopyOnWriteArrayList<>(); - } - else { - results.clear(); - } - - results.addAll(helper.readPage(getPage(), getPageSize(), fetchSize, parameterValues)); - - } - - @Override - protected void doClose() throws Exception { - helper.close(); - super.doClose(); - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/HibernateCursorItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/HibernateCursorItemReaderBuilder.java deleted file mode 100644 index 864a6ad562..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/HibernateCursorItemReaderBuilder.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright 2017-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database.builder; - -import java.util.Map; - -import org.hibernate.SessionFactory; - -import org.springframework.batch.item.database.HibernateCursorItemReader; -import org.springframework.batch.item.database.orm.HibernateNativeQueryProvider; -import org.springframework.batch.item.database.orm.HibernateQueryProvider; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * This is a builder for the {@link HibernateCursorItemReader}. When configuring, one of - * the following should be provided (listed in order of precedence): - *
      - *
    • {@link #queryProvider(HibernateQueryProvider)}
    • - *
    • {@link #queryName(String)}
    • - *
    • {@link #queryString(String)}
    • - *
    • {@link #nativeQuery(String)} and {@link #entityClass(Class)}
    • - *
    - * - * @author Michael Minella - * @author Glenn Renfro - * @author Mahmoud Ben Hassine - * @since 4.0 - * @see HibernateCursorItemReader - * @deprecated since 5.0 for removal in 5.2. Use the {@link JpaCursorItemReaderBuilder} - * instead. - */ -@Deprecated(since = "5.0", forRemoval = true) -public class HibernateCursorItemReaderBuilder { - - private Map parameterValues; - - private String queryName; - - private int fetchSize; - - private HibernateQueryProvider queryProvider; - - private String queryString; - - private SessionFactory sessionFactory; - - private boolean useStatelessSession; - - private String nativeQuery; - - private Class nativeClass; - - private boolean saveState = true; - - private String name; - - private int maxItemCount = Integer.MAX_VALUE; - - private int currentItemCount; - - /** - * Configure if the state of the - * {@link org.springframework.batch.item.ItemStreamSupport} should be persisted within - * the {@link org.springframework.batch.item.ExecutionContext} for restart purposes. - * @param saveState defaults to true - * @return The current instance of the builder. - */ - public HibernateCursorItemReaderBuilder saveState(boolean saveState) { - this.saveState = saveState; - - return this; - } - - /** - * The name used to calculate the key within the - * {@link org.springframework.batch.item.ExecutionContext}. Required if - * {@link #saveState(boolean)} is set to true. - * @param name name of the reader instance - * @return The current instance of the builder. - * @see org.springframework.batch.item.ItemStreamSupport#setName(String) - */ - public HibernateCursorItemReaderBuilder name(String name) { - this.name = name; - - return this; - } - - /** - * Configure the max number of items to be read. - * @param maxItemCount the max items to be read - * @return The current instance of the builder. - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int) - */ - public HibernateCursorItemReaderBuilder maxItemCount(int maxItemCount) { - this.maxItemCount = maxItemCount; - - return this; - } - - /** - * Index for the current item. Used on restarts to indicate where to start from. - * @param currentItemCount current index - * @return this instance for method chaining - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setCurrentItemCount(int) - */ - public HibernateCursorItemReaderBuilder currentItemCount(int currentItemCount) { - this.currentItemCount = currentItemCount; - - return this; - } - - /** - * A map of parameter values to be set on the query. The key of the map is the name of - * the parameter to be set with the value being the value to be set. - * @param parameterValues map of values - * @return this instance for method chaining - * @see HibernateCursorItemReader#setParameterValues(Map) - */ - public HibernateCursorItemReaderBuilder parameterValues(Map parameterValues) { - this.parameterValues = parameterValues; - - return this; - } - - /** - * The name of the Hibernate named query to be executed for this reader. - * @param queryName name of the query to execute - * @return this instance for method chaining - * @see HibernateCursorItemReader#setQueryName(String) - */ - public HibernateCursorItemReaderBuilder queryName(String queryName) { - this.queryName = queryName; - - return this; - } - - /** - * The number of items to be returned with each round trip to the database. Used - * internally by Hibernate. - * @param fetchSize number of records to return per fetch - * @return this instance for method chaining - * @see HibernateCursorItemReader#setFetchSize(int) - */ - public HibernateCursorItemReaderBuilder fetchSize(int fetchSize) { - this.fetchSize = fetchSize; - - return this; - } - - /** - * A query provider. This should be set only if {@link #queryString(String)} and - * {@link #queryName(String)} have not been set. - * @param queryProvider the query provider - * @return this instance for method chaining - * @see HibernateCursorItemReader#setQueryProvider(HibernateQueryProvider) - */ - public HibernateCursorItemReaderBuilder queryProvider(HibernateQueryProvider queryProvider) { - this.queryProvider = queryProvider; - - return this; - } - - /** - * The HQL query string to execute. This should only be set if - * {@link #queryProvider(HibernateQueryProvider)} and {@link #queryName(String)} have - * not been set. - * @param queryString the HQL query - * @return this instance for method chaining - * @see HibernateCursorItemReader#setQueryString(String) - */ - public HibernateCursorItemReaderBuilder queryString(String queryString) { - this.queryString = queryString; - - return this; - } - - /** - * The Hibernate {@link SessionFactory} to execute the query against. - * @param sessionFactory the session factory - * @return this instance for method chaining - * @see HibernateCursorItemReader#setSessionFactory(SessionFactory) - */ - public HibernateCursorItemReaderBuilder sessionFactory(SessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - - return this; - } - - /** - * Indicator for whether to use a {@link org.hibernate.StatelessSession} - * (true) or a {@link org.hibernate.Session} (false). - * @param useStatelessSession Defaults to false - * @return this instance for method chaining - * @see HibernateCursorItemReader#setUseStatelessSession(boolean) - */ - public HibernateCursorItemReaderBuilder useStatelessSession(boolean useStatelessSession) { - this.useStatelessSession = useStatelessSession; - - return this; - } - - /** - * Used to configure a {@link HibernateNativeQueryProvider}. This is ignored if - * @param nativeQuery {@link String} containing the native query. - * @return this instance for method chaining - */ - public HibernateCursorItemReaderBuilder nativeQuery(String nativeQuery) { - this.nativeQuery = nativeQuery; - - return this; - } - - public HibernateCursorItemReaderBuilder entityClass(Class nativeClass) { - this.nativeClass = nativeClass; - - return this; - } - - /** - * Returns a fully constructed {@link HibernateCursorItemReader}. - * @return a new {@link HibernateCursorItemReader} - */ - public HibernateCursorItemReader build() { - Assert.state(this.fetchSize >= 0, "fetchSize must not be negative"); - Assert.state(this.sessionFactory != null, "A SessionFactory must be provided"); - - if (this.saveState) { - Assert.state(StringUtils.hasText(this.name), "A name is required when saveState is set to true."); - } - - HibernateCursorItemReader reader = new HibernateCursorItemReader<>(); - - reader.setFetchSize(this.fetchSize); - reader.setParameterValues(this.parameterValues); - - if (this.queryProvider != null) { - reader.setQueryProvider(this.queryProvider); - } - else if (StringUtils.hasText(this.queryName)) { - reader.setQueryName(this.queryName); - } - else if (StringUtils.hasText(this.queryString)) { - reader.setQueryString(this.queryString); - } - else if (StringUtils.hasText(this.nativeQuery) && this.nativeClass != null) { - HibernateNativeQueryProvider provider = new HibernateNativeQueryProvider<>(); - provider.setSqlQuery(this.nativeQuery); - provider.setEntityClass(this.nativeClass); - - try { - provider.afterPropertiesSet(); - } - catch (Exception e) { - throw new IllegalStateException("Unable to initialize the HibernateNativeQueryProvider", e); - } - - reader.setQueryProvider(provider); - } - else { - throw new IllegalStateException("A HibernateQueryProvider, queryName, queryString, " - + "or both the nativeQuery and entityClass must be configured"); - } - - reader.setSessionFactory(this.sessionFactory); - reader.setUseStatelessSession(this.useStatelessSession); - reader.setCurrentItemCount(this.currentItemCount); - reader.setMaxItemCount(this.maxItemCount); - reader.setName(this.name); - reader.setSaveState(this.saveState); - - return reader; - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/HibernateItemWriterBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/HibernateItemWriterBuilder.java deleted file mode 100644 index ab313ef3d1..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/HibernateItemWriterBuilder.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2017-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database.builder; - -import org.hibernate.SessionFactory; - -import org.springframework.batch.item.database.HibernateItemWriter; -import org.springframework.util.Assert; - -/** - * A builder for the {@link HibernateItemWriter} - * - * @author Michael Minella - * @author Mahmoud Ben Hassine - * @since 4.0 - * @see HibernateItemWriter - * @deprecated since 5.0 for removal in 5.2. Use the {@link JpaItemWriterBuilder} instead. - */ -@Deprecated(since = "5.0", forRemoval = true) -public class HibernateItemWriterBuilder { - - private boolean clearSession = true; - - private SessionFactory sessionFactory; - - /** - * If set to false, the {@link org.hibernate.Session} will not be cleared at the end - * of the chunk. - * @param clearSession defaults to true - * @return this instance for method chaining - * @see HibernateItemWriter#setClearSession(boolean) - */ - public HibernateItemWriterBuilder clearSession(boolean clearSession) { - this.clearSession = clearSession; - - return this; - } - - /** - * The Hibernate {@link SessionFactory} to obtain a session from. Required. - * @param sessionFactory the {@link SessionFactory} - * @return this instance for method chaining - * @see HibernateItemWriter#setSessionFactory(SessionFactory) - */ - public HibernateItemWriterBuilder sessionFactory(SessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - - return this; - } - - /** - * Returns a fully built {@link HibernateItemWriter} - * @return the writer - */ - public HibernateItemWriter build() { - Assert.state(this.sessionFactory != null, "SessionFactory must be provided"); - - HibernateItemWriter writer = new HibernateItemWriter<>(); - writer.setSessionFactory(this.sessionFactory); - writer.setClearSession(this.clearSession); - - return writer; - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/HibernatePagingItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/HibernatePagingItemReaderBuilder.java deleted file mode 100644 index 8a9c20b84c..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/HibernatePagingItemReaderBuilder.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright 2017-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database.builder; - -import java.util.Map; - -import org.hibernate.SessionFactory; - -import org.springframework.batch.item.database.HibernatePagingItemReader; -import org.springframework.batch.item.database.orm.HibernateQueryProvider; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * A builder for the {@link HibernatePagingItemReader}. When configuring, only one of the - * following should be provided: - *
      - *
    • {@link #queryString(String)}
    • - *
    • {@link #queryName(String)}
    • - *
    • {@link #queryProvider(HibernateQueryProvider)}
    • - *
    - * - * @author Michael Minella - * @author Glenn Renfro - * @author Mahmoud Ben Hassine - * @since 4.0 - * @see HibernatePagingItemReader - * @deprecated since 5.0 for removal in 5.2. Use the {@link JpaPagingItemReaderBuilder} - * instead. - */ -@Deprecated(since = "5.0", forRemoval = true) -public class HibernatePagingItemReaderBuilder { - - private int pageSize = 10; - - private Map parameterValues; - - private String queryName; - - private int fetchSize; - - private HibernateQueryProvider queryProvider; - - private String queryString; - - private SessionFactory sessionFactory; - - private boolean statelessSession = true; - - private boolean saveState = true; - - private String name; - - private int maxItemCount = Integer.MAX_VALUE; - - private int currentItemCount; - - /** - * Configure if the state of the - * {@link org.springframework.batch.item.ItemStreamSupport} should be persisted within - * the {@link org.springframework.batch.item.ExecutionContext} for restart purposes. - * @param saveState defaults to true - * @return The current instance of the builder. - */ - public HibernatePagingItemReaderBuilder saveState(boolean saveState) { - this.saveState = saveState; - - return this; - } - - /** - * The name used to calculate the key within the - * {@link org.springframework.batch.item.ExecutionContext}. Required if - * {@link #saveState(boolean)} is set to true. - * @param name name of the reader instance - * @return The current instance of the builder. - * @see org.springframework.batch.item.ItemStreamSupport#setName(String) - */ - public HibernatePagingItemReaderBuilder name(String name) { - this.name = name; - - return this; - } - - /** - * Configure the max number of items to be read. - * @param maxItemCount the max items to be read - * @return The current instance of the builder. - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int) - */ - public HibernatePagingItemReaderBuilder maxItemCount(int maxItemCount) { - this.maxItemCount = maxItemCount; - - return this; - } - - /** - * Index for the current item. Used on restarts to indicate where to start from. - * @param currentItemCount current index - * @return this instance for method chaining - * @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setCurrentItemCount(int) - */ - public HibernatePagingItemReaderBuilder currentItemCount(int currentItemCount) { - this.currentItemCount = currentItemCount; - - return this; - } - - /** - * The number of records to request per page/query. Defaults to 10. Must be greater - * than zero. - * @param pageSize number of items - * @return this instance for method chaining - * @see HibernatePagingItemReader#setPageSize(int) - */ - public HibernatePagingItemReaderBuilder pageSize(int pageSize) { - this.pageSize = pageSize; - - return this; - } - - /** - * A map of parameter values to be set on the query. The key of the map is the name of - * the parameter to be set with the value being the value to be set. - * @param parameterValues map of values - * @return this instance for method chaining - * @see HibernatePagingItemReader#setParameterValues(Map) - */ - public HibernatePagingItemReaderBuilder parameterValues(Map parameterValues) { - this.parameterValues = parameterValues; - - return this; - } - - /** - * The name of the Hibernate named query to be executed for this reader. - * @param queryName name of the query to execute - * @return this instance for method chaining - * @see HibernatePagingItemReader#setQueryName(String) - */ - public HibernatePagingItemReaderBuilder queryName(String queryName) { - this.queryName = queryName; - - return this; - } - - /** - * Fetch size used internally by Hibernate to limit amount of data fetched from - * database per round trip. - * @param fetchSize number of records - * @return this instance for method chaining - * @see HibernatePagingItemReader#setFetchSize(int) - */ - public HibernatePagingItemReaderBuilder fetchSize(int fetchSize) { - this.fetchSize = fetchSize; - - return this; - } - - /** - * A query provider. This should be set only if {@link #queryString(String)} and - * {@link #queryName(String)} have not been set. - * @param queryProvider the query provider - * @return this instance for method chaining - * @see HibernatePagingItemReader#setQueryProvider(HibernateQueryProvider) - */ - public HibernatePagingItemReaderBuilder queryProvider(HibernateQueryProvider queryProvider) { - this.queryProvider = queryProvider; - - return this; - } - - /** - * The HQL query string to execute. This should only be set if - * {@link #queryProvider(HibernateQueryProvider)} and {@link #queryName(String)} have - * not been set. - * @param queryString the HQL query - * @return this instance for method chaining - * @see HibernatePagingItemReader#setQueryString(String) - */ - public HibernatePagingItemReaderBuilder queryString(String queryString) { - this.queryString = queryString; - - return this; - } - - /** - * The Hibernate {@link SessionFactory} to execute the query against. - * @param sessionFactory the session factory - * @return this instance for method chaining - * @see HibernatePagingItemReader#setSessionFactory(SessionFactory) - */ - public HibernatePagingItemReaderBuilder sessionFactory(SessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - - return this; - } - - /** - * Indicator for whether to use a {@link org.hibernate.StatelessSession} - * (true) or a {@link org.hibernate.Session} (false). - * @param useStatelessSession Defaults to false - * @return this instance for method chaining - * @see HibernatePagingItemReader#setUseStatelessSession(boolean) - */ - public HibernatePagingItemReaderBuilder useStatelessSession(boolean useStatelessSession) { - this.statelessSession = useStatelessSession; - - return this; - } - - /** - * Returns a fully constructed {@link HibernatePagingItemReader}. - * @return a new {@link HibernatePagingItemReader} - */ - public HibernatePagingItemReader build() { - Assert.notNull(this.sessionFactory, "A SessionFactory must be provided"); - Assert.state(this.fetchSize >= 0, "fetchSize must not be negative"); - - if (this.saveState) { - Assert.hasText(this.name, "A name is required when saveState is set to true"); - } - - if (this.queryProvider == null) { - Assert.state(StringUtils.hasText(queryString) ^ StringUtils.hasText(queryName), - "queryString or queryName must be set"); - } - - HibernatePagingItemReader reader = new HibernatePagingItemReader<>(); - - reader.setSessionFactory(this.sessionFactory); - reader.setSaveState(this.saveState); - reader.setMaxItemCount(this.maxItemCount); - reader.setCurrentItemCount(this.currentItemCount); - reader.setName(this.name); - reader.setFetchSize(this.fetchSize); - reader.setParameterValues(this.parameterValues); - reader.setQueryName(this.queryName); - reader.setQueryProvider(this.queryProvider); - reader.setQueryString(this.queryString); - reader.setPageSize(this.pageSize); - reader.setUseStatelessSession(this.statelessSession); - - return reader; - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/AbstractHibernateQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/AbstractHibernateQueryProvider.java deleted file mode 100644 index 23332c065f..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/AbstractHibernateQueryProvider.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2006-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.database.orm; - -import org.hibernate.query.Query; -import org.hibernate.Session; -import org.hibernate.StatelessSession; - -/** - *

    - * Abstract Hibernate Query Provider to serve as a base class for all Hibernate - * {@link Query} providers. - *

    - * - *

    - * The implementing provider can be configured to use either {@link StatelessSession} - * sufficient for simple mappings without the need to cascade to associated objects or - * standard Hibernate {@link Session} for more advanced mappings or when caching is - * desired. - *

    - * - * @author Anatoly Polinsky - * @author Dave Syer - * @author Mahmoud Ben Hassine - * @since 2.1 - * - */ -@Deprecated(since = "5.0", forRemoval = true) -public abstract class AbstractHibernateQueryProvider implements HibernateQueryProvider { - - private StatelessSession statelessSession; - - private Session statefulSession; - - @Override - public void setStatelessSession(StatelessSession statelessSession) { - this.statelessSession = statelessSession; - } - - @Override - public void setSession(Session statefulSession) { - this.statefulSession = statefulSession; - } - - public boolean isStatelessSession() { - return this.statefulSession == null && this.statelessSession != null; - } - - protected StatelessSession getStatelessSession() { - return statelessSession; - } - - protected Session getStatefulSession() { - return statefulSession; - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/AbstractJpaQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/AbstractJpaQueryProvider.java index 0da409e537..f44cb4da39 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/AbstractJpaQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/AbstractJpaQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2021 the original author or authors. + * Copyright 2006-2024 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. @@ -39,8 +39,8 @@ public abstract class AbstractJpaQueryProvider implements JpaQueryProvider, Init /** *

    * Public setter to override the entityManager that was created by this - * {@link HibernateQueryProvider}. This is currently needed to allow - * {@link HibernateQueryProvider} to participate in a user's managed transaction. + * {@link JpaQueryProvider}. This is currently needed to allow + * {@link JpaQueryProvider} to participate in a user's managed transaction. *

    * @param entityManager EntityManager to use */ diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/HibernateNativeQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/HibernateNativeQueryProvider.java deleted file mode 100644 index c2bd2a9a0e..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/HibernateNativeQueryProvider.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2006-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.database.orm; - -import org.hibernate.query.NativeQuery; -import org.hibernate.query.Query; - -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - *

    - * This query provider creates Hibernate {@link Query}s from injected native SQL queries. - * This is useful if there is a need to utilize database-specific features such as query - * hints, the CONNECT keyword in Oracle, etc. - *

    - * - * @author Anatoly Polinsky - * @author Mahmoud Ben Hassine - * @param entity returned by executing the query - * @deprecated since 5.0 for removal in 5.2. Use the {@link JpaNativeQueryProvider} - * instead. - */ -@Deprecated(since = "5.0", forRemoval = true) -public class HibernateNativeQueryProvider extends AbstractHibernateQueryProvider { - - private String sqlQuery; - - private Class entityClass; - - /** - *

    - * Create an {@link NativeQuery} from the session provided (preferring stateless if - * both are available). - *

    - */ - @Override - @SuppressWarnings("unchecked") - public NativeQuery createQuery() { - - if (isStatelessSession()) { - return getStatelessSession().createNativeQuery(sqlQuery).addEntity(entityClass); - } - else { - return getStatefulSession().createNativeQuery(sqlQuery).addEntity(entityClass); - } - } - - public void setSqlQuery(String sqlQuery) { - this.sqlQuery = sqlQuery; - } - - public void setEntityClass(Class entityClazz) { - this.entityClass = entityClazz; - } - - public void afterPropertiesSet() throws Exception { - Assert.state(StringUtils.hasText(sqlQuery), "Native SQL query cannot be empty"); - Assert.state(entityClass != null, "Entity class cannot be NULL"); - } - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/HibernateQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/HibernateQueryProvider.java deleted file mode 100644 index c51930baec..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/orm/HibernateQueryProvider.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2006-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.database.orm; - -import org.hibernate.query.Query; -import org.hibernate.Session; -import org.hibernate.StatelessSession; -import org.springframework.batch.item.ItemReader; - -/** - *

    - * Interface defining the functionality to be provided for generating queries for use with - * Hibernate {@link ItemReader}s or other custom built artifacts. - *

    - * - * @author Anatoly Polinsky - * @author Dave Syer - * @author Mahmoud Ben Hassine - * @since 2.1 - * @deprecated since 5.0 for removal in 5.2. Use the {@link JpaQueryProvider} instead. - * - */ -@Deprecated(since = "5.0", forRemoval = true) -public interface HibernateQueryProvider { - - /** - *

    - * Create the query object which type will be determined by the underline - * implementation (e.g. Hibernate, JPA, etc.) - *

    - * @return created query - */ - Query createQuery(); - - /** - *

    - * Inject a {@link Session} that can be used as a factory for queries. The state of - * the session is controlled by the caller (i.e. it should be closed if necessary). - *

    - * - *

    - * Use either this method or {@link #setStatelessSession(StatelessSession)} - *

    - * @param session the {@link Session} to set - */ - void setSession(Session session); - - /** - *

    - * Inject a {@link StatelessSession} that can be used as a factory for queries. The - * state of the session is controlled by the caller (i.e. it should be closed if - * necessary). - *

    - * - *

    - * Use either this method or {@link #setSession(Session)} - *

    - * @param session the {@link StatelessSession} to set - */ - void setStatelessSession(StatelessSession session); - -} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/annotation/Classifier.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/annotation/Classifier.java deleted file mode 100644 index 03bebcd404..0000000000 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/annotation/Classifier.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2006-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.support.annotation; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Mark a method as capable of classifying its input to an instance of its output. Should - * only be used on non-void methods with one parameter. - * - * @author Dave Syer - * @author Mahmoud Ben Hassine - * @deprecated since 5.0 with no replacement. Scheduled for removal in 5.2. - * - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -@Inherited -@Documented -@Deprecated(since = "5.0", forRemoval = true) -public @interface Classifier { - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/AbstractHibernateCursorItemReaderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/AbstractHibernateCursorItemReaderIntegrationTests.java deleted file mode 100644 index cecc0262c3..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/AbstractHibernateCursorItemReaderIntegrationTests.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2010-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import org.hibernate.SessionFactory; -import org.hibernate.StatelessSession; - -import org.springframework.batch.item.ItemReader; -import org.springframework.batch.item.sample.Foo; -import org.springframework.core.io.ClassPathResource; -import org.springframework.orm.hibernate5.LocalSessionFactoryBean; - -/** - * Tests for {@link HibernateCursorItemReader} using {@link StatelessSession}. - * - * @author Robert Kasanicky - * @author Dave Syer - */ -public abstract class AbstractHibernateCursorItemReaderIntegrationTests - extends AbstractGenericDataSourceItemReaderIntegrationTests { - - @Override - protected ItemReader createItemReader() throws Exception { - - LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean(); - factoryBean.setDataSource(dataSource); - factoryBean.setMappingLocations(new ClassPathResource("Foo.hbm.xml", getClass())); - customizeSessionFactory(factoryBean); - factoryBean.afterPropertiesSet(); - - SessionFactory sessionFactory = factoryBean.getObject(); - - HibernateCursorItemReader hibernateReader = new HibernateCursorItemReader<>(); - setQuery(hibernateReader); - hibernateReader.setSessionFactory(sessionFactory); - hibernateReader.setUseStatelessSession(isUseStatelessSession()); - hibernateReader.afterPropertiesSet(); - hibernateReader.setSaveState(true); - - return hibernateReader; - - } - - protected void customizeSessionFactory(LocalSessionFactoryBean factoryBean) { - } - - protected void setQuery(HibernateCursorItemReader reader) throws Exception { - reader.setQueryString("from Foo"); - } - - protected boolean isUseStatelessSession() { - return true; - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderCommonTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderCommonTests.java deleted file mode 100644 index d99adf816c..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderCommonTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2008-2013 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import org.hibernate.SessionFactory; -import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.item.ItemReader; -import org.springframework.batch.item.sample.Foo; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.orm.hibernate5.LocalSessionFactoryBean; - -public class HibernateCursorItemReaderCommonTests extends AbstractDatabaseItemStreamItemReaderTests { - - @Override - protected ItemReader getItemReader() throws Exception { - - SessionFactory sessionFactory = createSessionFactory(); - - String hsqlQuery = "from Foo"; - - HibernateCursorItemReader reader = new HibernateCursorItemReader<>(); - reader.setQueryString(hsqlQuery); - reader.setSessionFactory(sessionFactory); - reader.setUseStatelessSession(true); - reader.setFetchSize(10); - reader.afterPropertiesSet(); - reader.setSaveState(true); - - return reader; - } - - private SessionFactory createSessionFactory() throws Exception { - LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean(); - factoryBean.setDataSource(getDataSource()); - factoryBean.setMappingLocations(new Resource[] { new ClassPathResource("Foo.hbm.xml", getClass()) }); - factoryBean.afterPropertiesSet(); - - return factoryBean.getObject(); - - } - - @Override - protected void pointToEmptyInput(ItemReader tested) throws Exception { - HibernateCursorItemReader reader = (HibernateCursorItemReader) tested; - reader.close(); - reader.setQueryString("from Foo foo where foo.id = -1"); - reader.afterPropertiesSet(); - reader.open(new ExecutionContext()); - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderIntegrationTests.java deleted file mode 100644 index 3bd5ed7a6d..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderIntegrationTests.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2008-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.hibernate.StatelessSession; -import org.junit.jupiter.api.Test; -import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.item.sample.Foo; - -/** - * Tests for {@link HibernateCursorItemReader} using {@link StatelessSession}. - * - * @author Robert Kasanicky - */ -class HibernateCursorItemReaderIntegrationTests extends AbstractHibernateCursorItemReaderIntegrationTests { - - /** - * Exception scenario. - *

    - * {@link HibernateCursorItemReader#setUseStatelessSession(boolean)} can be called - * only in uninitialized state. - */ - @Test - void testSetUseStatelessSession() { - HibernateCursorItemReader inputSource = (HibernateCursorItemReader) reader; - - // initialize and call setter => error - inputSource.open(new ExecutionContext()); - assertThrows(IllegalStateException.class, () -> inputSource.setUseStatelessSession(false)); - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderNamedQueryIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderNamedQueryIntegrationTests.java deleted file mode 100644 index 37ecb8690d..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderNamedQueryIntegrationTests.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2009-2010 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import org.springframework.batch.item.sample.Foo; - -/** - * Tests {@link HibernateCursorItemReader} configured with named query. - */ -public class HibernateCursorItemReaderNamedQueryIntegrationTests - extends AbstractHibernateCursorItemReaderIntegrationTests { - - @Override - protected void setQuery(HibernateCursorItemReader reader) { - reader.setQueryName("allFoos"); - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderNativeQueryIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderNativeQueryIntegrationTests.java deleted file mode 100644 index d46b9f8b47..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderNativeQueryIntegrationTests.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2010 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import org.springframework.batch.item.database.orm.HibernateNativeQueryProvider; -import org.springframework.batch.item.sample.Foo; - -/** - * @author Anatoly Polinsky - * @author Dave Syer - */ -public class HibernateCursorItemReaderNativeQueryIntegrationTests - extends AbstractHibernateCursorItemReaderIntegrationTests { - - @Override - protected void setQuery(HibernateCursorItemReader hibernateReader) throws Exception { - - String nativeQuery = "select * from T_FOOS"; - - // creating a native query provider as it would be created in configuration - HibernateNativeQueryProvider queryProvider = new HibernateNativeQueryProvider<>(); - - queryProvider.setSqlQuery(nativeQuery); - queryProvider.setEntityClass(Foo.class); - queryProvider.afterPropertiesSet(); - - hibernateReader.setQueryProvider(queryProvider); - - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderParametersIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderParametersIntegrationTests.java deleted file mode 100644 index 90772286c1..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderParametersIntegrationTests.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2010-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import java.util.Collections; - -import org.hibernate.StatelessSession; - -import org.springframework.batch.item.sample.Foo; - -/** - * Tests for {@link HibernateCursorItemReader} using {@link StatelessSession}. - * - * @author Robert Kasanicky - * @author Dave Syer - */ -public class HibernateCursorItemReaderParametersIntegrationTests - extends AbstractHibernateCursorItemReaderIntegrationTests { - - @Override - protected void setQuery(HibernateCursorItemReader reader) { - reader.setQueryString("from Foo where name like :name"); - reader.setParameterValues(Collections.singletonMap("name", "bar%")); - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderStatefulIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderStatefulIntegrationTests.java deleted file mode 100644 index cc361b11f3..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderStatefulIntegrationTests.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2008-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.query.Query; -import org.junit.jupiter.api.Test; - -import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.item.sample.Foo; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * Tests for {@link HibernateCursorItemReader} using standard hibernate {@link Session}. - * - * @author Robert Kasanicky - * @author Will Schipp - */ -class HibernateCursorItemReaderStatefulIntegrationTests extends AbstractHibernateCursorItemReaderIntegrationTests { - - @Override - protected boolean isUseStatelessSession() { - return false; - } - - // Ensure close is called on the stateful session correctly. - @Test - @SuppressWarnings("unchecked") - void testStatefulClose() { - - SessionFactory sessionFactory = mock(); - Session session = mock(); - Query scrollableResults = mock(); - HibernateCursorItemReader itemReader = new HibernateCursorItemReader<>(); - itemReader.setSessionFactory(sessionFactory); - itemReader.setQueryString("testQuery"); - itemReader.setUseStatelessSession(false); - - when(sessionFactory.openSession()).thenReturn(session); - when(session.createQuery("testQuery")).thenReturn(scrollableResults); - when(scrollableResults.setFetchSize(0)).thenReturn(scrollableResults); - - itemReader.open(new ExecutionContext()); - itemReader.close(); - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderStatefulNamedQueryIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderStatefulNamedQueryIntegrationTests.java deleted file mode 100644 index a0b046c072..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorItemReaderStatefulNamedQueryIntegrationTests.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2009 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -/** - * Tests {@link HibernateCursorItemReader} configured with stateful session and named - * query. - */ -public class HibernateCursorItemReaderStatefulNamedQueryIntegrationTests - extends HibernateCursorItemReaderNamedQueryIntegrationTests { - - @Override - protected boolean isUseStatelessSession() { - return false; - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorProjectionItemReaderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorProjectionItemReaderIntegrationTests.java deleted file mode 100644 index e79849849e..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateCursorProjectionItemReaderIntegrationTests.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2008-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import javax.sql.DataSource; - -import org.hibernate.SessionFactory; -import org.hibernate.StatelessSession; -import org.junit.jupiter.api.Test; - -import org.springframework.batch.item.ExecutionContext; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.orm.hibernate5.LocalSessionFactoryBean; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * Tests for {@link HibernateCursorItemReader} using {@link StatelessSession}. - * - * @author Robert Kasanicky - * @author Mahmoud Ben Hassine - */ -@SpringJUnitConfig(locations = "classpath:data-source-context.xml") -class HibernateCursorProjectionItemReaderIntegrationTests { - - @Autowired - private DataSource dataSource; - - private void initializeItemReader(HibernateCursorItemReader reader, String hsqlQuery) throws Exception { - - LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean(); - factoryBean.setDataSource(dataSource); - factoryBean.setMappingLocations(new Resource[] { new ClassPathResource("Foo.hbm.xml", getClass()) }); - factoryBean.afterPropertiesSet(); - - SessionFactory sessionFactory = factoryBean.getObject(); - - reader.setQueryString(hsqlQuery); - reader.setSessionFactory(sessionFactory); - reader.afterPropertiesSet(); - reader.setSaveState(true); - reader.open(new ExecutionContext()); - - } - - @Test - void testMultipleItemsInProjection() throws Exception { - HibernateCursorItemReader reader = new HibernateCursorItemReader<>(); - initializeItemReader(reader, "select f.value, f.name from Foo f"); - Object[] foo1 = reader.read(); - assertEquals(1, foo1[0]); - } - - @Test - void testSingleItemInProjection() throws Exception { - HibernateCursorItemReader reader = new HibernateCursorItemReader<>(); - initializeItemReader(reader, "select f.value from Foo f"); - Object foo1 = reader.read(); - assertEquals(1, foo1); - } - - @Test - void testSingleItemInProjectionWithArrayType() throws Exception { - HibernateCursorItemReader reader = new HibernateCursorItemReader<>(); - initializeItemReader(reader, "select f.value from Foo f"); - assertThrows(ClassCastException.class, () -> { - Object[] foo1 = reader.read(); - }); - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateItemReaderHelperTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateItemReaderHelperTests.java deleted file mode 100644 index fd2d54f75e..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateItemReaderHelperTests.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2006-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.database; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; - -import org.hibernate.SessionFactory; -import org.hibernate.StatelessSession; -import org.junit.jupiter.api.Test; -import org.springframework.test.util.ReflectionTestUtils; - -/** - * @author Dave Syer - * @author Will Schipp - * - */ -class HibernateItemReaderHelperTests { - - private final HibernateItemReaderHelper helper = new HibernateItemReaderHelper<>(); - - private final SessionFactory sessionFactory = mock(); - - @Test - void testOneSessionForAllPages() { - - StatelessSession session = mock(); - when(sessionFactory.openStatelessSession()).thenReturn(session); - - helper.setSessionFactory(sessionFactory); - - helper.createQuery(); - // Multiple calls to createQuery only creates one session - helper.createQuery(); - - } - - @Test - void testSessionReset() { - - StatelessSession session = mock(); - when(sessionFactory.openStatelessSession()).thenReturn(session); - - helper.setSessionFactory(sessionFactory); - - helper.createQuery(); - assertNotNull(ReflectionTestUtils.getField(helper, "statelessSession")); - - helper.close(); - assertNull(ReflectionTestUtils.getField(helper, "statelessSession")); - - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateItemWriterTests.java deleted file mode 100644 index 9d4272130f..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernateItemWriterTests.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2006-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.batch.item.Chunk; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * @author Dave Syer - * @author Thomas Risberg - * @author Michael Minella - * @author Will Schipp - * @author Mahmoud Ben Hassine - */ -class HibernateItemWriterTests { - - HibernateItemWriter writer; - - SessionFactory factory; - - Session currentSession; - - @BeforeEach - void setUp() { - writer = new HibernateItemWriter<>(); - factory = mock(); - currentSession = mock(); - - when(this.factory.getCurrentSession()).thenReturn(this.currentSession); - } - - /** - * Test method for - * {@link org.springframework.batch.item.database.HibernateItemWriter#afterPropertiesSet()} - */ - @Test - void testAfterPropertiesSet() { - writer = new HibernateItemWriter<>(); - Exception exception = assertThrows(IllegalStateException.class, writer::afterPropertiesSet); - String message = exception.getMessage(); - assertTrue(message.contains("SessionFactory"), "Wrong message for exception: " + message); - } - - /** - * Test method for - * {@link org.springframework.batch.item.database.HibernateItemWriter#afterPropertiesSet()} - */ - @Test - void testAfterPropertiesSetWithDelegate() { - writer.setSessionFactory(this.factory); - writer.afterPropertiesSet(); - } - - @Test - void testWriteAndFlushSunnyDayHibernate3() { - this.writer.setSessionFactory(this.factory); - when(this.currentSession.contains("foo")).thenReturn(true); - when(this.currentSession.contains("bar")).thenReturn(false); - this.currentSession.saveOrUpdate("bar"); - this.currentSession.flush(); - this.currentSession.clear(); - - Chunk items = Chunk.of("foo", "bar"); - writer.write(items); - - } - - @Test - void testWriteAndFlushWithFailureHibernate3() { - this.writer.setSessionFactory(this.factory); - final RuntimeException ex = new RuntimeException("ERROR"); - when(this.currentSession.contains("foo")).thenThrow(ex); - - Exception exception = assertThrows(RuntimeException.class, () -> writer.write(Chunk.of("foo"))); - assertEquals("ERROR", exception.getMessage()); - } - - @Test - void testWriteAndFlushSunnyDayHibernate4() { - writer.setSessionFactory(factory); - when(factory.getCurrentSession()).thenReturn(currentSession); - when(currentSession.contains("foo")).thenReturn(true); - when(currentSession.contains("bar")).thenReturn(false); - currentSession.saveOrUpdate("bar"); - currentSession.flush(); - currentSession.clear(); - - Chunk items = Chunk.of("foo", "bar"); - writer.write(items); - } - - @Test - void testWriteAndFlushWithFailureHibernate4() { - writer.setSessionFactory(factory); - final RuntimeException ex = new RuntimeException("ERROR"); - - when(factory.getCurrentSession()).thenReturn(currentSession); - when(currentSession.contains("foo")).thenThrow(ex); - - Exception exception = assertThrows(RuntimeException.class, () -> writer.write(Chunk.of("foo"))); - assertEquals("ERROR", exception.getMessage()); - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernatePagingItemReaderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernatePagingItemReaderIntegrationTests.java deleted file mode 100644 index b2bf1fcd7a..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/HibernatePagingItemReaderIntegrationTests.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2010-2012 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database; - -import org.hibernate.SessionFactory; -import org.hibernate.StatelessSession; - -import org.springframework.batch.item.ItemReader; -import org.springframework.batch.item.sample.Foo; -import org.springframework.core.io.ClassPathResource; -import org.springframework.orm.hibernate5.LocalSessionFactoryBean; - -/** - * Tests for {@link HibernateCursorItemReader} using {@link StatelessSession}. - * - * @author Robert Kasanicky - * @author Dave Syer - */ -public class HibernatePagingItemReaderIntegrationTests extends AbstractGenericDataSourceItemReaderIntegrationTests { - - @Override - protected ItemReader createItemReader() throws Exception { - - LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean(); - factoryBean.setDataSource(dataSource); - factoryBean.setMappingLocations(new ClassPathResource("Foo.hbm.xml", getClass())); - customizeSessionFactory(factoryBean); - factoryBean.afterPropertiesSet(); - - SessionFactory sessionFactory = factoryBean.getObject(); - - HibernatePagingItemReader hibernateReader = new HibernatePagingItemReader<>(); - setQuery(hibernateReader); - hibernateReader.setPageSize(2); - hibernateReader.setSessionFactory(sessionFactory); - hibernateReader.setUseStatelessSession(isUseStatelessSession()); - hibernateReader.afterPropertiesSet(); - hibernateReader.setSaveState(true); - - return hibernateReader; - - } - - protected void customizeSessionFactory(LocalSessionFactoryBean factoryBean) { - } - - protected void setQuery(HibernatePagingItemReader reader) throws Exception { - reader.setQueryString("from Foo"); - } - - protected boolean isUseStatelessSession() { - return true; - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/HibernateCursorItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/HibernateCursorItemReaderBuilderTests.java deleted file mode 100644 index 0611e6b53b..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/HibernateCursorItemReaderBuilderTests.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2017-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database.builder; - -import java.util.HashMap; -import java.util.Map; -import javax.sql.DataSource; - -import org.hibernate.SessionFactory; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.item.database.HibernateCursorItemReader; -import org.springframework.batch.item.database.orm.HibernateNativeQueryProvider; -import org.springframework.batch.item.sample.Foo; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.init.DataSourceInitializer; -import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; -import org.springframework.orm.hibernate5.LocalSessionFactoryBean; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * @author Michael Minella - * @author Mahmoud Ben Hassine - */ -class HibernateCursorItemReaderBuilderTests { - - private SessionFactory sessionFactory; - - private ConfigurableApplicationContext context; - - @BeforeEach - void setUp() { - this.context = new AnnotationConfigApplicationContext(TestDataSourceConfiguration.class); - this.sessionFactory = (SessionFactory) context.getBean("sessionFactory"); - } - - @AfterEach - void tearDown() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - void testConfiguration() throws Exception { - HibernateCursorItemReader reader = new HibernateCursorItemReaderBuilder().name("fooReader") - .sessionFactory(this.sessionFactory) - .fetchSize(2) - .currentItemCount(2) - .maxItemCount(4) - .queryName("allFoos") - .useStatelessSession(true) - .build(); - - reader.afterPropertiesSet(); - - ExecutionContext executionContext = new ExecutionContext(); - - reader.open(executionContext); - Foo item1 = reader.read(); - Foo item2 = reader.read(); - assertNull(reader.read()); - reader.update(executionContext); - reader.close(); - - assertEquals(3, item1.getId()); - assertEquals("bar3", item1.getName()); - assertEquals(3, item1.getValue()); - assertEquals(4, item2.getId()); - assertEquals("bar4", item2.getName()); - assertEquals(4, item2.getValue()); - - assertEquals(2, executionContext.size()); - } - - @Test - void testConfigurationNoSaveState() throws Exception { - Map parameters = new HashMap<>(); - parameters.put("value", 2); - - HibernateCursorItemReader reader = new HibernateCursorItemReaderBuilder().name("fooReader") - .sessionFactory(this.sessionFactory) - .queryString("from Foo foo where foo.id > :value") - .parameterValues(parameters) - .saveState(false) - .build(); - - reader.afterPropertiesSet(); - - ExecutionContext executionContext = new ExecutionContext(); - - reader.open(executionContext); - - int i = 0; - while (reader.read() != null) { - i++; - } - - reader.update(executionContext); - reader.close(); - - assertEquals(3, i); - assertEquals(0, executionContext.size()); - } - - @Test - void testConfigurationQueryProvider() throws Exception { - - HibernateNativeQueryProvider provider = new HibernateNativeQueryProvider<>(); - provider.setEntityClass(Foo.class); - provider.setSqlQuery("select * from T_FOOS"); - provider.afterPropertiesSet(); - - HibernateCursorItemReader reader = new HibernateCursorItemReaderBuilder().name("fooReader") - .sessionFactory(this.sessionFactory) - .queryProvider(provider) - .build(); - - reader.afterPropertiesSet(); - - ExecutionContext executionContext = new ExecutionContext(); - - reader.open(executionContext); - - int i = 0; - while (reader.read() != null) { - i++; - } - - reader.update(executionContext); - reader.close(); - - assertEquals(5, i); - } - - @Test - void testConfigurationNativeQuery() throws Exception { - HibernateCursorItemReader reader = new HibernateCursorItemReaderBuilder().name("fooReader") - .sessionFactory(this.sessionFactory) - .nativeQuery("select * from T_FOOS") - .entityClass(Foo.class) - .build(); - - reader.afterPropertiesSet(); - - ExecutionContext executionContext = new ExecutionContext(); - - reader.open(executionContext); - - int i = 0; - while (reader.read() != null) { - i++; - } - - reader.update(executionContext); - reader.close(); - - assertEquals(5, i); - } - - @Test - void testValidation() { - Exception exception = assertThrows(IllegalStateException.class, - () -> new HibernateCursorItemReaderBuilder().fetchSize(-2).build()); - assertEquals("fetchSize must not be negative", exception.getMessage()); - - exception = assertThrows(IllegalStateException.class, - () -> new HibernateCursorItemReaderBuilder().build()); - assertEquals("A SessionFactory must be provided", exception.getMessage()); - - exception = assertThrows(IllegalStateException.class, - () -> new HibernateCursorItemReaderBuilder().sessionFactory(this.sessionFactory) - .saveState(true) - .build()); - assertEquals("A name is required when saveState is set to true.", exception.getMessage()); - - exception = assertThrows(IllegalStateException.class, - () -> new HibernateCursorItemReaderBuilder().sessionFactory(this.sessionFactory) - .saveState(false) - .build()); - assertEquals("A HibernateQueryProvider, queryName, queryString, " - + "or both the nativeQuery and entityClass must be configured", exception.getMessage()); - } - - @Configuration - public static class TestDataSourceConfiguration { - - @Bean - public DataSource dataSource() { - return new EmbeddedDatabaseBuilder().generateUniqueName(true).build(); - } - - @Bean - public DataSourceInitializer initializer(DataSource dataSource) { - DataSourceInitializer dataSourceInitializer = new DataSourceInitializer(); - dataSourceInitializer.setDataSource(dataSource); - - Resource create = new ClassPathResource("org/springframework/batch/item/database/init-foo-schema.sql"); - dataSourceInitializer.setDatabasePopulator(new ResourceDatabasePopulator(create)); - - return dataSourceInitializer; - } - - @Bean - public SessionFactory sessionFactory() throws Exception { - LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean(); - factoryBean.setDataSource(dataSource()); - factoryBean.setMappingLocations( - new ClassPathResource("/org/springframework/batch/item/database/Foo.hbm.xml", getClass())); - factoryBean.afterPropertiesSet(); - - return factoryBean.getObject(); - - } - - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/HibernateItemWriterBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/HibernateItemWriterBuilderTests.java deleted file mode 100644 index 455496e1a7..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/HibernateItemWriterBuilderTests.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2017-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database.builder; - -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; - -import org.springframework.batch.item.Chunk; -import org.springframework.batch.item.database.HibernateItemWriter; -import org.springframework.batch.item.sample.Foo; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * @author Michael Minella - * @author Mahmoud Ben Hassine - */ -@MockitoSettings(strictness = Strictness.LENIENT) -class HibernateItemWriterBuilderTests { - - @Mock - private SessionFactory sessionFactory; - - @Mock - private Session session; - - @BeforeEach - void setUp() { - when(this.sessionFactory.getCurrentSession()).thenReturn(this.session); - } - - @Test - void testConfiguration() { - HibernateItemWriter itemWriter = new HibernateItemWriterBuilder().sessionFactory(this.sessionFactory) - .build(); - - itemWriter.afterPropertiesSet(); - - Chunk foos = getFoos(); - - itemWriter.write(foos); - - verify(this.session).saveOrUpdate(foos.getItems().get(0)); - verify(this.session).saveOrUpdate(foos.getItems().get(1)); - verify(this.session).saveOrUpdate(foos.getItems().get(2)); - } - - @Test - void testConfigurationClearSession() { - HibernateItemWriter itemWriter = new HibernateItemWriterBuilder().sessionFactory(this.sessionFactory) - .clearSession(false) - .build(); - - itemWriter.afterPropertiesSet(); - - Chunk foos = getFoos(); - - itemWriter.write(foos); - - verify(this.session).saveOrUpdate(foos.getItems().get(0)); - verify(this.session).saveOrUpdate(foos.getItems().get(1)); - verify(this.session).saveOrUpdate(foos.getItems().get(2)); - verify(this.session, never()).clear(); - } - - @Test - void testValidation() { - Exception exception = assertThrows(IllegalStateException.class, - () -> new HibernateItemWriterBuilder().build()); - assertEquals("SessionFactory must be provided", exception.getMessage()); - } - - private Chunk getFoos() { - Chunk foos = new Chunk<>(); - - for (int i = 1; i < 4; i++) { - Foo foo = new Foo(); - foo.setName("foo" + i); - foo.setValue(i); - foos.add(foo); - } - - return foos; - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/HibernatePagingItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/HibernatePagingItemReaderBuilderTests.java deleted file mode 100644 index 843cc867b2..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/HibernatePagingItemReaderBuilderTests.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2017-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.item.database.builder; - -import java.util.HashMap; -import java.util.Map; -import javax.sql.DataSource; - -import org.hibernate.SessionFactory; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.item.database.HibernateItemReaderHelper; -import org.springframework.batch.item.database.HibernatePagingItemReader; -import org.springframework.batch.item.database.orm.HibernateNativeQueryProvider; -import org.springframework.batch.item.sample.Foo; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.init.DataSourceInitializer; -import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; -import org.springframework.orm.hibernate5.LocalSessionFactoryBean; -import org.springframework.test.util.ReflectionTestUtils; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * @author Michael Minella - * @author Mahmoud Ben Hassine - */ -class HibernatePagingItemReaderBuilderTests { - - private SessionFactory sessionFactory; - - private ConfigurableApplicationContext context; - - @BeforeEach - void setUp() { - this.context = new AnnotationConfigApplicationContext( - HibernatePagingItemReaderBuilderTests.TestDataSourceConfiguration.class); - this.sessionFactory = (SessionFactory) context.getBean("sessionFactory"); - } - - @AfterEach - void tearDown() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - @SuppressWarnings("unchecked") - void testConfiguration() throws Exception { - HibernatePagingItemReader reader = new HibernatePagingItemReaderBuilder().name("fooReader") - .sessionFactory(this.sessionFactory) - .fetchSize(2) - .currentItemCount(2) - .maxItemCount(4) - .pageSize(5) - .queryName("allFoos") - .useStatelessSession(false) - .build(); - - reader.afterPropertiesSet(); - - ExecutionContext executionContext = new ExecutionContext(); - - reader.open(executionContext); - Foo item1 = reader.read(); - Foo item2 = reader.read(); - assertNull(reader.read()); - reader.update(executionContext); - reader.close(); - - assertEquals(3, item1.getId()); - assertEquals("bar3", item1.getName()); - assertEquals(3, item1.getValue()); - assertEquals(4, item2.getId()); - assertEquals("bar4", item2.getName()); - assertEquals(4, item2.getValue()); - - assertEquals(2, executionContext.size()); - assertEquals(5, ReflectionTestUtils.getField(reader, "pageSize")); - - HibernateItemReaderHelper helper = (HibernateItemReaderHelper) ReflectionTestUtils.getField(reader, - "helper"); - assertEquals(false, ReflectionTestUtils.getField(helper, "useStatelessSession")); - } - - @Test - void testConfigurationNoSaveState() throws Exception { - Map parameters = new HashMap<>(); - parameters.put("value", 2); - - HibernatePagingItemReader reader = new HibernatePagingItemReaderBuilder().name("fooReader") - .sessionFactory(this.sessionFactory) - .queryString("from Foo foo where foo.id > :value") - .parameterValues(parameters) - .saveState(false) - .build(); - - reader.afterPropertiesSet(); - - ExecutionContext executionContext = new ExecutionContext(); - - reader.open(executionContext); - - int i = 0; - while (reader.read() != null) { - i++; - } - - reader.update(executionContext); - reader.close(); - - assertEquals(3, i); - assertEquals(0, executionContext.size()); - } - - @Test - void testConfigurationQueryProvider() throws Exception { - - HibernateNativeQueryProvider provider = new HibernateNativeQueryProvider<>(); - provider.setEntityClass(Foo.class); - provider.setSqlQuery("select * from T_FOOS"); - provider.afterPropertiesSet(); - - HibernatePagingItemReader reader = new HibernatePagingItemReaderBuilder().name("fooReader") - .sessionFactory(this.sessionFactory) - .queryProvider(provider) - .build(); - - reader.afterPropertiesSet(); - - ExecutionContext executionContext = new ExecutionContext(); - - reader.open(executionContext); - - int i = 0; - while (reader.read() != null) { - i++; - } - - reader.update(executionContext); - reader.close(); - - assertEquals(5, i); - } - - @Test - void testValidation() { - Exception exception = assertThrows(IllegalStateException.class, - () -> new HibernatePagingItemReaderBuilder().sessionFactory(this.sessionFactory) - .fetchSize(-2) - .build()); - assertEquals("fetchSize must not be negative", exception.getMessage()); - - exception = assertThrows(IllegalArgumentException.class, - () -> new HibernatePagingItemReaderBuilder().build()); - assertEquals("A SessionFactory must be provided", exception.getMessage()); - - exception = assertThrows(IllegalArgumentException.class, - () -> new HibernatePagingItemReaderBuilder().sessionFactory(this.sessionFactory) - .saveState(true) - .build()); - assertEquals("A name is required when saveState is set to true", exception.getMessage()); - - exception = assertThrows(IllegalStateException.class, - () -> new HibernatePagingItemReaderBuilder().sessionFactory(this.sessionFactory) - .saveState(false) - .build()); - assertEquals("queryString or queryName must be set", exception.getMessage()); - } - - @Configuration - public static class TestDataSourceConfiguration { - - @Bean - public DataSource dataSource() { - return new EmbeddedDatabaseBuilder().generateUniqueName(true).build(); - } - - @Bean - public DataSourceInitializer initializer(DataSource dataSource) { - DataSourceInitializer dataSourceInitializer = new DataSourceInitializer(); - dataSourceInitializer.setDataSource(dataSource); - - Resource create = new ClassPathResource("org/springframework/batch/item/database/init-foo-schema.sql"); - dataSourceInitializer.setDatabasePopulator(new ResourceDatabasePopulator(create)); - - return dataSourceInitializer; - } - - @Bean - public SessionFactory sessionFactory() throws Exception { - LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean(); - factoryBean.setDataSource(dataSource()); - factoryBean.setMappingLocations( - new ClassPathResource("/org/springframework/batch/item/database/Foo.hbm.xml", getClass())); - factoryBean.afterPropertiesSet(); - - return factoryBean.getObject(); - - } - - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HibernateNativeQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HibernateNativeQueryProviderIntegrationTests.java deleted file mode 100644 index a8a20696f6..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HibernateNativeQueryProviderIntegrationTests.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2006-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.database.support; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.ArrayList; -import java.util.List; - -import javax.sql.DataSource; - -import org.hibernate.query.Query; -import org.hibernate.SessionFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.batch.item.database.orm.HibernateNativeQueryProvider; -import org.springframework.batch.item.sample.Foo; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.orm.hibernate5.LocalSessionFactoryBean; -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; -import org.springframework.transaction.annotation.Transactional; - -/** - * @author Anatoly Polinsky - * @author Dave Syer - * @author Mahmoud Ben Hassine - */ -@SpringJUnitConfig(locations = "classpath:data-source-context.xml") -class HibernateNativeQueryProviderIntegrationTests { - - @Autowired - private DataSource dataSource; - - private final HibernateNativeQueryProvider hibernateQueryProvider; - - private SessionFactory sessionFactory; - - HibernateNativeQueryProviderIntegrationTests() { - hibernateQueryProvider = new HibernateNativeQueryProvider<>(); - hibernateQueryProvider.setEntityClass(Foo.class); - } - - @BeforeEach - void setUp() throws Exception { - - LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean(); - factoryBean.setDataSource(dataSource); - factoryBean.setMappingLocations(new Resource[] { new ClassPathResource("../Foo.hbm.xml", getClass()) }); - factoryBean.afterPropertiesSet(); - - sessionFactory = factoryBean.getObject(); - - } - - @Test - @Transactional - void shouldRetrieveAndMapAllFoos() throws Exception { - - String nativeQuery = "select * from T_FOOS"; - - hibernateQueryProvider.setSqlQuery(nativeQuery); - hibernateQueryProvider.afterPropertiesSet(); - hibernateQueryProvider.setSession(sessionFactory.openSession()); - - Query query = hibernateQueryProvider.createQuery(); - - List expectedFoos = new ArrayList<>(); - - expectedFoos.add(new Foo(1, "bar1", 1)); - expectedFoos.add(new Foo(2, "bar2", 2)); - expectedFoos.add(new Foo(3, "bar3", 3)); - expectedFoos.add(new Foo(4, "bar4", 4)); - expectedFoos.add(new Foo(5, "bar5", 5)); - - List actualFoos = query.list(); - - assertEquals(actualFoos, expectedFoos); - - } - -} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HibernateNativeQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HibernateNativeQueryProviderTests.java deleted file mode 100644 index a03276fd0e..0000000000 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HibernateNativeQueryProviderTests.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2006-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.item.database.support; - -import org.hibernate.Session; -import org.hibernate.StatelessSession; -import org.hibernate.query.NativeQuery; -import org.junit.jupiter.api.Test; - -import org.springframework.batch.item.database.orm.HibernateNativeQueryProvider; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * @author Anatoly Polinsky - * @author Dave Syer - * @author Will Schipp - */ -class HibernateNativeQueryProviderTests { - - private final HibernateNativeQueryProvider hibernateQueryProvider; - - HibernateNativeQueryProviderTests() { - hibernateQueryProvider = new HibernateNativeQueryProvider<>(); - hibernateQueryProvider.setEntityClass(Foo.class); - } - - @Test - @SuppressWarnings("unchecked") - void testCreateQueryWithStatelessSession() { - String sqlQuery = "select * from T_FOOS"; - hibernateQueryProvider.setSqlQuery(sqlQuery); - - StatelessSession session = mock(); - NativeQuery query = mock(); - - when(session.createNativeQuery(sqlQuery)).thenReturn(query); - when(query.addEntity(Foo.class)).thenReturn(query); - - hibernateQueryProvider.setStatelessSession(session); - assertNotNull(hibernateQueryProvider.createQuery()); - - } - - @Test - @SuppressWarnings("unchecked") - void shouldCreateQueryWithStatefulSession() { - String sqlQuery = "select * from T_FOOS"; - hibernateQueryProvider.setSqlQuery(sqlQuery); - - Session session = mock(); - NativeQuery query = mock(); - - when(session.createNativeQuery(sqlQuery)).thenReturn(query); - when(query.addEntity(Foo.class)).thenReturn(query); - - hibernateQueryProvider.setSession(session); - assertNotNull(hibernateQueryProvider.createQuery()); - - } - - private static class Foo { - - } - -} diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/step/package-info.java b/spring-batch-integration/src/main/java/org/springframework/batch/integration/step/package-info.java deleted file mode 100644 index 82e7319a2e..0000000000 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/step/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Support classes related to steps when used with Spring Integration. - * - * @author Mahmoud Ben Hassine - */ -@NonNullApi -package org.springframework.batch.integration.step; - -import org.springframework.lang.NonNullApi; diff --git a/spring-batch-integration/src/main/java/org/springframework/batch/integration/step/DelegateStep.java b/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/DelegateStep.java similarity index 92% rename from spring-batch-integration/src/main/java/org/springframework/batch/integration/step/DelegateStep.java rename to spring-batch-integration/src/test/java/org/springframework/batch/integration/step/DelegateStep.java index 3c09af11f3..4338a9ba70 100644 --- a/spring-batch-integration/src/main/java/org/springframework/batch/integration/step/DelegateStep.java +++ b/spring-batch-integration/src/test/java/org/springframework/batch/integration/step/DelegateStep.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.batch.integration.step; import org.springframework.batch.core.Step; @@ -27,10 +26,8 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine - * @deprecated since 5.0 with no replacement. Scheduled for removal in 5.2. * */ -@Deprecated(since = "5.0", forRemoval = true) public class DelegateStep extends AbstractStep { private Step delegate; @@ -56,4 +53,4 @@ protected void doExecute(StepExecution stepExecution) throws Exception { delegate.execute(stepExecution); } -} +} \ No newline at end of file diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/internal/HibernateAwareCustomerCreditItemWriter.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/internal/HibernateAwareCustomerCreditItemWriter.java deleted file mode 100644 index 55172e1f92..0000000000 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/internal/HibernateAwareCustomerCreditItemWriter.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2006-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.samples.domain.trade.internal; - -import org.hibernate.SessionFactory; - -import org.springframework.batch.item.Chunk; -import org.springframework.batch.item.ItemWriter; -import org.springframework.batch.samples.domain.trade.CustomerCredit; -import org.springframework.batch.samples.domain.trade.CustomerCreditDao; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; - -/** - * Delegates writing to a custom DAO and flushes + clears hibernate session to fulfill the - * {@link ItemWriter} contract. - * - * @author Robert Kasanicky - * @author Michael Minella - * @author Mahmoud Ben Hassine - */ -public class HibernateAwareCustomerCreditItemWriter implements ItemWriter, InitializingBean { - - private CustomerCreditDao dao; - - private SessionFactory sessionFactory; - - @Override - public void write(Chunk items) throws Exception { - for (CustomerCredit credit : items) { - dao.writeCredit(credit); - } - try { - sessionFactory.getCurrentSession().flush(); - } - finally { - // this should happen automatically on commit, but to be on the safe - // side... - sessionFactory.getCurrentSession().clear(); - } - - } - - public void setDao(CustomerCreditDao dao) { - this.dao = dao; - } - - public void setSessionFactory(SessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - } - - @Override - public void afterPropertiesSet() throws Exception { - Assert.state(sessionFactory != null, "Hibernate SessionFactory is required"); - Assert.state(dao != null, "Delegate DAO must be set"); - } - -} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/internal/HibernateCreditDao.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/internal/HibernateCreditDao.java deleted file mode 100644 index b0f3284fd4..0000000000 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/domain/trade/internal/HibernateCreditDao.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2006-2023 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.samples.domain.trade.internal; - -import java.util.ArrayList; -import java.util.List; - -import org.hibernate.SessionFactory; -import org.springframework.batch.repeat.RepeatContext; -import org.springframework.batch.repeat.RepeatListener; -import org.springframework.batch.repeat.RepeatStatus; -import org.springframework.batch.samples.domain.trade.CustomerCredit; -import org.springframework.batch.samples.domain.trade.CustomerCreditDao; - -/** - * @author Lucas Ward - * @author Dave Syer - * @author Mahmoud Ben Hassine - * - */ -public class HibernateCreditDao implements CustomerCreditDao, RepeatListener { - - private int failOnFlush = -1; - - private final List errors = new ArrayList<>(); - - private SessionFactory sessionFactory; - - public void setSessionFactory(SessionFactory sessionFactory) { - this.sessionFactory = sessionFactory; - } - - /** - * Public accessor for the errors property. - * @return the errors - a list of Throwable instances - */ - public List getErrors() { - return errors; - } - - @Override - public void writeCredit(CustomerCredit customerCredit) { - if (customerCredit.getId() == failOnFlush) { - // try to insert one with a duplicate ID - CustomerCredit newCredit = new CustomerCredit(); - newCredit.setId(customerCredit.getId()); - newCredit.setName(customerCredit.getName()); - newCredit.setCredit(customerCredit.getCredit()); - sessionFactory.getCurrentSession().save(newCredit); - } - else { - sessionFactory.getCurrentSession().update(customerCredit); - } - } - - public void write(Object output) { - writeCredit((CustomerCredit) output); - } - - /** - * Public setter for the failOnFlush property. - * @param failOnFlush the ID of the record you want to fail on flush (for testing) - */ - public void setFailOnFlush(int failOnFlush) { - this.failOnFlush = failOnFlush; - } - - @Override - public void onError(RepeatContext context, Throwable e) { - errors.add(e); - } - - @Override - public void after(RepeatContext context, RepeatStatus result) { - } - - @Override - public void before(RepeatContext context) { - } - - @Override - public void close(RepeatContext context) { - } - - @Override - public void open(RepeatContext context) { - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunner.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/misc/jmx/JobRegistryBackgroundJobRunner.java similarity index 98% rename from spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunner.java rename to spring-batch-samples/src/main/java/org/springframework/batch/samples/misc/jmx/JobRegistryBackgroundJobRunner.java index da01a1af5b..7a1a36c231 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/JobRegistryBackgroundJobRunner.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/misc/jmx/JobRegistryBackgroundJobRunner.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.launch.support; +package org.springframework.batch.samples.misc.jmx; import java.io.IOException; import java.util.ArrayList; @@ -57,10 +57,8 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine - * @deprecated since 5.0 with no replacement. Scheduled for removal in 5.2. * */ -@Deprecated(since = "5.0", forRemoval = true) public class JobRegistryBackgroundJobRunner { /** diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/misc/quartz/quartz-job-launcher-context.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/misc/quartz/quartz-job-launcher-context.xml index 076bd0da1d..0be94b1dbe 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/misc/quartz/quartz-job-launcher-context.xml +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/misc/quartz/quartz-job-launcher-context.xml @@ -27,7 +27,7 @@ - + diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherTests.java index 32abadfda9..b3b91d0257 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/misc/jmx/RemoteLauncherTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -21,7 +21,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.batch.core.launch.JobOperator; -import org.springframework.batch.core.launch.support.JobRegistryBackgroundJobRunner; import org.springframework.batch.samples.launch.JobLoader; import org.springframework.jmx.MBeanServerNotFoundException; import org.springframework.jmx.access.InvalidInvocationException; diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/AssertFile.java b/spring-batch-test/src/main/java/org/springframework/batch/test/AssertFile.java deleted file mode 100644 index 8c1170c6c5..0000000000 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/AssertFile.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2006-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.test; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; - -import org.springframework.core.io.Resource; -import org.springframework.util.Assert; - -/** - * This class can be used to assert that two files are the same. - * - * @author Dan Garrette - * @author Glenn Renfro - * @author Mahmoud Ben Hassine - * @since 2.0 - * @deprecated since 5.0 (for removal in 5.2) in favor of test utilities provided by - * modern test libraries like JUnit 5, AssertJ, etc. - */ -@Deprecated(since = "5.0", forRemoval = true) -public abstract class AssertFile { - - public static void assertFileEquals(File expected, File actual) throws Exception { - BufferedReader expectedReader = new BufferedReader(new FileReader(expected)); - BufferedReader actualReader = new BufferedReader(new FileReader(actual)); - try { - int lineNum = 1; - for (String expectedLine = null; (expectedLine = expectedReader.readLine()) != null; lineNum++) { - String actualLine = actualReader.readLine(); - Assert.state(assertStringEqual(expectedLine, actualLine), - "Line number " + lineNum + " does not match."); - } - - String actualLine = actualReader.readLine(); - Assert.state(assertStringEqual(null, actualLine), - "More lines than expected. There should not be a line number " + lineNum + "."); - } - finally { - expectedReader.close(); - actualReader.close(); - } - } - - public static void assertFileEquals(Resource expected, Resource actual) throws Exception { - assertFileEquals(expected.getFile(), actual.getFile()); - } - - public static void assertLineCount(int expectedLineCount, File file) throws Exception { - BufferedReader expectedReader = new BufferedReader(new FileReader(file)); - try { - int lineCount = 0; - while (expectedReader.readLine() != null) { - lineCount++; - } - Assert.state(expectedLineCount == lineCount, String - .format("Line count of %d does not match expected count of %d", lineCount, expectedLineCount)); - } - finally { - expectedReader.close(); - } - } - - public static void assertLineCount(int expectedLineCount, Resource resource) throws Exception { - assertLineCount(expectedLineCount, resource.getFile()); - } - - private static boolean assertStringEqual(String expected, String actual) { - if (expected == null) { - return actual == null; - } - else { - return expected.equals(actual); - } - } - -} diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/DataSourceInitializer.java b/spring-batch-test/src/main/java/org/springframework/batch/test/DataSourceInitializer.java deleted file mode 100755 index c34651ca32..0000000000 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/DataSourceInitializer.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 2006-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.batch.test; - -import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystemNotFoundException; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.List; - -import javax.sql.DataSource; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.BeanInitializationException; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.support.ClassPathXmlApplicationContext; -import org.springframework.core.io.Resource; -import org.springframework.dao.DataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.JdbcTransactionManager; -import org.springframework.transaction.support.TransactionCallback; -import org.springframework.transaction.support.TransactionTemplate; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; - -/** - * Wrapper for a {@link DataSource} that can run scripts on start up and shut down. Use as - * a bean definition
    - * - * Run this class to initialize a database in a running server process. Make sure the - * server is running first by launching the "hsql-server" from the - * hsql.server project. Then you can right click in Eclipse and Run As -> - * Java Application. Do the same any time you want to wipe the database and start again. - * - * @author Dave Syer - * @author Drummond Dawson - * @author Mahmoud Ben Hassine - * @deprecated since 5.0 in favor of similar utilities provided by Spring Framework. - * Scheduled for removal in 5.2. - * - */ -@Deprecated(since = "5.0", forRemoval = true) -public class DataSourceInitializer implements InitializingBean, DisposableBean { - - private static final Log logger = LogFactory.getLog(DataSourceInitializer.class); - - private Resource[] initScripts; - - private Resource[] destroyScripts; - - private DataSource dataSource; - - private boolean ignoreFailedDrop = true; - - private boolean initialized = false; - - /** - * Main method as convenient entry point. - * @param args arguments to be passed to main. - */ - @SuppressWarnings("resource") - public static void main(String... args) { - new ClassPathXmlApplicationContext(ClassUtils.addResourcePathToPackagePath(DataSourceInitializer.class, - DataSourceInitializer.class.getSimpleName() + "-context.xml")); - } - - @Override - public void destroy() { - if (this.destroyScripts == null) { - return; - } - for (Resource destroyScript : this.destroyScripts) { - try { - doExecuteScript(destroyScript); - } - catch (Exception e) { - if (logger.isDebugEnabled()) { - logger.warn("Could not execute destroy script [" + destroyScript + "]", e); - } - else { - logger.warn("Could not execute destroy script [" + destroyScript + "]"); - } - } - } - } - - @Override - public void afterPropertiesSet() { - Assert.state(this.dataSource != null, "A DataSource is required"); - initialize(); - } - - private void initialize() { - if (!this.initialized) { - destroy(); - if (this.initScripts != null) { - for (Resource initScript : this.initScripts) { - doExecuteScript(initScript); - } - } - this.initialized = true; - } - } - - private void doExecuteScript(final Resource scriptResource) { - if (scriptResource == null || !scriptResource.exists()) { - return; - } - TransactionTemplate transactionTemplate = new TransactionTemplate(new JdbcTransactionManager(this.dataSource)); - transactionTemplate.execute((TransactionCallback) status -> { - JdbcTemplate jdbcTemplate = new JdbcTemplate(this.dataSource); - String[] scripts; - try { - scripts = StringUtils.delimitedListToStringArray(stripComments(getScriptLines(scriptResource)), ";"); - } - catch (IOException e) { - throw new BeanInitializationException("Cannot load script from [" + scriptResource + "]", e); - } - for (String script : scripts) { - String trimmedScript = script.trim(); - if (StringUtils.hasText(trimmedScript)) { - try { - jdbcTemplate.execute(trimmedScript); - } - catch (DataAccessException e) { - if (this.ignoreFailedDrop && trimmedScript.toLowerCase().startsWith("drop") - && logger.isDebugEnabled()) { - logger.debug("DROP script failed (ignoring): " + trimmedScript); - } - else { - throw e; - } - } - } - } - return null; - }); - - } - - private List getScriptLines(Resource scriptResource) throws IOException { - URI uri = scriptResource.getURI(); - initFileSystem(uri); - return Files.readAllLines(Paths.get(uri), StandardCharsets.UTF_8); - } - - private void initFileSystem(URI uri) throws IOException { - try { - FileSystems.getFileSystem(uri); - } - catch (FileSystemNotFoundException e) { - FileSystems.newFileSystem(uri, Collections.emptyMap()); - } - catch (IllegalArgumentException e) { - FileSystems.getDefault(); - } - } - - private String stripComments(List list) { - StringBuilder buffer = new StringBuilder(); - for (String line : list) { - if (!line.startsWith("//") && !line.startsWith("--")) { - buffer.append(line).append("\n"); - } - } - return buffer.toString(); - } - - public void setInitScripts(Resource[] initScripts) { - this.initScripts = initScripts; - } - - public void setDestroyScripts(Resource[] destroyScripts) { - this.destroyScripts = destroyScripts; - } - - public void setDataSource(DataSource dataSource) { - this.dataSource = dataSource; - } - - public void setIgnoreFailedDrop(boolean ignoreFailedDrop) { - this.ignoreFailedDrop = ignoreFailedDrop; - } - -} diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java b/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java index 1348b39e6e..ebf96e5d88 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -107,24 +107,6 @@ public static JobExecution createJobExecution(String jobName, Long instanceId, L return createJobExecution(jobName, instanceId, executionId, new JobParameters()); } - /** - * Create a {@link JobExecution} with the parameters provided. - * @param jobName the name of the job - * @param instanceId the Id of the {@link JobInstance} - * @param executionId the id for the {@link JobExecution} - * @param jobParameters new line separated key=value pairs - * @return a {@link JobExecution} - * @deprecated use {{@link #createJobExecution(String, Long, Long, JobParameters)}} - * instead. Will be removed in v5.2 - */ - @Deprecated(since = "5.0.1", forRemoval = true) - public static JobExecution createJobExecution(String jobName, Long instanceId, Long executionId, - String jobParameters) { - JobParameters params = new DefaultJobParametersConverter() - .getJobParameters(PropertiesConverter.stringToProperties(jobParameters)); - return createJobExecution(jobName, instanceId, executionId, params); - } - /** * Create a {@link JobExecution} with the parameters provided. * @param jobName the name of the job diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/AssertFileTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/AssertFileTests.java deleted file mode 100644 index 894ec9dec5..0000000000 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/AssertFileTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2008-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.batch.test; - -import org.junit.jupiter.api.Test; - -import org.springframework.core.io.FileSystemResource; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -/** - * This class can be used to assert that two files are the same. - * - * @author Dan Garrette - * @author Glenn Renfro - * @since 2.0 - */ -class AssertFileTests { - - private static final String DIRECTORY = "src/test/resources/data/input/"; - - @Test - void testAssertEquals_equal() { - assertDoesNotThrow(() -> executeAssertEquals("input1.txt", "input1.txt")); - } - - @Test - public void testAssertEquals_notEqual() throws Exception { - try { - executeAssertEquals("input1.txt", "input2.txt"); - fail(); - } - catch (IllegalStateException e) { - assertTrue(e.getMessage().startsWith("Line number 3 does not match.")); - } - } - - @Test - public void testAssertEquals_tooLong() throws Exception { - try { - executeAssertEquals("input3.txt", "input1.txt"); - fail(); - } - catch (IllegalStateException e) { - assertTrue(e.getMessage().startsWith("More lines than expected. There should not be a line number 4.")); - } - } - - @Test - public void testAssertEquals_tooShort() throws Exception { - try { - executeAssertEquals("input1.txt", "input3.txt"); - fail(); - } - catch (IllegalStateException e) { - assertTrue(e.getMessage().startsWith("Line number 4 does not match.")); - } - } - - @Test - void testAssertEquals_blank_equal() { - assertDoesNotThrow(() -> executeAssertEquals("blank.txt", "blank.txt")); - } - - @Test - public void testAssertEquals_blank_tooLong() throws Exception { - try { - executeAssertEquals("blank.txt", "input1.txt"); - fail(); - } - catch (IllegalStateException e) { - assertTrue(e.getMessage().startsWith("More lines than expected. There should not be a line number 1.")); - } - } - - @Test - public void testAssertEquals_blank_tooShort() throws Exception { - try { - executeAssertEquals("input1.txt", "blank.txt"); - fail(); - } - catch (IllegalStateException e) { - assertTrue(e.getMessage().startsWith("Line number 1 does not match.")); - } - } - - private void executeAssertEquals(String expected, String actual) throws Exception { - AssertFile.assertFileEquals(new FileSystemResource(DIRECTORY + expected), - new FileSystemResource(DIRECTORY + actual)); - } - - @Test - void testAssertLineCount() { - assertDoesNotThrow(() -> AssertFile.assertLineCount(5, new FileSystemResource(DIRECTORY + "input1.txt"))); - } - -} diff --git a/spring-batch-test/src/test/java/org/springframework/batch/test/MetaDataInstanceFactoryTests.java b/spring-batch-test/src/test/java/org/springframework/batch/test/MetaDataInstanceFactoryTests.java index d48a037132..2b6412962e 100644 --- a/spring-batch-test/src/test/java/org/springframework/batch/test/MetaDataInstanceFactoryTests.java +++ b/spring-batch-test/src/test/java/org/springframework/batch/test/MetaDataInstanceFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -70,12 +70,6 @@ void testCreateJobExecutionStringLongLong() { assertNotNull(MetaDataInstanceFactory.createJobExecution(jobName, instanceId, executionId)); } - @Test - void testCreateJobExecutionStringLongLongString() { - assertNotNull( - MetaDataInstanceFactory.createJobExecution(jobName, instanceId, executionId, jobParametersString)); - } - @Test void testCreateJobExecutionStringLongLongJobParameters() { assertNotNull(MetaDataInstanceFactory.createJobExecution(jobName, instanceId, executionId, jobParameters)); From e9eafb5b42b5baed84b64d70c7127266db92905c Mon Sep 17 00:00:00 2001 From: Seungrae Date: Wed, 24 Apr 2024 18:37:56 +0900 Subject: [PATCH 042/152] Fix code sample that uses deprecated StepBuilderFactory Issue #4582 Signed-off-by: Fabrice Bibonne --- spring-batch-docs/modules/ROOT/pages/scalability.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/scalability.adoc b/spring-batch-docs/modules/ROOT/pages/scalability.adoc index 5836fddf56..b00353c4e6 100644 --- a/spring-batch-docs/modules/ROOT/pages/scalability.adoc +++ b/spring-batch-docs/modules/ROOT/pages/scalability.adoc @@ -346,8 +346,8 @@ configuration: [source, java] ---- @Bean -public Step step1Manager() { - return stepBuilderFactory.get("step1.manager") +public Step step1Manager(JobRepository jobRepository) { + return new StepBuilder("step1.manager", jobRepository) .partitioner("step1", partitioner()) .step(step1()) .gridSize(10) From 666b646c771044585f991f8bfc475ad1297ef17a Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 25 Apr 2024 15:15:32 +0200 Subject: [PATCH 043/152] Fix incorrect error message in RecordFieldSetMapper Signed-off-by: Fabrice Bibonne --- .../batch/item/file/mapping/RecordFieldSetMapper.java | 4 ++-- .../batch/item/file/mapping/RecordFieldSetMapperTests.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapper.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapper.java index 860a4a660d..a86079cc0f 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapper.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 the original author or authors. + * Copyright 2020-2024 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. @@ -69,7 +69,7 @@ public RecordFieldSetMapper(Class targetType, ConversionService conversionSer public T mapFieldSet(FieldSet fieldSet) { Assert.isTrue(fieldSet.getFieldCount() == this.constructorParameterNames.length, "Fields count must be equal to record components count"); - Assert.isTrue(fieldSet.hasNames(), "Field names must specified"); + Assert.isTrue(fieldSet.hasNames(), "Field names must be specified"); Object[] args = new Object[0]; if (this.constructorParameterNames != null && this.constructorParameterTypes != null) { args = new Object[this.constructorParameterNames.length]; diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java index 379220931d..06291d4490 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 the original author or authors. + * Copyright 2020-2024 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. @@ -65,7 +65,7 @@ void testMapFieldSetWhenFieldNamesAreNotSpecified() { // when Exception exception = assertThrows(IllegalArgumentException.class, () -> recordFieldSetMapper.mapFieldSet(fieldSet)); - assertEquals("Field names must specified", exception.getMessage()); + assertEquals("Field names must be specified", exception.getMessage()); } public static class Person { From bafbd31be04ecc25d20bd10209229733c4a09e55 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 25 Apr 2024 15:20:01 +0200 Subject: [PATCH 044/152] Change class to record in RecordFieldSetMapperTests Signed-off-by: Fabrice Bibonne --- .../mapping/RecordFieldSetMapperTests.java | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java index 06291d4490..a3fc68ff44 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/RecordFieldSetMapperTests.java @@ -68,26 +68,7 @@ void testMapFieldSetWhenFieldNamesAreNotSpecified() { assertEquals("Field names must be specified", exception.getMessage()); } - public static class Person { - - // TODO change to record in v5 - private final int id; - - private final String name; - - public Person(int id, String name) { - this.id = id; - this.name = name; - } - - public int id() { - return id; - } - - public String name() { - return name; - } - + record Person(int id, String name) { } } From 614731e1fd7525c4c5d3e549f66668cea53effc7 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 29 Apr 2024 12:12:20 +0200 Subject: [PATCH 045/152] Remove unused imports Signed-off-by: Fabrice Bibonne --- .../java/org/springframework/batch/core/JobParameters.java | 2 -- .../org/springframework/batch/item/data/MongoItemWriter.java | 3 +-- .../org/springframework/batch/item/data/Neo4jItemWriter.java | 3 +-- .../batch/item/data/RepositoryItemWriter.java | 3 +-- .../batch/item/file/DefaultBufferedReaderFactory.java | 3 +-- .../batch/item/support/SingleItemPeekableItemReader.java | 4 +--- .../batch/item/support/SynchronizedItemStreamReader.java | 5 +---- .../springframework/batch/test/MetaDataInstanceFactory.java | 2 -- 8 files changed, 6 insertions(+), 19 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java index 8a22407d9b..a5e54b0c65 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParameters.java @@ -26,8 +26,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.Properties; import org.springframework.lang.Nullable; import org.springframework.util.Assert; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemWriter.java index 0cb21451e7..b7aa27f375 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/MongoItemWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -34,7 +34,6 @@ import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemWriter.java index 68d717d1d9..c9bcd35bf6 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/Neo4jItemWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -25,7 +25,6 @@ import org.springframework.batch.item.ItemWriter; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; /** *

    diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemWriter.java index 2065bf0d5a..996bdf5fd3 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -27,7 +27,6 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.data.repository.CrudRepository; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; import org.springframework.util.MethodInvoker; import org.springframework.util.StringUtils; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/DefaultBufferedReaderFactory.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/DefaultBufferedReaderFactory.java index 7684d8791b..01c5995509 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/DefaultBufferedReaderFactory.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/DefaultBufferedReaderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -18,7 +18,6 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; import org.springframework.core.io.Resource; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/SingleItemPeekableItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/SingleItemPeekableItemReader.java index b8780e3613..84e751e7f6 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/SingleItemPeekableItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/SingleItemPeekableItemReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -22,9 +22,7 @@ import org.springframework.batch.item.ItemStream; import org.springframework.batch.item.ItemStreamException; import org.springframework.batch.item.ItemStreamReader; -import org.springframework.batch.item.ParseException; import org.springframework.batch.item.PeekableItemReader; -import org.springframework.batch.item.UnexpectedInputException; import org.springframework.lang.Nullable; /** diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/SynchronizedItemStreamReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/SynchronizedItemStreamReader.java index 3affd83e07..a2909b9228 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/SynchronizedItemStreamReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/SynchronizedItemStreamReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2023 the original author or authors. + * Copyright 2015-2024 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. @@ -20,9 +20,6 @@ import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemStreamReader; -import org.springframework.batch.item.NonTransientResourceException; -import org.springframework.batch.item.ParseException; -import org.springframework.batch.item.UnexpectedInputException; import org.springframework.beans.factory.InitializingBean; import org.springframework.lang.Nullable; import org.springframework.util.Assert; diff --git a/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java b/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java index ebf96e5d88..9ade608be6 100644 --- a/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java +++ b/spring-batch-test/src/main/java/org/springframework/batch/test/MetaDataInstanceFactory.java @@ -21,9 +21,7 @@ import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.support.PropertiesConverter; /** * Convenience methods for creating test instances of {@link JobExecution}, From b746312a4f10c333f8b9627f07ad950cb3678f92 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 29 Apr 2024 16:03:03 +0200 Subject: [PATCH 046/152] Improve error messages in JobParametersBuilder Resolves #4581 Signed-off-by: Fabrice Bibonne --- .../batch/core/JobParametersBuilder.java | 16 ++++++++++++---- .../batch/core/JobParametersBuilderTests.java | 10 +++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java index c4af64719f..a12ad7bc67 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobParametersBuilder.java @@ -105,6 +105,7 @@ public JobParametersBuilder addString(String key, @NonNull String parameter) { * @return a reference to this object. */ public JobParametersBuilder addString(String key, @NonNull String parameter, boolean identifying) { + Assert.notNull(parameter, "Value for parameter '" + key + "' must not be null"); this.parameterMap.put(key, new JobParameter<>(parameter, String.class, identifying)); return this; } @@ -128,6 +129,7 @@ public JobParametersBuilder addDate(String key, @NonNull Date parameter) { * @return a reference to this object. */ public JobParametersBuilder addDate(String key, @NonNull Date parameter, boolean identifying) { + Assert.notNull(parameter, "Value for parameter '" + key + "' must not be null"); this.parameterMap.put(key, new JobParameter<>(parameter, Date.class, identifying)); return this; } @@ -151,6 +153,7 @@ public JobParametersBuilder addLocalDate(String key, @NonNull LocalDate paramete * @return a reference to this object. */ public JobParametersBuilder addLocalDate(String key, @NonNull LocalDate parameter, boolean identifying) { + Assert.notNull(parameter, "Value for parameter '" + key + "' must not be null"); this.parameterMap.put(key, new JobParameter<>(parameter, LocalDate.class, identifying)); return this; } @@ -174,6 +177,7 @@ public JobParametersBuilder addLocalTime(String key, @NonNull LocalTime paramete * @return a reference to this object. */ public JobParametersBuilder addLocalTime(String key, @NonNull LocalTime parameter, boolean identifying) { + Assert.notNull(parameter, "Value for parameter '" + key + "' must not be null"); this.parameterMap.put(key, new JobParameter<>(parameter, LocalTime.class, identifying)); return this; } @@ -197,6 +201,7 @@ public JobParametersBuilder addLocalDateTime(String key, @NonNull LocalDateTime * @return a reference to this object. */ public JobParametersBuilder addLocalDateTime(String key, @NonNull LocalDateTime parameter, boolean identifying) { + Assert.notNull(parameter, "Value for parameter '" + key + "' must not be null"); this.parameterMap.put(key, new JobParameter<>(parameter, LocalDateTime.class, identifying)); return this; } @@ -220,6 +225,7 @@ public JobParametersBuilder addLong(String key, @NonNull Long parameter) { * @return a reference to this object. */ public JobParametersBuilder addLong(String key, @NonNull Long parameter, boolean identifying) { + Assert.notNull(parameter, "Value for parameter '" + key + "' must not be null"); this.parameterMap.put(key, new JobParameter<>(parameter, Long.class, identifying)); return this; } @@ -243,6 +249,7 @@ public JobParametersBuilder addDouble(String key, @NonNull Double parameter) { * @return a reference to this object. */ public JobParametersBuilder addDouble(String key, @NonNull Double parameter, boolean identifying) { + Assert.notNull(parameter, "Value for parameter '" + key + "' must not be null"); this.parameterMap.put(key, new JobParameter<>(parameter, Double.class, identifying)); return this; } @@ -271,27 +278,28 @@ public JobParametersBuilder addJobParameter(String key, JobParameter jobParam /** * Add a job parameter. * @param name the name of the parameter - * @param value the value of the parameter + * @param value the value of the parameter. Must not be {@code null}. * @param type the type of the parameter * @param identifying true if the parameter is identifying. false otherwise * @return a reference to this object. * @param the type of the parameter * @since 5.0 */ - public JobParametersBuilder addJobParameter(String name, T value, Class type, boolean identifying) { + public JobParametersBuilder addJobParameter(String name, @NonNull T value, Class type, boolean identifying) { + Assert.notNull(value, "Value for parameter '" + name + "' must not be null"); return addJobParameter(name, new JobParameter<>(value, type, identifying)); } /** * Add an identifying job parameter. * @param name the name of the parameter - * @param value the value of the parameter + * @param value the value of the parameter. Must not be {@code null}. * @param type the type of the parameter * @return a reference to this object. * @param the type of the parameter * @since 5.0 */ - public JobParametersBuilder addJobParameter(String name, T value, Class type) { + public JobParametersBuilder addJobParameter(String name, @NonNull T value, Class type) { return addJobParameter(name, value, type, true); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java index 2f5cdb5d6e..220dfc4724 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobParametersBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2023 the original author or authors. + * Copyright 2008-2024 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. @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Set; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -85,6 +86,13 @@ void testAddingExistingJobParameters() { assertEquals(finalParams.getString("baz"), "quix"); } + @Test + void testAddingNullJobParameters() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> new JobParametersBuilder().addString("foo", null).toJobParameters()); + Assertions.assertEquals("Value for parameter 'foo' must not be null", exception.getMessage()); + } + @Test void testNonIdentifyingParameters() { this.parametersBuilder.addDate("SCHEDULE_DATE", date, false); From 1aaabb8acc3d2ffb093d7ba4f1031f926dd3f530 Mon Sep 17 00:00:00 2001 From: Faraz <58445945+farazahmadk@users.noreply.github.com> Date: Mon, 9 Oct 2023 23:31:35 +0200 Subject: [PATCH 047/152] Update Javadoc regarding sort order in RepositoryItemReader Issue #4462 Signed-off-by: Fabrice Bibonne --- .../batch/item/data/RepositoryItemReader.java | 6 ++++-- .../item/data/builder/RepositoryItemReaderBuilder.java | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemReader.java index 4494b2d8cb..d44af4edaa 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -117,7 +117,9 @@ public void setArguments(List arguments) { } /** - * Provides ordering of the results so that order is maintained between paged queries + * Provides ordering of the results so that order is maintained between paged queries. + * Use a {@link java.util.LinkedHashMap} in case of multiple sort entries to keep the + * order. * @param sorts the fields to sort by and the directions */ public void setSort(Map sorts) { diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/RepositoryItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/RepositoryItemReaderBuilder.java index e90a21f0ed..8b51679673 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/RepositoryItemReaderBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/builder/RepositoryItemReaderBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2023 the original author or authors. + * Copyright 2017-2024 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. @@ -130,6 +130,8 @@ public RepositoryItemReaderBuilder arguments(Object... arguments) { /** * Provides ordering of the results so that order is maintained between paged queries. + * Use a {@link java.util.LinkedHashMap} in case of multiple sort entries to keep the + * order. * @param sorts the fields to sort by and the directions. * @return The current instance of the builder. * @see RepositoryItemReader#setSort(Map) From 5ec705859d803cdaeaf6d33b8cfbc40639bf7f0f Mon Sep 17 00:00:00 2001 From: "injae.kim" Date: Fri, 17 Nov 2023 20:35:08 +0900 Subject: [PATCH 048/152] Update missing information about error handling in ChunkListener Resolves #4384 Signed-off-by: Fabrice Bibonne --- .../batch/core/ChunkListener.java | 6 +- .../builder/FaultTolerantStepBuilder.java | 6 +- .../step/item/SimpleStepFactoryBeanTests.java | 83 ++++++++++++++++++- .../intercepting-execution.adoc | 3 + 4 files changed, 91 insertions(+), 7 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java index d7339459cd..951410235b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -20,11 +20,15 @@ /** * Listener interface for the lifecycle of a chunk. A chunk can be thought of as a * collection of items that are committed together. + *

    + * {@link ChunkListener} shouldn't throw exceptions and expect continued processing, they + * must be handled in the implementation or the step will terminate. * * @author Lucas Ward * @author Michael Minella * @author Mahmoud Ben Hassine * @author Parikshit Dutta + * @author Injae Kim */ public interface ChunkListener extends StepListener { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java index 78c5fadc9b..c5a5e5eb55 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -689,10 +689,6 @@ private void addNonRetryableExceptionIfMissing(Class... cls /** * ChunkListener that wraps exceptions thrown from the ChunkListener in * {@link FatalStepExecutionException} to force termination of StepExecution - *

    - * ChunkListeners shoulnd't throw exceptions and expect continued processing, they - * must be handled in the implementation or the step will terminate - * */ private static class TerminateOnExceptionChunkListenerDelegate implements ChunkListener { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java index 09743e3c69..d4f6137dbb 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -328,6 +328,87 @@ public void afterChunkError(ChunkContext context) { assertTrue(writeListener.trail.startsWith("1234"), "Listener order not as expected: " + writeListener.trail); } + @Test + void testChunkListenersThrowException() throws Exception { + String[] items = new String[] { "1", "2", "3", "4", "5", "6", "7" }; + int commitInterval = 3; + + SimpleStepFactoryBean factory = getStepFactory(items); + class AssertingWriteListener extends StepListenerSupport { + + String trail = ""; + + @Override + public void beforeWrite(Chunk chunk) { + trail = trail + "2"; + } + + @Override + public void afterWrite(Chunk items) { + trail = trail + "3"; + } + + } + class CountingChunkListener implements ChunkListener { + + int beforeCount = 0; + + int afterCount = 0; + + int failedCount = 0; + + private final AssertingWriteListener writeListener; + + public CountingChunkListener(AssertingWriteListener writeListener) { + super(); + this.writeListener = writeListener; + } + + @Override + public void afterChunk(ChunkContext context) { + writeListener.trail = writeListener.trail + "4"; + afterCount++; + throw new RuntimeException("Step will be terminated when ChunkListener throws exceptions."); + } + + @Override + public void beforeChunk(ChunkContext context) { + writeListener.trail = writeListener.trail + "1"; + beforeCount++; + throw new RuntimeException("Step will be terminated when ChunkListener throws exceptions."); + } + + @Override + public void afterChunkError(ChunkContext context) { + writeListener.trail = writeListener.trail + "5"; + failedCount++; + throw new RuntimeException("Step will be terminated when ChunkListener throws exceptions."); + } + + } + AssertingWriteListener writeListener = new AssertingWriteListener(); + CountingChunkListener chunkListener = new CountingChunkListener(writeListener); + factory.setListeners(new StepListener[] { chunkListener, writeListener }); + factory.setCommitInterval(commitInterval); + + AbstractStep step = (AbstractStep) factory.getObject(); + + job.setSteps(Collections.singletonList((Step) step)); + + JobExecution jobExecution = repository.createJobExecution(job.getName(), new JobParameters()); + job.execute(jobExecution); + + assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); + assertEquals("1", reader.read()); + assertEquals(0, written.size()); + + assertEquals(0, chunkListener.afterCount); + assertEquals(1, chunkListener.beforeCount); + assertEquals(1, chunkListener.failedCount); + assertEquals("15", writeListener.trail); + assertTrue(writeListener.trail.startsWith("15"), "Listener order not as expected: " + writeListener.trail); + } + /* * Commit interval specified is not allowed to be zero or negative. */ diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc index 142cc7772a..023dcaee4f 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/intercepting-execution.adoc @@ -130,6 +130,9 @@ You can apply a `ChunkListener` when there is no chunk declaration. The `Tasklet responsible for calling the `ChunkListener`, so it applies to a non-item-oriented tasklet as well (it is called before and after the tasklet). +A `ChunkListener` is not designed to throw checked exceptions. Errors must be handled in the +implementation or the step will terminate. + [[itemReadListener]] == `ItemReadListener` From 605757ac21a58604e136db04c3264344bae15fe9 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 3 May 2024 08:55:01 +0100 Subject: [PATCH 049/152] Remove wildcard return type in ListItemWriter Resolves #4576 Signed-off-by: Fabrice Bibonne --- .../springframework/batch/item/support/ListItemWriter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/ListItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/ListItemWriter.java index 58247b9ad7..773cd6c3c0 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/ListItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/ListItemWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2023 the original author or authors. + * Copyright 2014-2024 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. @@ -40,7 +40,7 @@ public void write(Chunk chunk) throws Exception { writtenItems.addAll(chunk.getItems()); } - public List getWrittenItems() { + public List getWrittenItems() { return this.writtenItems; } From 63454deb15a735f14c547675719de8b3e3edf02e Mon Sep 17 00:00:00 2001 From: Juyoung Kim Date: Sun, 5 May 2024 20:57:23 +0900 Subject: [PATCH 050/152] Add Data class support in JdbcCursorItemReaderBuilder & JdbcPagingItemReaderBuilder Resolves #4578 Signed-off-by: Fabrice Bibonne --- .../builder/JdbcCursorItemReaderBuilder.java | 14 ++++++ .../builder/JdbcPagingItemReaderBuilder.java | 16 ++++++- .../JdbcCursorItemReaderBuilderTests.java | 44 +++++++++++++++-- .../JdbcPagingItemReaderBuilderTests.java | 47 ++++++++++++++++++- 4 files changed, 115 insertions(+), 6 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java index 980ae932c6..69903eddbb 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java @@ -25,6 +25,7 @@ import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.PreparedStatementSetter; import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.DataClassRowMapper; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -38,6 +39,7 @@ * @author Ankur Trapasiya * @author Parikshit Dutta * @author Fabio Molignoni + * @author Juyoung Kim * @since 4.0 */ public class JdbcCursorItemReaderBuilder { @@ -312,6 +314,18 @@ public JdbcCursorItemReaderBuilder beanRowMapper(Class mappedClass) { return this; } + /** + * Creates a {@link DataClassRowMapper} to be used as your {@link RowMapper}. + * @param mappedClass the class for the row mapper + * @return this instance for method chaining + * @see DataClassRowMapper + */ + public JdbcCursorItemReaderBuilder dataRowMapper(Class mappedClass) { + this.rowMapper = new DataClassRowMapper<>(mappedClass); + + return this; + } + /** * Set whether "autoCommit" should be overridden for the connection used by the * cursor. If not set, defaults to Connection / Datasource default configuration. diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilder.java index 2f9cee1d10..0dc527902f 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2023 the original author or authors. + * Copyright 2017-2024 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. @@ -36,6 +36,7 @@ import org.springframework.batch.item.database.support.SybasePagingQueryProvider; import org.springframework.batch.support.DatabaseType; import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.DataClassRowMapper; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.MetaDataAccessException; import org.springframework.util.Assert; @@ -52,6 +53,7 @@ * @author Drummond Dawson * @author Mahmoud Ben Hassine * @author Minsoo Kim + * @author Juyoung Kim * @since 4.0 * @see JdbcPagingItemReader */ @@ -186,6 +188,18 @@ public JdbcPagingItemReaderBuilder beanRowMapper(Class mappedClass) { return this; } + /** + * Creates a {@link DataClassRowMapper} to be used as your {@link RowMapper}. + * @param mappedClass the class for the row mapper + * @return this instance for method chaining + * @see DataClassRowMapper + */ + public JdbcPagingItemReaderBuilder dataRowMapper(Class mappedClass) { + this.rowMapper = new DataClassRowMapper<>(mappedClass); + + return this; + } + /** * A {@link Map} of values to set on the SQL's prepared statement. * @param parameterValues Map of values diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilderTests.java index c8b2528e1a..ba25663265 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 the original author or authors. + * Copyright 2016-2024 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. @@ -47,6 +47,7 @@ * @author Ankur Trapasiya * @author Parikshit Dutta * @author Mahmoud Ben Hassine + * @author Juyoung Kim */ class JdbcCursorItemReaderBuilderTests { @@ -334,6 +335,29 @@ private void validateFoo(Foo item, int first, String second, String third) { assertEquals(third, item.getThird()); } + @Test + void testDataRowMapper() throws Exception { + JdbcCursorItemReader reader = new JdbcCursorItemReaderBuilder() + .name("barReader") + .dataSource(this.dataSource) + .currentItemCount(1) + .maxItemCount(2) + .sql("SELECT ID, FIRST, SECOND, THIRD FROM BAR ORDER BY ID DESC") + .dataRowMapper(Bar.class) + .build(); + + reader.afterPropertiesSet(); + + reader.open(new ExecutionContext()); + Bar item1 = reader.read(); + assertNull(reader.read()); + + assertEquals(3, item1.id()); + assertEquals(10, item1.first()); + assertEquals("11", item1.second()); + assertEquals("12", item1.third()); + } + public static class Foo { private int first; @@ -368,6 +392,8 @@ public void setThird(String third) { } + public record Bar(int id, int first, String second, String third) {} + @Configuration public static class TestDataSourceConfiguration { @@ -376,12 +402,24 @@ CREATE TABLE FOO ( ID BIGINT IDENTITY NOT NULL PRIMARY KEY , FIRST BIGINT , SECOND VARCHAR(5) NOT NULL, - THIRD VARCHAR(5) NOT NULL);"""; + THIRD VARCHAR(5) NOT NULL); + + CREATE TABLE BAR ( + ID BIGINT IDENTITY NOT NULL PRIMARY KEY , + FIRST BIGINT , + SECOND VARCHAR(5) NOT NULL, + THIRD VARCHAR(5) NOT NULL) ;"""; private static final String INSERT_SQL = """ INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (1, '2', '3'); INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (4, '5', '6'); - INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (7, '8', '9');"""; + INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (7, '8', '9'); + + INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (1, '2', '3'); + INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (4, '5', '6'); + INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (7, '8', '9'); + INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (10, '11', '12'); + INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (13, '14', '15');"""; @Bean public DataSource dataSource() { diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilderTests.java index 47999e920d..8061442cbd 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2023 the original author or authors. + * Copyright 2017-2024 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. @@ -47,6 +47,7 @@ * @author Michael Minella * @author Drummond Dawson * @author Mahmoud Ben Hassine + * @author Juyoung Kim */ class JdbcPagingItemReaderBuilderTests { @@ -262,6 +263,34 @@ void testBeanRowMapper() throws Exception { assertEquals("12", item1.getThird()); } + @Test + void testDataRowMapper() throws Exception { + Map sortKeys = new HashMap<>(1); + sortKeys.put("ID", Order.DESCENDING); + + JdbcPagingItemReader reader = new JdbcPagingItemReaderBuilder() + .name("barReader") + .dataSource(this.dataSource) + .currentItemCount(1) + .maxItemCount(2) + .selectClause("SELECT ID, FIRST, SECOND, THIRD") + .fromClause("BAR") + .sortKeys(sortKeys) + .dataRowMapper(Bar.class) + .build(); + + reader.afterPropertiesSet(); + + reader.open(new ExecutionContext()); + Bar item1 = reader.read(); + assertNull(reader.read()); + + assertEquals(3, item1.id()); + assertEquals(10, item1.first()); + assertEquals("11", item1.second()); + assertEquals("12", item1.third()); + } + @Test void testValidation() { var builder = new JdbcPagingItemReaderBuilder(); @@ -354,6 +383,8 @@ public void setThird(String third) { } + public record Bar(int id, int first, String second, String third) {} + @Configuration public static class TestDataSourceConfiguration { @@ -362,6 +393,12 @@ CREATE TABLE FOO ( ID BIGINT IDENTITY NOT NULL PRIMARY KEY , FIRST BIGINT , SECOND VARCHAR(5) NOT NULL, + THIRD VARCHAR(5) NOT NULL) ; + + CREATE TABLE BAR ( + ID BIGINT IDENTITY NOT NULL PRIMARY KEY , + FIRST BIGINT , + SECOND VARCHAR(5) NOT NULL, THIRD VARCHAR(5) NOT NULL) ;"""; private static final String INSERT_SQL = """ @@ -369,7 +406,13 @@ SECOND VARCHAR(5) NOT NULL, INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (4, '5', '6'); INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (7, '8', '9'); INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (10, '11', '12'); - INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (13, '14', '15');"""; + INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (13, '14', '15'); + + INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (1, '2', '3'); + INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (4, '5', '6'); + INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (7, '8', '9'); + INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (10, '11', '12'); + INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (13, '14', '15');"""; @Bean public DataSource dataSource() { From a196e19f2810d755197e4cc011b1b3252f8e03ad Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 7 May 2024 06:39:19 +0100 Subject: [PATCH 051/152] Refine contribution #4585 - Update Javadocs - Update tests Signed-off-by: Fabrice Bibonne --- .../builder/JdbcCursorItemReaderBuilder.java | 1 + .../builder/JdbcPagingItemReaderBuilder.java | 1 + .../JdbcCursorItemReaderBuilderTests.java | 52 ++++++++++--------- .../JdbcPagingItemReaderBuilderTests.java | 10 ++-- 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java index 69903eddbb..a4014536d5 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilder.java @@ -319,6 +319,7 @@ public JdbcCursorItemReaderBuilder beanRowMapper(Class mappedClass) { * @param mappedClass the class for the row mapper * @return this instance for method chaining * @see DataClassRowMapper + * @since 5.2 */ public JdbcCursorItemReaderBuilder dataRowMapper(Class mappedClass) { this.rowMapper = new DataClassRowMapper<>(mappedClass); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilder.java index 0dc527902f..408263ea42 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilder.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilder.java @@ -193,6 +193,7 @@ public JdbcPagingItemReaderBuilder beanRowMapper(Class mappedClass) { * @param mappedClass the class for the row mapper * @return this instance for method chaining * @see DataClassRowMapper + * @since 5.2 */ public JdbcPagingItemReaderBuilder dataRowMapper(Class mappedClass) { this.rowMapper = new DataClassRowMapper<>(mappedClass); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilderTests.java index ba25663265..16e33c6107 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcCursorItemReaderBuilderTests.java @@ -329,33 +329,36 @@ void testValidation() { assertEquals("A rowmapper is required", exception.getMessage()); } - private void validateFoo(Foo item, int first, String second, String third) { - assertEquals(first, item.getFirst()); - assertEquals(second, item.getSecond()); - assertEquals(third, item.getThird()); - } - @Test void testDataRowMapper() throws Exception { - JdbcCursorItemReader reader = new JdbcCursorItemReaderBuilder() - .name("barReader") - .dataSource(this.dataSource) - .currentItemCount(1) - .maxItemCount(2) - .sql("SELECT ID, FIRST, SECOND, THIRD FROM BAR ORDER BY ID DESC") - .dataRowMapper(Bar.class) - .build(); + JdbcCursorItemReader reader = new JdbcCursorItemReaderBuilder().name("barReader") + .dataSource(this.dataSource) + .sql("SELECT * FROM BAR ORDER BY FIRST") + .dataRowMapper(Bar.class) + .build(); reader.afterPropertiesSet(); reader.open(new ExecutionContext()); - Bar item1 = reader.read(); + + validateBar(reader.read(), 0, 1, "2", "3"); + validateBar(reader.read(), 1, 4, "5", "6"); + validateBar(reader.read(), 2, 7, "8", "9"); + assertNull(reader.read()); + } - assertEquals(3, item1.id()); - assertEquals(10, item1.first()); - assertEquals("11", item1.second()); - assertEquals("12", item1.third()); + private void validateFoo(Foo item, int first, String second, String third) { + assertEquals(first, item.getFirst()); + assertEquals(second, item.getSecond()); + assertEquals(third, item.getThird()); + } + + private void validateBar(Bar item, int id, int first, String second, String third) { + assertEquals(id, item.id()); + assertEquals(first, item.first()); + assertEquals(second, item.second()); + assertEquals(third, item.third()); } public static class Foo { @@ -392,7 +395,8 @@ public void setThird(String third) { } - public record Bar(int id, int first, String second, String third) {} + public record Bar(int id, int first, String second, String third) { + } @Configuration public static class TestDataSourceConfiguration { @@ -403,7 +407,7 @@ CREATE TABLE FOO ( FIRST BIGINT , SECOND VARCHAR(5) NOT NULL, THIRD VARCHAR(5) NOT NULL); - + CREATE TABLE BAR ( ID BIGINT IDENTITY NOT NULL PRIMARY KEY , FIRST BIGINT , @@ -414,12 +418,10 @@ SECOND VARCHAR(5) NOT NULL, INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (1, '2', '3'); INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (4, '5', '6'); INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (7, '8', '9'); - + INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (1, '2', '3'); INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (4, '5', '6'); - INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (7, '8', '9'); - INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (10, '11', '12'); - INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (13, '14', '15');"""; + INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (7, '8', '9');"""; @Bean public DataSource dataSource() { diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilderTests.java index 8061442cbd..a6220cbeb1 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/builder/JdbcPagingItemReaderBuilderTests.java @@ -268,8 +268,7 @@ void testDataRowMapper() throws Exception { Map sortKeys = new HashMap<>(1); sortKeys.put("ID", Order.DESCENDING); - JdbcPagingItemReader reader = new JdbcPagingItemReaderBuilder() - .name("barReader") + JdbcPagingItemReader reader = new JdbcPagingItemReaderBuilder().name("barReader") .dataSource(this.dataSource) .currentItemCount(1) .maxItemCount(2) @@ -383,7 +382,8 @@ public void setThird(String third) { } - public record Bar(int id, int first, String second, String third) {} + public record Bar(int id, int first, String second, String third) { + } @Configuration public static class TestDataSourceConfiguration { @@ -394,7 +394,7 @@ CREATE TABLE FOO ( FIRST BIGINT , SECOND VARCHAR(5) NOT NULL, THIRD VARCHAR(5) NOT NULL) ; - + CREATE TABLE BAR ( ID BIGINT IDENTITY NOT NULL PRIMARY KEY , FIRST BIGINT , @@ -407,7 +407,7 @@ SECOND VARCHAR(5) NOT NULL, INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (7, '8', '9'); INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (10, '11', '12'); INSERT INTO FOO (FIRST, SECOND, THIRD) VALUES (13, '14', '15'); - + INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (1, '2', '3'); INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (4, '5', '6'); INSERT INTO BAR (FIRST, SECOND, THIRD) VALUES (7, '8', '9'); From 01bfcd92dc89e0eae1c5d13b578f290443b37397 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Wed, 7 Feb 2024 18:32:54 +0100 Subject: [PATCH 052/152] Reduce formatter initializations in `DefaultFieldSet` Resolves #1694 Signed-off-by: Fabrice Bibonne --- .../item/file/transform/DefaultFieldSet.java | 62 +++++++++++++++---- .../transform/DefaultFieldSetFactory.java | 37 ++++++----- 2 files changed, 71 insertions(+), 28 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DefaultFieldSet.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DefaultFieldSet.java index 9214291e09..540a8236aa 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DefaultFieldSet.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DefaultFieldSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -45,17 +45,13 @@ public class DefaultFieldSet implements FieldSet { private final static String DEFAULT_DATE_PATTERN = "yyyy-MM-dd"; - private DateFormat dateFormat = new SimpleDateFormat(DEFAULT_DATE_PATTERN); + private DateFormat dateFormat; - { - dateFormat.setLenient(false); - } + private NumberFormat numberFormat; - private NumberFormat numberFormat = NumberFormat.getInstance(Locale.US); + private String grouping; - private String grouping = ","; - - private String decimal = "."; + private String decimal; /** * The fields wrapped by this 'FieldSet' instance. @@ -77,6 +73,10 @@ public final void setNumberFormat(NumberFormat numberFormat) { } } + private static NumberFormat getDefaultNumberFormat() { + return NumberFormat.getInstance(Locale.US); + } + /** * The {@link DateFormat} to use for parsing dates. If unset the default pattern is * ISO standard yyyy-MM-dd. @@ -86,15 +86,35 @@ public void setDateFormat(DateFormat dateFormat) { this.dateFormat = dateFormat; } + private static DateFormat getDefaultDateFormat() { + DateFormat dateFormat = new SimpleDateFormat(DEFAULT_DATE_PATTERN); + dateFormat.setLenient(false); + return dateFormat; + } + /** * Create a FieldSet with anonymous tokens. They can only be retrieved by column * number. * @param tokens the token values + * @param dateFormat the {@link DateFormat} to use + * @param numberFormat the {@link NumberFormat} to use * @see FieldSet#readString(int) + * @since 5.2 */ - public DefaultFieldSet(String[] tokens) { + public DefaultFieldSet(String[] tokens, @Nullable DateFormat dateFormat, @Nullable NumberFormat numberFormat) { this.tokens = tokens == null ? null : tokens.clone(); - setNumberFormat(NumberFormat.getInstance(Locale.US)); + setDateFormat(dateFormat == null ? getDefaultDateFormat() : dateFormat); + setNumberFormat(numberFormat == null ? getDefaultNumberFormat() : numberFormat); + } + + /** + * Create a FieldSet with anonymous tokens. They can only be retrieved by column + * number. + * @param tokens the token values + * @see FieldSet#readString(int) + */ + public DefaultFieldSet(String[] tokens) { + this(tokens, null, null); } /** @@ -102,9 +122,13 @@ public DefaultFieldSet(String[] tokens) { * by name or by column number. * @param tokens the token values * @param names the names of the tokens + * @param dateFormat the {@link DateFormat} to use + * @param numberFormat the {@link NumberFormat} to use * @see FieldSet#readString(String) + * @since 5.2 */ - public DefaultFieldSet(String[] tokens, String[] names) { + public DefaultFieldSet(String[] tokens, String[] names, @Nullable DateFormat dateFormat, + @Nullable NumberFormat numberFormat) { Assert.notNull(tokens, "Tokens must not be null"); Assert.notNull(names, "Names must not be null"); if (tokens.length != names.length) { @@ -113,7 +137,19 @@ public DefaultFieldSet(String[] tokens, String[] names) { } this.tokens = tokens.clone(); this.names = Arrays.asList(names); - setNumberFormat(NumberFormat.getInstance(Locale.US)); + setDateFormat(dateFormat == null ? getDefaultDateFormat() : dateFormat); + setNumberFormat(numberFormat == null ? getDefaultNumberFormat() : numberFormat); + } + + /** + * Create a FieldSet with named tokens. The token values can then be retrieved either + * by name or by column number. + * @param tokens the token values + * @param names the names of the tokens + * @see FieldSet#readString(String) + */ + public DefaultFieldSet(String[] tokens, String[] names) { + this(tokens, names, null, null); } @Override diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DefaultFieldSetFactory.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DefaultFieldSetFactory.java index a958f50a1f..fe3dd0989c 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DefaultFieldSetFactory.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/DefaultFieldSetFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2023 the original author or authors. + * Copyright 2009-2024 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. @@ -18,6 +18,8 @@ import java.text.DateFormat; import java.text.NumberFormat; +import org.springframework.lang.Nullable; + /** * Default implementation of {@link FieldSetFactory} with no special knowledge of the * {@link FieldSet} required. Returns a {@link DefaultFieldSet} from both factory methods. @@ -32,6 +34,23 @@ public class DefaultFieldSetFactory implements FieldSetFactory { private NumberFormat numberFormat; + /** + * Default constructor. + */ + public DefaultFieldSetFactory() { + } + + /** + * Convenience constructor + * @param dateFormat the {@link DateFormat} to use for parsing dates + * @param numberFormat the {@link NumberFormat} to use for parsing numbers + * @since 5.2 + */ + public DefaultFieldSetFactory(@Nullable DateFormat dateFormat, @Nullable NumberFormat numberFormat) { + this.dateFormat = dateFormat; + this.numberFormat = numberFormat; + } + /** * The {@link NumberFormat} to use for parsing numbers. If unset then * {@link java.util.Locale#US} will be used. @@ -55,8 +74,7 @@ public void setDateFormat(DateFormat dateFormat) { */ @Override public FieldSet create(String[] values, String[] names) { - DefaultFieldSet fieldSet = new DefaultFieldSet(values, names); - return enhance(fieldSet); + return new DefaultFieldSet(values, names, dateFormat, numberFormat); } /** @@ -64,18 +82,7 @@ public FieldSet create(String[] values, String[] names) { */ @Override public FieldSet create(String[] values) { - DefaultFieldSet fieldSet = new DefaultFieldSet(values); - return enhance(fieldSet); - } - - private FieldSet enhance(DefaultFieldSet fieldSet) { - if (dateFormat != null) { - fieldSet.setDateFormat(dateFormat); - } - if (numberFormat != null) { - fieldSet.setNumberFormat(numberFormat); - } - return fieldSet; + return new DefaultFieldSet(values, dateFormat, numberFormat); } } From 310c38fb6c1cb4d4928e687612970c29aa170b8c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 10 May 2024 11:58:34 +0200 Subject: [PATCH 053/152] Fix incorrect Javadoc in SimpleSystemProcessExitCodeMapper Signed-off-by: Fabrice Bibonne --- .../core/step/tasklet/SimpleSystemProcessExitCodeMapper.java | 5 +++-- .../step/tasklet/SimpleSystemProcessExitCodeMapperTests.java | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SimpleSystemProcessExitCodeMapper.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SimpleSystemProcessExitCodeMapper.java index 1685914c1a..55b5684f3d 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SimpleSystemProcessExitCodeMapper.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/SimpleSystemProcessExitCodeMapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -22,9 +22,10 @@ * Simple {@link SystemProcessExitCodeMapper} implementation that performs following * mapping: *

    - * 0 -> ExitStatus.FINISHED else -> ExitStatus.FAILED + * 0 -> ExitStatus.COMPLETED else -> ExitStatus.FAILED * * @author Robert Kasanicky + * @author Mahmoud Ben Hassine */ public class SimpleSystemProcessExitCodeMapper implements SystemProcessExitCodeMapper { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/SimpleSystemProcessExitCodeMapperTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/SimpleSystemProcessExitCodeMapperTests.java index a6df66f121..bbd253f425 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/SimpleSystemProcessExitCodeMapperTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/SimpleSystemProcessExitCodeMapperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2022 the original author or authors. + * Copyright 2008-2024 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. @@ -28,7 +28,7 @@ class SimpleSystemProcessExitCodeMapperTests { private final SimpleSystemProcessExitCodeMapper mapper = new SimpleSystemProcessExitCodeMapper(); /** - * 0 -> ExitStatus.FINISHED else -> ExitStatus.FAILED + * 0 -> ExitStatus.COMPLETED else -> ExitStatus.FAILED */ @Test void testMapping() { From ee9acfdc24b7fb8ffa2297fa9d34d0543f5c4c22 Mon Sep 17 00:00:00 2001 From: Cameron Kirk Date: Sun, 12 May 2024 09:31:09 -0700 Subject: [PATCH 054/152] Fix typo in java-config.adoc Issue #4591 Signed-off-by: Fabrice Bibonne --- spring-batch-docs/modules/ROOT/pages/job/java-config.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-batch-docs/modules/ROOT/pages/job/java-config.adoc b/spring-batch-docs/modules/ROOT/pages/job/java-config.adoc index 3cbcb727cc..472650b763 100644 --- a/spring-batch-docs/modules/ROOT/pages/job/java-config.adoc +++ b/spring-batch-docs/modules/ROOT/pages/job/java-config.adoc @@ -42,6 +42,7 @@ public class MyJobConfiguration { return new JdbcTransactionManager(dataSource); } + @Bean public Job job(JobRepository jobRepository) { return new JobBuilder("myJob", jobRepository) //define job flow as needed From 9647be2ecccba3424cc2f96a48474e6396adb965 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 13 May 2024 16:11:27 +0200 Subject: [PATCH 055/152] Update antora resources Signed-off-by: Fabrice Bibonne --- spring-batch-docs/antora-playbook.yml | 2 +- spring-batch-docs/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-docs/antora-playbook.yml b/spring-batch-docs/antora-playbook.yml index 037f9e80d6..bf81856fbb 100644 --- a/spring-batch-docs/antora-playbook.yml +++ b/spring-batch-docs/antora-playbook.yml @@ -37,5 +37,5 @@ runtime: format: pretty ui: bundle: - url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.7/ui-bundle.zip + url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.11/ui-bundle.zip snapshot: true \ No newline at end of file diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index 0768fac6c7..c0e835ea10 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -26,7 +26,7 @@ @antora/atlas-extension@1.0.0-alpha.1 @antora/collector-extension@1.0.0-alpha.3 @asciidoctor/tabs@1.0.0-beta.3 - @springio/antora-extensions@1.8.2 + @springio/antora-extensions@1.10.0 @springio/asciidoctor-extensions@1.0.0-alpha.9 From b0c602f6901852bfee7be3f954808452ff2ad1fc Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 14 May 2024 12:01:51 +0200 Subject: [PATCH 056/152] Move aot.factories file under resources folder Signed-off-by: Fabrice Bibonne --- spring-batch-integration/src/main/java/META-INF/MANIFEST.MF | 3 --- .../src/main/{java => resources}/META-INF/spring/aot.factories | 0 2 files changed, 3 deletions(-) delete mode 100644 spring-batch-integration/src/main/java/META-INF/MANIFEST.MF rename spring-batch-integration/src/main/{java => resources}/META-INF/spring/aot.factories (100%) diff --git a/spring-batch-integration/src/main/java/META-INF/MANIFEST.MF b/spring-batch-integration/src/main/java/META-INF/MANIFEST.MF deleted file mode 100644 index 5e9495128c..0000000000 --- a/spring-batch-integration/src/main/java/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/spring-batch-integration/src/main/java/META-INF/spring/aot.factories b/spring-batch-integration/src/main/resources/META-INF/spring/aot.factories similarity index 100% rename from spring-batch-integration/src/main/java/META-INF/spring/aot.factories rename to spring-batch-integration/src/main/resources/META-INF/spring/aot.factories From 3d07a347ee33bc25a3569d3f76760ea468a680b5 Mon Sep 17 00:00:00 2001 From: ChangYong Date: Wed, 15 May 2024 15:15:54 +0900 Subject: [PATCH 057/152] Fix incorrect link in appendix Issue #4595 Signed-off-by: Fabrice Bibonne --- spring-batch-docs/modules/ROOT/pages/appendix.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-batch-docs/modules/ROOT/pages/appendix.adoc b/spring-batch-docs/modules/ROOT/pages/appendix.adoc index 14c09d122c..28d4ca344e 100644 --- a/spring-batch-docs/modules/ROOT/pages/appendix.adoc +++ b/spring-batch-docs/modules/ROOT/pages/appendix.adoc @@ -39,7 +39,7 @@ This reader stores message offsets in the execution context to support restart c |`ItemReaderAdapter`|Adapts any class to the `ItemReader` interface.|Yes |`JdbcCursorItemReader`|Reads from a database cursor over JDBC. See - link:readersAndWriters.html#cursorBasedItemReaders["`Cursor-based ItemReaders`"].|No + link:readers-and-writers/database.html#cursorBasedItemReaders["`Cursor-based ItemReaders`"].|No |`JdbcPagingItemReader`|Given an SQL statement, pages through the rows, such that large datasets can be read without running out of memory.|Yes From 9cdd0037dacfe640f08b326260c5b7b1e6448861 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 21 May 2024 16:14:16 +0200 Subject: [PATCH 058/152] Make line separator configurable in RecursiveCollectionLineAggregator Resolves #4594 Signed-off-by: Fabrice Bibonne --- .../RecursiveCollectionLineAggregator.java | 23 +++++++++++++++---- ...cursiveCollectionLineAggregatorTests.java} | 20 +++++++++++----- 2 files changed, 32 insertions(+), 11 deletions(-) rename spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/{RecursiveCollectionItemTransformerTests.java => RecursiveCollectionLineAggregatorTests.java} (72%) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/RecursiveCollectionLineAggregator.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/RecursiveCollectionLineAggregator.java index b5c7fa8ef9..ddb447047d 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/RecursiveCollectionLineAggregator.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/RecursiveCollectionLineAggregator.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -18,16 +18,19 @@ import java.util.Collection; +import org.springframework.util.Assert; + /** * An implementation of {@link LineAggregator} that concatenates a collection of items of - * a common type with the system line separator. + * a common type with a line separator. * * @author Dave Syer + * @author Mahmoud Ben Hassine * */ public class RecursiveCollectionLineAggregator implements LineAggregator> { - private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + private String lineSeparator = System.lineSeparator(); private LineAggregator delegate = new PassThroughLineAggregator<>(); @@ -41,13 +44,23 @@ public void setDelegate(LineAggregator delegate) { this.delegate = delegate; } + /** + * Set the line separator to use. Defaults to the System's line separator. + * @param lineSeparator the line separator to use. Must not be {@code null}. + * @since 5.2 + */ + public void setLineSeparator(String lineSeparator) { + Assert.notNull(lineSeparator, "The line separator must not be null"); + this.lineSeparator = lineSeparator; + } + @Override public String aggregate(Collection items) { StringBuilder builder = new StringBuilder(); for (T value : items) { - builder.append(delegate.aggregate(value)).append(LINE_SEPARATOR); + builder.append(delegate.aggregate(value)).append(lineSeparator); } - return builder.delete(builder.length() - LINE_SEPARATOR.length(), builder.length()).toString(); + return builder.delete(builder.length() - lineSeparator.length(), builder.length()).toString(); } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/RecursiveCollectionItemTransformerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/RecursiveCollectionLineAggregatorTests.java similarity index 72% rename from spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/RecursiveCollectionItemTransformerTests.java rename to spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/RecursiveCollectionLineAggregatorTests.java index 4e0048ce35..eef13e3db3 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/RecursiveCollectionItemTransformerTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/RecursiveCollectionLineAggregatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -19,6 +19,7 @@ import java.util.Collections; import org.junit.jupiter.api.Test; + import org.springframework.util.StringUtils; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -28,9 +29,7 @@ * @author Mahmoud Ben Hassine * */ -class RecursiveCollectionItemTransformerTests { - - private static final String LINE_SEPARATOR = System.getProperty("line.separator"); +class RecursiveCollectionLineAggregatorTests { private final RecursiveCollectionLineAggregator aggregator = new RecursiveCollectionLineAggregator<>(); @@ -41,9 +40,18 @@ void testSetDelegateAndPassInString() { } @Test - void testTransformList() { + void testAggregateListWithDefaultLineSeparator() { + String result = aggregator.aggregate(Arrays.asList(StringUtils.commaDelimitedListToStringArray("foo,bar"))); + String[] array = StringUtils.delimitedListToStringArray(result, System.lineSeparator()); + assertEquals("foo", array[0]); + assertEquals("bar", array[1]); + } + + @Test + void testAggregateListWithCustomLineSeparator() { + aggregator.setLineSeparator("#"); String result = aggregator.aggregate(Arrays.asList(StringUtils.commaDelimitedListToStringArray("foo,bar"))); - String[] array = StringUtils.delimitedListToStringArray(result, LINE_SEPARATOR); + String[] array = StringUtils.delimitedListToStringArray(result, "#"); assertEquals("foo", array[0]); assertEquals("bar", array[1]); } From 95675250c93e936d3090ca0a9b996ed7c2d7441d Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Sun, 19 May 2024 19:29:31 +0200 Subject: [PATCH 059/152] Keep heap lean during remote partition polling Resolves #4598 Signed-off-by: Fabrice Bibonne --- .../MessageChannelPartitionHandler.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) 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 index a5cc624196..f0c710c544 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2009-2023 the original author or authors. + * Copyright 2009-2024 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. @@ -251,24 +251,22 @@ protected Set doHandle(StepExecution managerStepExecution, private Set pollReplies(final StepExecution managerStepExecution, final Set split) throws Exception { - final Set result = new HashSet<>(split.size()); + Set partitionStepExecutionIds = split.stream().map(StepExecution::getId).collect(Collectors.toSet()); Callable> callback = () -> { - Set currentStepExecutionIds = split.stream().map(StepExecution::getId).collect(Collectors.toSet()); JobExecution jobExecution = jobExplorer.getJobExecution(managerStepExecution.getJobExecutionId()); - jobExecution.getStepExecutions() + Set finishedStepExecutions = jobExecution.getStepExecutions() .stream() - .filter(stepExecution -> currentStepExecutionIds.contains(stepExecution.getId())) - .filter(stepExecution -> !result.contains(stepExecution)) + .filter(stepExecution -> partitionStepExecutionIds.contains(stepExecution.getId())) .filter(stepExecution -> !stepExecution.getStatus().isRunning()) - .forEach(result::add); + .collect(Collectors.toSet()); if (logger.isDebugEnabled()) { logger.debug(String.format("Currently waiting on %s partitions to finish", split.size())); } - if (result.size() == split.size()) { - return result; + if (finishedStepExecutions.size() == split.size()) { + return finishedStepExecutions; } else { return null; From deb5bb27412097b9b1dd135c2f98ef3dde24b243 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 23 May 2024 09:44:10 +0200 Subject: [PATCH 060/152] Update antora playbook Signed-off-by: Fabrice Bibonne --- spring-batch-docs/antora-playbook.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-batch-docs/antora-playbook.yml b/spring-batch-docs/antora-playbook.yml index bf81856fbb..bbf8fac2a9 100644 --- a/spring-batch-docs/antora-playbook.yml +++ b/spring-batch-docs/antora-playbook.yml @@ -4,9 +4,10 @@ antora: extensions: - '@springio/antora-extensions/partial-build-extension' + - '@antora/atlas-extension' + - require: '@springio/antora-extensions/latest-version-extension' - require: '@springio/antora-extensions/inject-collector-cache-config-extension' - '@antora/collector-extension' - - '@antora/atlas-extension' - require: '@springio/antora-extensions/root-component-extension' root_component_name: 'batch' - '@springio/antora-extensions/static-page-extension' @@ -37,5 +38,5 @@ runtime: format: pretty ui: bundle: - url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.11/ui-bundle.zip + url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.15/ui-bundle.zip snapshot: true \ No newline at end of file From fcf0a5714490236f4cd40f49da543edc8bd4f2ce Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 24 May 2024 10:02:24 +0200 Subject: [PATCH 061/152] Fix http link syntax in repeat.adoc Signed-off-by: Fabrice Bibonne --- spring-batch-docs/modules/ROOT/pages/repeat.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/repeat.adoc b/spring-batch-docs/modules/ROOT/pages/repeat.adoc index 4836d338b2..7836d11043 100644 --- a/spring-batch-docs/modules/ROOT/pages/repeat.adoc +++ b/spring-batch-docs/modules/ROOT/pages/repeat.adoc @@ -207,7 +207,7 @@ Java:: The following example uses Java configuration to repeat a service call to a method called `processMessage` (for more detail on how to configure AOP interceptors, see the -<>): +https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop[Spring User Guide]): + [source, java] ---- @@ -234,7 +234,7 @@ XML:: The following example shows declarative iteration that uses the Spring AOP namespace to repeat a service call to a method called `processMessage` (for more detail on how to configure AOP interceptors, see the -<>): +https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop[Spring User Guide]): + [source, xml] ---- From d5aeca4e82f17f53fc2ce320030d72b106fe1521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20B=C3=BCld?= Date: Tue, 21 May 2024 15:40:23 +0200 Subject: [PATCH 062/152] Fix grammatical error in restart.adoc Issue #4600 Signed-off-by: Fabrice Bibonne --- .../ROOT/pages/step/chunk-oriented-processing/restart.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/restart.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/restart.adoc index 8f4af6f71e..20e80bd72d 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/restart.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/restart.adoc @@ -9,7 +9,7 @@ require some specific configuration. == Setting a Start Limit There are many scenarios where you may want to control the number of times a `Step` can -be started. For example, you might need to configure a particular `Step` might so that it +be started. For example, you might need to configure a particular `Step` so that it runs only once because it invalidates some resource that must be fixed manually before it can be run again. This is configurable on the step level, since different steps may have different requirements. A `Step` that can be executed only once can exist as part of the From c7e88cbcd8843f9510e5cfdbdcc9cdedb2121148 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 27 May 2024 11:26:07 +0200 Subject: [PATCH 063/152] Remove unused parameter in SqlPagingQueryUtils#generateGroupedTopSqlQuery Signed-off-by: Fabrice Bibonne --- .../support/HsqlPagingQueryProvider.java | 5 ++-- .../database/support/SqlPagingQueryUtils.java | 26 +++++++++++++++++++ .../support/SqlServerPagingQueryProvider.java | 5 ++-- .../support/SybasePagingQueryProvider.java | 5 ++-- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HsqlPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HsqlPagingQueryProvider.java index fed56bab8b..49e3741a4f 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HsqlPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/HsqlPagingQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -25,6 +25,7 @@ * * @author Thomas Risberg * @author Michael Minella + * @author Mahmoud Ben Hassine * @since 2.0 */ public class HsqlPagingQueryProvider extends AbstractSqlPagingQueryProvider { @@ -37,7 +38,7 @@ public String generateFirstPageQuery(int pageSize) { @Override public String generateRemainingPagesQuery(int pageSize) { if (StringUtils.hasText(getGroupClause())) { - return SqlPagingQueryUtils.generateGroupedTopSqlQuery(this, true, buildTopClause(pageSize)); + return SqlPagingQueryUtils.generateGroupedTopSqlQuery(this, buildTopClause(pageSize)); } else { return SqlPagingQueryUtils.generateTopSqlQuery(this, true, buildTopClause(pageSize)); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java index 29e1ee2a52..2ae66e4388 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlPagingQueryUtils.java @@ -144,7 +144,10 @@ public static String generateTopSqlQuery(AbstractSqlPagingQueryProvider provider * to the first page (false) * @param topClause the implementation specific top clause to be used * @return the generated query + * @deprecated since v5.2 in favor of + * {@link #generateGroupedTopSqlQuery(AbstractSqlPagingQueryProvider, String)} */ + @Deprecated public static String generateGroupedTopSqlQuery(AbstractSqlPagingQueryProvider provider, boolean remainingPageQuery, String topClause) { StringBuilder sql = new StringBuilder(); @@ -161,6 +164,29 @@ public static String generateGroupedTopSqlQuery(AbstractSqlPagingQueryProvider p return sql.toString(); } + /** + * Generate SQL query string using a TOP clause + * @param provider {@link AbstractSqlPagingQueryProvider} providing the implementation + * specifics + * @param topClause the implementation specific top clause to be used + * @return the generated query + * @since 5.2 + */ + public static String generateGroupedTopSqlQuery(AbstractSqlPagingQueryProvider provider, String topClause) { + StringBuilder sql = new StringBuilder(); + sql.append("SELECT ").append(topClause).append(" * FROM ("); + sql.append("SELECT ").append(provider.getSelectClause()); + sql.append(" FROM ").append(provider.getFromClause()); + sql.append(provider.getWhereClause() == null ? "" : " WHERE " + provider.getWhereClause()); + buildGroupByClause(provider, sql); + sql.append(") AS MAIN_QRY "); + sql.append("WHERE "); + buildSortConditions(provider, sql); + sql.append(" ORDER BY ").append(buildSortClause(provider)); + + return sql.toString(); + } + /** * Generate SQL query string using a ROW_NUM condition * @param provider {@link AbstractSqlPagingQueryProvider} providing the implementation diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java index 59332cf271..51b879ed2a 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2012 the original author or authors. + * Copyright 2006-2024 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. @@ -25,6 +25,7 @@ * * @author Thomas Risberg * @author Michael Minella + * @author Mahmoud Ben Hassine * @since 2.0 */ public class SqlServerPagingQueryProvider extends SqlWindowingPagingQueryProvider { @@ -37,7 +38,7 @@ public String generateFirstPageQuery(int pageSize) { @Override public String generateRemainingPagesQuery(int pageSize) { if (StringUtils.hasText(getGroupClause())) { - return SqlPagingQueryUtils.generateGroupedTopSqlQuery(this, true, buildTopClause(pageSize)); + return SqlPagingQueryUtils.generateGroupedTopSqlQuery(this, buildTopClause(pageSize)); } else { return SqlPagingQueryUtils.generateTopSqlQuery(this, true, buildTopClause(pageSize)); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java index d91e1f44c4..af69169139 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2012 the original author or authors. + * Copyright 2006-2024 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. @@ -25,6 +25,7 @@ * * @author Thomas Risberg * @author Michael Minella + * @author Mahmoud Ben Hassine * @since 2.0 */ public class SybasePagingQueryProvider extends SqlWindowingPagingQueryProvider { @@ -37,7 +38,7 @@ public String generateFirstPageQuery(int pageSize) { @Override public String generateRemainingPagesQuery(int pageSize) { if (StringUtils.hasText(getGroupClause())) { - return SqlPagingQueryUtils.generateGroupedTopSqlQuery(this, true, buildTopClause(pageSize)); + return SqlPagingQueryUtils.generateGroupedTopSqlQuery(this, buildTopClause(pageSize)); } else { return SqlPagingQueryUtils.generateTopSqlQuery(this, true, buildTopClause(pageSize)); From cbb541179cb235da50c0947490667542f3d48a15 Mon Sep 17 00:00:00 2001 From: Seungrae Date: Thu, 23 May 2024 02:53:28 +0900 Subject: [PATCH 064/152] Use Threadlocal.remove() instead of Threadlocal.set(null) Issue #4601 Signed-off-by: Fabrice Bibonne --- .../batch/core/job/flow/JobFlowExecutor.java | 6 +++--- .../springframework/batch/core/step/item/ChunkMonitor.java | 5 +++-- .../batch/repeat/support/RepeatSynchronizationManager.java | 7 ++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java index 9827040573..c1583e25f1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/JobFlowExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -35,6 +35,7 @@ * @author Dave Syer * @author Michael Minella * @author Mahmoud Ben Hassine + * @author Seungrae Kim * */ public class JobFlowExecutor implements FlowExecutor { @@ -58,7 +59,6 @@ public JobFlowExecutor(JobRepository jobRepository, StepHandler stepHandler, Job this.jobRepository = jobRepository; this.stepHandler = stepHandler; this.execution = execution; - stepExecutionHolder.set(null); } @Override @@ -118,7 +118,7 @@ public StepExecution getStepExecution() { @Override public void close(FlowExecution result) { - stepExecutionHolder.set(null); + stepExecutionHolder.remove(); } @Override diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkMonitor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkMonitor.java index ae83b261c3..47ac071075 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkMonitor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkMonitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -32,6 +32,7 @@ * * @author Dave Syer * @author Mahmoud Ben Hassine + * @author Seungrae Kim * @since 2.0 */ public class ChunkMonitor extends ItemStreamSupport { @@ -104,7 +105,7 @@ public void setChunkSize(int chunkSize) { @Override public void close() throws ItemStreamException { super.close(); - holder.set(null); + holder.remove(); if (streamsRegistered) { stream.close(); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatSynchronizationManager.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatSynchronizationManager.java index 8a809cfcae..c057138549 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatSynchronizationManager.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatSynchronizationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 the original author or authors. + * Copyright 2006-2024 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. @@ -30,6 +30,7 @@ * {@link RepeatOperations} implementations. * * @author Dave Syer + * @author Seungrae Kim * */ public final class RepeatSynchronizationManager { @@ -70,7 +71,7 @@ public static void setCompleteOnly() { */ public static RepeatContext register(RepeatContext context) { RepeatContext oldSession = getContext(); - RepeatSynchronizationManager.contextHolder.set(context); + contextHolder.set(context); return oldSession; } @@ -81,7 +82,7 @@ public static RepeatContext register(RepeatContext context) { */ public static RepeatContext clear() { RepeatContext context = getContext(); - RepeatSynchronizationManager.contextHolder.set(null); + contextHolder.remove(); return context; } From f777ada301caabcd3585484c74143c2dbe64c98c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 28 May 2024 11:35:40 +0200 Subject: [PATCH 065/152] Fix warning about nullable parameter in DataFieldMaxValueJobParametersIncrementer Signed-off-by: Fabrice Bibonne --- .../support/DataFieldMaxValueJobParametersIncrementer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementer.java index c01e511c1e..5cce9c53f9 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/DataFieldMaxValueJobParametersIncrementer.java @@ -19,6 +19,7 @@ import org.springframework.batch.core.JobParametersBuilder; import org.springframework.batch.core.JobParametersIncrementer; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -50,7 +51,7 @@ public DataFieldMaxValueJobParametersIncrementer(DataFieldMaxValueIncrementer da } @Override - public JobParameters getNext(JobParameters jobParameters) { + public JobParameters getNext(@Nullable JobParameters jobParameters) { return new JobParametersBuilder(jobParameters == null ? new JobParameters() : jobParameters) .addLong(this.key, this.dataFieldMaxValueIncrementer.nextLongValue()) .toJobParameters(); From a8c34d70652a38fd2a4651b3b7526f6c9e1262a5 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 29 May 2024 11:01:31 +0200 Subject: [PATCH 066/152] Fix javadocs deployment path Signed-off-by: Fabrice Bibonne --- .github/workflows/continuous-integration.yml | 4 ++-- .github/workflows/documentation-upload.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 3100a95115..fb735ac918 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -53,5 +53,5 @@ jobs: working-directory: spring-batch-docs/target run: | unzip spring-batch-$PROJECT_VERSION-javadocs.zip - ssh -i $HOME/.ssh/key $DOCS_USERNAME@$DOCS_HOST "cd $DOCS_PATH && mkdir -p $PROJECT_VERSION/api" - scp -i $HOME/.ssh/key -r api $DOCS_USERNAME@$DOCS_HOST:$DOCS_PATH/$PROJECT_VERSION/api + ssh -i $HOME/.ssh/key $DOCS_USERNAME@$DOCS_HOST "cd $DOCS_PATH && mkdir -p $PROJECT_VERSION" + scp -i $HOME/.ssh/key -r api $DOCS_USERNAME@$DOCS_HOST:$DOCS_PATH/$PROJECT_VERSION diff --git a/.github/workflows/documentation-upload.yml b/.github/workflows/documentation-upload.yml index 2f099ef046..bf3f725cd7 100644 --- a/.github/workflows/documentation-upload.yml +++ b/.github/workflows/documentation-upload.yml @@ -55,8 +55,8 @@ jobs: working-directory: spring-batch-docs/target run: | unzip spring-batch-$RELEASE_VERSION-javadocs.zip - ssh -i $HOME/.ssh/key $DOCS_USERNAME@$DOCS_HOST "cd $DOCS_PATH && mkdir -p $RELEASE_VERSION/api" - scp -i $HOME/.ssh/key -r api $DOCS_USERNAME@$DOCS_HOST:$DOCS_PATH/$RELEASE_VERSION/api + ssh -i $HOME/.ssh/key $DOCS_USERNAME@$DOCS_HOST "cd $DOCS_PATH && mkdir -p $RELEASE_VERSION" + scp -i $HOME/.ssh/key -r api $DOCS_USERNAME@$DOCS_HOST:$DOCS_PATH/$RELEASE_VERSION unzip spring-batch-$RELEASE_VERSION-schemas.zip scp -i $HOME/.ssh/key batch/*.xsd $DOCS_USERNAME@$DOCS_HOST:$BATCH_SCHEMA_PATH From 43592ccbb28db50d0caa9e217caf0efa776833a7 Mon Sep 17 00:00:00 2001 From: Kyoungwoong Date: Sun, 12 May 2024 19:45:53 +0900 Subject: [PATCH 067/152] Implement equals() and hashCode() in StateTransition This commit prevents duplicate state transition entries in flow definitions Resolves #3674 Signed-off-by: Fabrice Bibonne --- .../job/flow/support/StateTransition.java | 21 ++++++++++++++++++- .../flow/support/StateTransitionTests.java | 18 ++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/StateTransition.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/StateTransition.java index ca2f762b1a..6757f9cc69 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/StateTransition.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/flow/support/StateTransition.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -22,6 +22,8 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; +import java.util.Objects; + /** * Value object representing a potential transition from one {@link State} to another. The * originating State name and the next {@link State} to execute are linked by a pattern @@ -31,6 +33,7 @@ * @author Dave Syer * @author Michael Minella * @author Mahmoud Ben Hassine + * @author Kim Youngwoong * @since 2.0 */ public final class StateTransition { @@ -159,6 +162,22 @@ public boolean isEnd() { return next == null; } + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + StateTransition that = (StateTransition) o; + return Objects.equals(state, that.state) && Objects.equals(pattern, that.pattern) + && Objects.equals(next, that.next); + } + + @Override + public int hashCode() { + return Objects.hash(state, pattern, next); + } + @Override public String toString() { return String.format("StateTransition: [state=%s, pattern=%s, next=%s]", state == null ? null : state.getName(), diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/StateTransitionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/StateTransitionTests.java index b194b2a5ca..f5ab5fb5fc 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/StateTransitionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/flow/support/StateTransitionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -15,6 +15,8 @@ */ package org.springframework.batch.core.job.flow.support; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -25,7 +27,7 @@ /** * @author Dave Syer * @author Michael Minella - * + * @author Kim Youngwoong */ class StateTransitionTests { @@ -74,6 +76,18 @@ void testMatchesPlaceholder() { assertTrue(transition.matches("CONTINUABLE")); } + @Test + void testEquals() { + StateTransition transition1 = StateTransition.createStateTransition(state, "pattern1", "next1"); + StateTransition transition2 = StateTransition.createStateTransition(state, "pattern1", "next1"); + StateTransition transition3 = StateTransition.createStateTransition(state, "pattern2", "next2"); + + assertEquals(transition1, transition2); + assertNotEquals(transition1, transition3); + assertEquals(transition1, transition1); + assertNotEquals(null, transition1); + } + @Test void testToString() { StateTransition transition = StateTransition.createStateTransition(state, "CONTIN???LE", "start"); From a8bea0b2ac8ee73e8102daeb4711250e032d7322 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 29 May 2024 15:57:27 +0200 Subject: [PATCH 068/152] Update Spring dependencies to latest snapshots Signed-off-by: Fabrice Bibonne --- pom.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 027103f934..0405458fac 100644 --- a/pom.xml +++ b/pom.xml @@ -63,16 +63,16 @@ 6.2.0-SNAPSHOT 2.0.6-SNAPSHOT - 6.3.0-SNAPSHOT - 1.13.0-SNAPSHOT + 6.4.0-SNAPSHOT + 1.14.0-SNAPSHOT - 3.3.0-SNAPSHOT - 3.3.0-SNAPSHOT - 3.3.0-SNAPSHOT - 4.3.0-SNAPSHOT - 3.2.0-SNAPSHOT - 3.1.5-SNAPSHOT + 3.4.0-SNAPSHOT + 3.4.0-SNAPSHOT + 3.4.0-SNAPSHOT + 4.4.0-SNAPSHOT + 3.3.0-SNAPSHOT + 3.2.0-SNAPSHOT 3.2.4-SNAPSHOT 2.17.0 @@ -92,7 +92,7 @@ 3.0.2 - 1.3.0-SNAPSHOT + 1.4.0-SNAPSHOT 1.4.20 4.13.2 From 8dc16538d718a0744bc8a8fa127e20c1875522ce Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 30 May 2024 16:46:15 +0200 Subject: [PATCH 069/152] Polish Signed-off-by: Fabrice Bibonne --- .../batch/core/job/builder/SimpleJobBuilder.java | 4 ++-- .../batch/core/repository/dao/JdbcExecutionContextDao.java | 4 ++-- .../batch/core/step/builder/AbstractTaskletStepBuilder.java | 4 ++-- .../batch/core/step/builder/FaultTolerantStepBuilder.java | 2 +- .../batch/core/step/builder/SimpleStepBuilder.java | 4 ++-- .../batch/item/data/RepositoryItemReader.java | 2 +- .../batch/item/database/JdbcPagingItemReader.java | 6 +++--- .../database/support/AbstractSqlPagingQueryProvider.java | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java index a8be4c6b31..b714d484ea 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/builder/SimpleJobBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -83,7 +83,7 @@ public SimpleJobBuilder start(Step step) { * @return a builder for fluent chaining */ public FlowBuilder.TransitionBuilder on(String pattern) { - Assert.state(steps.size() > 0, "You have to start a job with a step"); + Assert.state(!steps.isEmpty(), "You have to start a job with a step"); for (Step step : steps) { if (builder == null) { builder = new JobFlowBuilder(new FlowJobBuilder(this), step); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java index 14fd39e02d..e59c459390 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -156,7 +156,7 @@ public ExecutionContext getExecutionContext(JobExecution jobExecution) { List results = getJdbcTemplate().query(getQuery(FIND_JOB_EXECUTION_CONTEXT), new ExecutionContextRowMapper(), executionId); - if (results.size() > 0) { + if (!results.isEmpty()) { return results.get(0); } else { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java index 36ce5918a6..55e6a0fdce 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/AbstractTaskletStepBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2024 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. @@ -180,7 +180,7 @@ public B listener(Object listener) { chunkListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterChunk.class)); chunkListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), AfterChunkError.class)); - if (chunkListenerMethods.size() > 0) { + if (!chunkListenerMethods.isEmpty()) { StepListenerFactoryBean factory = new StepListenerFactoryBean(); factory.setDelegate(listener); this.listener((ChunkListener) factory.getObject()); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java index c5a5e5eb55..635c54550a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java @@ -199,7 +199,7 @@ public FaultTolerantStepBuilder listener(Object listener) { skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInProcess.class)); skipListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnSkipInWrite.class)); - if (skipListenerMethods.size() > 0) { + if (!skipListenerMethods.isEmpty()) { StepListenerFactoryBean factory = new StepListenerFactoryBean(); factory.setDelegate(listener); skipListeners.add((SkipListener) factory.getObject()); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java index 088b17ca5f..fed10c44a1 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/SimpleStepBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -269,7 +269,7 @@ public SimpleStepBuilder listener(Object listener) { itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnProcessError.class)); itemListenerMethods.addAll(ReflectionUtils.findMethod(listener.getClass(), OnWriteError.class)); - if (itemListenerMethods.size() > 0) { + if (!itemListenerMethods.isEmpty()) { StepListenerFactoryBean factory = new StepListenerFactoryBean(); factory.setDelegate(listener); itemListeners.add((StepListener) factory.getObject()); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemReader.java index d44af4edaa..98ce9941f3 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/RepositoryItemReader.java @@ -229,7 +229,7 @@ protected List doPageRead() throws Exception { List parameters = new ArrayList<>(); - if (arguments != null && arguments.size() > 0) { + if (arguments != null && !arguments.isEmpty()) { parameters.addAll(arguments); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcPagingItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcPagingItemReader.java index 0d484f2c45..547c8e3ff9 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcPagingItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/JdbcPagingItemReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -185,7 +185,7 @@ protected void doReadPage() { if (logger.isDebugEnabled()) { logger.debug("SQL used for reading first page: [" + firstPageSql + "]"); } - if (parameterValues != null && parameterValues.size() > 0) { + if (parameterValues != null && !parameterValues.isEmpty()) { if (this.queryProvider.isUsingNamedParameters()) { query = namedParameterJdbcTemplate.query(firstPageSql, getParameterMap(parameterValues, null), rowCallback); @@ -277,7 +277,7 @@ private List getParameterList(Map values, Map parameterList = new ArrayList<>(); parameterList.addAll(sm.values()); - if (sortKeyValue != null && sortKeyValue.size() > 0) { + if (sortKeyValue != null && !sortKeyValue.isEmpty()) { List> keys = new ArrayList<>(sortKeyValue.entrySet()); for (int i = 0; i < keys.size(); i++) { diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProvider.java index af9a17b393..dc78066fc5 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/AbstractSqlPagingQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -194,7 +194,7 @@ public void init(DataSource dataSource) throws Exception { } List namedParameters = new ArrayList<>(); parameterCount = JdbcParameterUtils.countParameterPlaceholders(sql.toString(), namedParameters); - if (namedParameters.size() > 0) { + if (!namedParameters.isEmpty()) { if (parameterCount != namedParameters.size()) { throw new InvalidDataAccessApiUsageException( "You can't use both named parameters and classic \"?\" placeholders: " + sql); From dff9f03e22df0dfebfb84a14daf5cdfd600465a3 Mon Sep 17 00:00:00 2001 From: Benjamin BONNET Date: Thu, 30 May 2024 11:13:15 +0200 Subject: [PATCH 070/152] Fix typo in spring-batch-architecture.adoc Signed-off-by: Fabrice Bibonne --- .../modules/ROOT/pages/spring-batch-architecture.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-batch-docs/modules/ROOT/pages/spring-batch-architecture.adoc b/spring-batch-docs/modules/ROOT/pages/spring-batch-architecture.adoc index 75e5ab926f..ea0d35f7c9 100644 --- a/spring-batch-docs/modules/ROOT/pages/spring-batch-architecture.adoc +++ b/spring-batch-docs/modules/ROOT/pages/spring-batch-architecture.adoc @@ -252,7 +252,7 @@ advisable). The following image illustrates the partitioning approach: image::partitioned.png[Figure 1.2: Partitioned Process, scaledwidth="60%"] The architecture should be flexible enough to allow dynamic configuration of the number -of partitions. You shoul consider both automatic and user controlled configuration. +of partitions. You should consider both automatic and user controlled configuration. Automatic configuration may be based on such parameters as the input file size and the number of input records. From 23ae02862e17e9ea837319ce5398a838908940e4 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 4 Sep 2024 11:34:47 +0200 Subject: [PATCH 071/152] Update dependencies Signed-off-by: Fabrice Bibonne --- pom.xml | 64 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/pom.xml b/pom.xml index 0405458fac..077e80b851 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ 6.2.0-SNAPSHOT - 2.0.6-SNAPSHOT + 2.0.9-SNAPSHOT 6.4.0-SNAPSHOT 1.14.0-SNAPSHOT @@ -73,20 +73,20 @@ 4.4.0-SNAPSHOT 3.3.0-SNAPSHOT 3.2.0-SNAPSHOT - 3.2.4-SNAPSHOT + 3.2.7-SNAPSHOT - 2.17.0 + 2.17.2 1.11.3 - 2.10.1 - 6.5.0.CR2 + 2.11.0 + 6.6.0.Final 2.1.1 2.1.3 3.1.0 3.0.2 3.1.0 - 4.0.10 - 5.0.1 - 5.10.2 + 4.0.11 + 5.1.3 + 5.11.0 3.0.2 @@ -97,38 +97,38 @@ 1.4.20 4.13.2 ${junit-jupiter.version} - 2.2 - 3.25.3 - 5.11.0 - 2.9.1 + 3.0 + 3.26.3 + 5.13.0 + 2.10.0 2.16.1 2.12.0 - 2.0.12 - 2.7.2 - 2.2.224 - 3.45.3.0 + 2.0.16 + 2.7.3 + 2.3.232 + 3.46.1.0 10.16.1.1 - 2.18.13 - 2.33.0 + 2.21.11 + 2.37.0 4.0.5 2.23.1 8.0.1.Final - 5.0.1 + 6.0.1 4.0.2 2.0.1 4.0.2 2.0.3 - 6.6.2 - 1.9.22 - 8.3.0 - 3.3.3 - 42.7.3 + 7.0.0 + 1.9.22.1 + 9.0.0 + 3.4.1 + 42.7.4 11.5.9.0 - 19.22.0.0 + 19.24.0.0 11.2.3.jre17 1.3.1 - 1.19.7 - 1.5.1 + 1.20.1 + 1.5.3 ${spring-amqp.version} @@ -141,15 +141,15 @@ 3.13.0 - 3.2.5 - 3.2.5 - 3.6.3 + 3.5.0 + 3.5.0 + 3.10.0 3.3.1 0.8.12 1.6.0 - 3.1.1 + 3.1.3 3.7.1 - 3.4.1 + 3.4.2 0.0.39 From 376bd4788c740783fef0292f4e05d3123a819416 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 4 Sep 2024 13:17:28 +0200 Subject: [PATCH 072/152] Fix javadocs assembly descriptor Signed-off-by: Fabrice Bibonne --- spring-batch-docs/src/assembly/javadocs.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-batch-docs/src/assembly/javadocs.xml b/spring-batch-docs/src/assembly/javadocs.xml index 9ec174daf0..2cea243ea6 100644 --- a/spring-batch-docs/src/assembly/javadocs.xml +++ b/spring-batch-docs/src/assembly/javadocs.xml @@ -8,7 +8,7 @@ false - ../target/site/apidocs + ../target/reports/apidocs api From 8edd29a72d35e995c9fd470367caa96e2ad655a4 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 17 Sep 2024 18:06:49 +0200 Subject: [PATCH 073/152] Update whatsnew.adoc for version 5.2 Signed-off-by: Fabrice Bibonne --- .../modules/ROOT/pages/whatsnew.adoc | 198 ++++-------------- 1 file changed, 42 insertions(+), 156 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc index b150c6f777..f88f68462f 100644 --- a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc +++ b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc @@ -1,179 +1,65 @@ [[whatsNew]] -= What's New in Spring Batch 5.1 += What's new in Spring Batch 5.2 -This section shows the major highlights of Spring Batch 5.1. For the complete list of changes, please refer to the https://github.com/spring-projects/spring-batch/releases[release notes]. +This section highlights the major changes in Spring Batch 5.2. For the complete list of changes, please refer to the https://github.com/spring-projects/spring-batch/releases[release notes]. -Spring Batch 5.1 includes the following features: +Spring Batch 5.2 includes the following features: * xref:whatsnew.adoc#dependencies-upgrade[Dependencies upgrade] -* xref:whatsnew.adoc#virtual-threads-support[Virtual Threads support] -* xref:whatsnew.adoc#memory-management-improvement-jpaitemwriter[Memory management improvement in the JpaItemWriter] -* xref:whatsnew.adoc#new-synchronized-decorators[New synchronized decorators for item readers and writers] -* xref:whatsnew.adoc#new-cursor-based-mongo-item-reader[New Cursor-based MongoItemReader] -* xref:whatsnew.adoc#bulk-inserts-support-mongo-item-writer[Bulk inserts support in MongoItemWriter] -* xref:whatsnew.adoc#new-item-reader-and-writer-for-redis[New item reader and writer for Redis] -* xref:whatsnew.adoc#automatic-configuration-of-jobregistrybeanpostprocessor[Automatic configuration of JobRegistryBeanPostProcessor] -* xref:whatsnew.adoc#ability-to-start-a-job-flow-with-a-decision[Ability to start a job flow with a decision] -* xref:whatsnew.adoc#ability-to-provide-a-custom-jobkeygenerator[Ability to provide a custom JobKeyGenerator] -* xref:whatsnew.adoc#new-documentation-based-on-antora[New documentation based on Antora] -* xref:whatsnew.adoc#improved-getting-started-experience[Improved Getting Started experience] - -Moreover, this release introduces the following experimental features: - -* xref:whatsnew.adoc#mongodb-job-repository[MongoDB Job Repository] -* xref:whatsnew.adoc#composite-item-reader[Composite Item Reader] -* xref:whatsnew.adoc#new-chunk-oriented-step-implementation[New Chunk-Oriented Step Implementation] +* xref:whatsnew.adoc#query-hints-support[Query hints support in JPA item readers] +* xref:whatsnew.adoc#data-class-support[Data class support in JDBC item readers] +* xref:whatsnew.adoc#configurable-line-separator-in-recursivecollectionlineaggregator[Configurable line separator in RecursiveCollectionLineAggregator] +* xref:whatsnew.adoc#job-registration-improvements[Job registration improvements] [[dependencies-upgrade]] == Dependencies upgrade In this release, the Spring dependencies are upgraded to the following versions: -* Spring Framework 6.1.0 -* Spring Integration 6.2.0 -* Spring Data 3.2.0 -* Spring LDAP 3.2.0 -* Spring AMQP 3.1.0 -* Spring Kafka 3.1.0 -* Micrometer 1.12.0 +* Spring Framework 6.2.0 +* Spring Integration 6.4.0 +* Spring Data 3.4.0 +* Spring Retry 2.0.9 +* Spring LDAP 3.2.7 +* Spring AMQP 3.2.0 +* Spring Kafka 3.3.0 +* Micrometer 1.14.0 -[[virtual-threads-support]] -== Virtual Threads support +[[query-hints-support]] +== Query hints support in JPA item readers -Embracing JDK 21 LTS is one of the main themes for Spring Batch 5.1, especially the support of -virtual threads from Project Loom. In this release, virtual threads can be used in all areas of the -framework, like running a concurrent step with virtual threads or launching multiple steps in parallel -using virtual threads. +Up until version 5.1, the JPA cursor and paging item readers did not support query hints (like the fetch size, timeout, etc). +Users were required to provide a custom query provider in order to specify custom hints. -Thanks to the well designed separation of concerns in Spring Batch, threads are not managed directly. Thread -management is rather delegated to `TaskExecutor` implementations from Spring Framework. This programming-to-interface -approach allows you to switch between `TaskExecutor` implementations in a transparent and a flexible way. +In this release, JPA readers and their respective builders were updated to accept query hints when defining the JPA query to use. -In Spring Framework 6.1, a new `TaskExecutor` implementation based on virtual threads has been introduced, which is the -`VirtualThreadTaskExecutor`. This `TaskExecutor` can be used in Spring Batch wherever a `TaskExecutor` is required. +[[data-class-support]] +== Data class support in JDBC item readers -[[memory-management-improvement-jpaitemwriter]] -== Memory management improvement in the JpaItemWriter +This release introduces a new method in the builders of JDBC cursor and paging item readers that allows users to specify a +`DataClassRowMapper` when the type of items is a data class (Java record or Kotlin data class). -When using the `JpaItemWriter`, the JPA persistence context can quickly grow when the chunk size -is large enough. This might lead to `OutOfMemoryError` errors if not cleared appropriately in a timely manner. +The new method named `dataRowMapper(TargetType.class)` is similar to the `beanRowMapper(TargetType.class)` and is designed +to make the configuration of row mappers consistent between regular classes (Java beans) and data classes (Java records). -In this release, a new option named `clearPersistenceContext` has been introduced in the `JpaItemWriter` -to clear the persistence context after writing each chunk of items. This option improves the memory management -of chunk-oriented steps dealing with large amounts of data and big chunk sizes. +[[configurable-line-separator-in-recursivecollectionlineaggregator]] +== Configurable line separator in RecursiveCollectionLineAggregator -[[new-synchronized-decorators]] -== New synchronized decorators for item readers and writers +Up until now, the line separator property in `RecursiveCollectionLineAggregator` was set to the System's line separator value. +While it is possible to change the value through a System property, this configuration style is not consistent with other properties +of batch artifacts. -Up to version 5.0, Spring Batch provided two decorators `SynchronizedItemStreamReader` and `SynchronizedItemStreamWriter` -to synchronize thread access to `ItemStreamReader#read` and `ItemStreamWriter#write`. Those decorators are useful when -using non thread-safe item streams in multi-threaded steps. +This release introduces a new setter in `RecursiveCollectionLineAggregator` that allows users to configure a custom value of +the line separator without having to use System properties. -While those decorators work with `ItemStream` implementations, they are not usable with non-item streams. For example, -those decorators cannot be used to synchronize access to `ListItemReader#read` or `KafkaItemWriter#write`. +[[job-registration-improvements]] +== Job registration improvements -For users convenience, this release introduces new decorators for non-item streams as well. With this new feature, all -item readers and writers in Spring Batch can now be synchronized without having to write custom decorators. +In version 5.1, the default configuration of batch infrastructure beans was updated to automatically populate the job registry +by defining a `JobRegistryBeanPostProcessor` bean in the application context. After a recent change in Spring Framework +that changed the log level in `BeanPostProcessorChecker`, several warnings related to the `JobRegistryBeanPostProcessor` were +logged in a typical Spring Batch application. These warnings are due to the `JobRegistryBeanPostProcessor` having a dependency +to a `JobRegistry` bean, which is not recommended and might cause bean lifecycle issues. -[[new-cursor-based-mongo-item-reader]] -=== New Cursor-based MongoItemReader - -Up to version 5.0, the `MongoItemReader` provided by Spring Batch used pagination, which is based on MongoDB's `skip` operation. -While this works well for small/medium data sets, it starts to perform poorly with large data sets. - -This release introduces the `MongoCursorItemReader`, a new cursor-based item reader for MongoDB. This implementation -uses cursors instead paging to read data from MongoDB, which improves the performance of reads on large collections. -For consistency with other cursor/paging readers, the current `MongoItemReader` has been renamed to `MongoPagingItemReader`. - -[[bulk-inserts-support-mongo-item-writer]] -=== Bulk inserts support in MongoItemWriter - -Up to version 5.0, the `MongoItemWriter` supported two operations: `upsert` and `delete`. While the `upsert` -operation works well for both inserts and updates, it does not perform well for items that are known to be new -in the target collection. - -Similar to the `persist` and `merge` operations in the `JpaItemWriter`, this release adds a new operation named -`insert` in the `MongoItemWriter`, which is designed for bulk inserts. This new option performs better than -`upsert` for new items as it does not require an additional lookup to check if items already exist in the target collection. - -[[new-item-reader-and-writer-for-redis]] -=== New item reader and writer for Redis - -A new `RedisItemReader` is now available in the library of built-in item readers. This reader is based on Spring Data Redis -and can be configured with a `ScanOptions` to scan the key set to read from Redis. - -Similarly, a new `RedisItemWriter` based on Spring Data Redis is now part of the writers library. This writer can be configured -with a `RedisTemplate` to write items to Redis. - -[[automatic-configuration-of-jobregistrybeanpostprocessor]] -=== Automatic configuration of JobRegistryBeanPostProcessor - -When configuring a `JobOperator` in a Spring Batch application, it is necessary to register the jobs in the operator's `JobRegistry`. -This registration process is either done manually or automatically by adding a `JobRegistryBeanPostProcessor` bean to the application -context. - -In this release, the default configuration of Spring Batch (ie by using `@EnableBatchProcessing` or extending `DefaultBatchConfiguration`) -now automatically registers a `JobRegistryBeanPostProcessor` bean to the application context. This simplifies the configuration process -and improves the user experience when using a `JobOperator`. - -[[ability-to-start-a-job-flow-with-a-decision]] -=== Ability to start a job flow with a decision - -When using the XML configuration style, it is possible to start a job flow with a decider thanks to the `` element. -However, up to version 5.0, it was not possible to achieve the same flow definition with the Java API. - -In this release, a new option to start a job flow with a `JobExecutionDecider` was added to the `JobBuilder` API. -This makes both configuration styles more consistent. - -[[ability-to-provide-a-custom-jobkeygenerator]] -=== Ability to provide a custom JobKeyGenerator - -By default, Spring Batch identifies job instances by calculating an MD5 hash of the identifying job parameters. While it is unlikely to -need to customize this identification process, Spring Batch still provide a strategy interface for users to override the default mechanism -through the `JobKeyGenerator` API. - -Up to version 5.0, it was not possible to provide a custom key generator without having to create a custom `JobRepository` and `JobExplorer`. -In this version, it is now possible to provide a custom `JobKeyGenerator` through the factory beans of `JobRepository` and `JobExplorer`. - -[[new-documentation-based-on-antora]] -=== New documentation based on Antora - -The reference documentation was updated to use https://antora.org[Antora]. This update introduces a number of improvements, including but not limited to: - -* Multi-version documentation: it is now possible to navigate from one version to another thanks to the drop down version list in the left side menu. -* Integrated search experience: powered by https://docsearch.algolia.com/[Algolia], the search experience in now better thanks to the integrated search box at the top left of the page -* Improved configuration style toggle: the toggle to switch between the XML and Java configuration styles for code snippets is now located near each sample, rather than the top of each page - -[[improved-getting-started-experience]] -=== Improved Getting Started experience - -In this release, the getting started experience was improved in many ways: - -* Samples are now packaged by feature and are provided in two configuration styles: XML and Java configuration -* A new https://github.com/spring-projects/spring-batch#two-minutes-tutorial[Two minutes tutorial] was added to the README -* The https://spring.io/guides/gs/batch-processing[Getting Started Guide] was updated to the latest and greatest Spring Batch and Spring Boot versions -* The https://github.com/spring-projects/spring-batch/blob/main/ISSUE_REPORTING.md[Issue Reporting Guide] was updated with detailed instructions and project templates to help you easily report issues - -[[mongodb-job-repository]] -=== MongoDB Job Repository (Experimental) - -This feature introduces new implementations of `JobRepository` and `JobExplorer` backed by MongoDB. This long-awaited feature is now available -as experimental and marks the introduction of the first NoSQL meta-data store for Spring Batch. - -Please refer to the https://github.com/spring-projects-experimental/spring-batch-experimental#mongodb-job-repository[Spring Batch Experimental] repository for more details about this feature. - -[[composite-item-reader]] -=== Composite Item Reader (Experimental) - -This feature introduces a composite `ItemReader` implementation. Similar to the `CompositeItemProcessor` and `CompositeItemWriter`, the idea is to delegate reading to a list of item readers in order. -This is useful when there is a requirement to read data having the same format from different sources (files, databases, etc). - -Please refer to the https://github.com/spring-projects-experimental/spring-batch-experimental#composite-item-reader[Spring Batch Experimental] repository for more details about this feature. - -[[new-chunk-oriented-step-implementation]] -=== New Chunk-Oriented Step implementation (Experimental) - -This is not a new feature, but rather a new implementation of the chunk-oriented processing model. -The goal is to address the reported issues with the current implementation and to provide a new base for the upcoming re-designed concurrency model. - -Please refer to the https://github.com/spring-projects-experimental/spring-batch-experimental#new-chunk-oriented-step-implementation[Spring Batch Experimental] repository for more details about this new implementation. \ No newline at end of file +These issues have been resolved in this release by changing the mechanism of populating the `JobRegistry` from using a `BeanPostProcessor` +to using a `SmartInitializingSingleton`. The `JobRegistryBeanPostProcessor` is now deprecated in favor of the newly added `JobRegistrySmartInitializingSingleton`. From 00c4c018b74eb44c0de35eeceb9603791a6b3183 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 17 Sep 2024 21:07:20 +0200 Subject: [PATCH 074/152] Update index.adoc for version 5.2 Signed-off-by: Fabrice Bibonne --- spring-batch-docs/modules/ROOT/pages/index.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-batch-docs/modules/ROOT/pages/index.adoc b/spring-batch-docs/modules/ROOT/pages/index.adoc index d03485e1d8..fb3d878723 100644 --- a/spring-batch-docs/modules/ROOT/pages/index.adoc +++ b/spring-batch-docs/modules/ROOT/pages/index.adoc @@ -9,7 +9,7 @@ xref:spring-batch-intro.adoc[Spring Batch Introduction] :: Background, usage scenarios, and general guidelines. xref:spring-batch-architecture.adoc[Spring Batch Architecture] :: Spring Batch architecture, general batch principles, batch processing strategies. -xref:whatsnew.adoc[What's new in Spring Batch 5.1] :: New features introduced in version 5.1. +xref:whatsnew.adoc[What's new in Spring Batch 5.2] :: New features introduced in version 5.2. xref:domain.adoc[The Domain Language of Batch] :: Core concepts and abstractions of the Batch domain language. xref:job.adoc[Configuring and Running a Job] :: Job configuration, execution, and From 7573eeea81fb1e05aacef9a03f852f5300f2ac7a Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Sep 2024 09:19:56 +0200 Subject: [PATCH 075/152] Prepare release 5.2.0-M1 Signed-off-by: Fabrice Bibonne --- pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 077e80b851..8615a31a8f 100644 --- a/pom.xml +++ b/pom.xml @@ -61,19 +61,19 @@ 17 - 6.2.0-SNAPSHOT - 2.0.9-SNAPSHOT - 6.4.0-SNAPSHOT - 1.14.0-SNAPSHOT + 6.2.0-RC1 + 2.0.9 + 6.4.0-M3 + 1.14.0-M3 - 3.4.0-SNAPSHOT - 3.4.0-SNAPSHOT - 3.4.0-SNAPSHOT - 4.4.0-SNAPSHOT - 3.3.0-SNAPSHOT - 3.2.0-SNAPSHOT - 3.2.7-SNAPSHOT + 3.4.0-M1 + 3.4.0-M1 + 3.4.0-M1 + 4.4.0-M1 + 3.3.0-M3 + 3.2.0-M3 + 3.2.6 2.17.2 1.11.3 @@ -92,7 +92,7 @@ 3.0.2 - 1.4.0-SNAPSHOT + 1.4.0-M3 1.4.20 4.13.2 From 37bfe406fb25aa43357b3d4f13764d860eb8da97 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Sep 2024 10:30:19 +0200 Subject: [PATCH 076/152] Release version 5.2.0-M1 Signed-off-by: Fabrice Bibonne --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 8615a31a8f..610fb5e5bc 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.0-SNAPSHOT + 5.2.0-M1 pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index 7ad96c8a70..31fdeb3e4a 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M1 spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index af28f85e8d..8165297d67 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M1 spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index c0e835ea10..ebfc00c2ab 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M1 spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index ba86d44878..04a0f0b7d7 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M1 spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index d02bb86b6e..03e46bbc8f 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M1 spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 49c229c025..2e7d4c3928 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M1 spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 15bbf21f7b..9ed8675102 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M1 spring-batch-test Spring Batch Test From ddbd1f4381507bfc1eb60ebb0b9a593f0738962a Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Sep 2024 10:30:37 +0200 Subject: [PATCH 077/152] Next development version Signed-off-by: Fabrice Bibonne --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 610fb5e5bc..8615a31a8f 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.0-M1 + 5.2.0-SNAPSHOT pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index 31fdeb3e4a..7ad96c8a70 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M1 + 5.2.0-SNAPSHOT spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 8165297d67..af28f85e8d 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M1 + 5.2.0-SNAPSHOT spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index ebfc00c2ab..c0e835ea10 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M1 + 5.2.0-SNAPSHOT spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index 04a0f0b7d7..ba86d44878 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M1 + 5.2.0-SNAPSHOT spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index 03e46bbc8f..d02bb86b6e 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M1 + 5.2.0-SNAPSHOT spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 2e7d4c3928..49c229c025 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M1 + 5.2.0-SNAPSHOT spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 9ed8675102..15bbf21f7b 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M1 + 5.2.0-SNAPSHOT spring-batch-test Spring Batch Test From 19157229463e4e77da58ef4b8895964dd1814fac Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Sep 2024 10:48:51 +0200 Subject: [PATCH 078/152] Update Spring dependencies to latest snapshots Signed-off-by: Fabrice Bibonne --- pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 8615a31a8f..2ca9c5905e 100644 --- a/pom.xml +++ b/pom.xml @@ -61,19 +61,19 @@ 17 - 6.2.0-RC1 - 2.0.9 - 6.4.0-M3 - 1.14.0-M3 + 6.2.0-SNAPSHOT + 2.0.10-SNAPSHOT + 6.4.0-SNAPSHOT + 1.14.0-SNAPSHOT - 3.4.0-M1 - 3.4.0-M1 - 3.4.0-M1 - 4.4.0-M1 - 3.3.0-M3 - 3.2.0-M3 - 3.2.6 + 3.4.0-SNAPSHOT + 3.4.0-SNAPSHOT + 3.4.0-SNAPSHOT + 4.4.0-SNAPSHOT + 3.3.0-SNAPSHOT + 3.2.0-SNAPSHOT + 3.2.7-SNAPSHOT 2.17.2 1.11.3 @@ -92,7 +92,7 @@ 3.0.2 - 1.4.0-M3 + 1.4.0-SNAPSHOT 1.4.20 4.13.2 From fba41ed0315519d6779b9ab5d23af61f0491e651 Mon Sep 17 00:00:00 2001 From: Yejin Choi <63734765+chldppwls12@users.noreply.github.com> Date: Mon, 9 Sep 2024 16:10:05 +0900 Subject: [PATCH 079/152] Remove Duplicate If Condition in SimpleJobTests Signed-off-by: Fabrice Bibonne --- .../org/springframework/batch/core/job/SimpleJobTests.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java index 742975fd15..c0d54fc017 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java @@ -607,12 +607,7 @@ public void execute(StepExecution stepExecution) stepExecution.addFailureException(exception); return; } - if (exception instanceof JobInterruptedException) { - stepExecution.setExitStatus(ExitStatus.FAILED); - stepExecution.setStatus(BatchStatus.FAILED); - stepExecution.addFailureException(exception); - return; - } + if (runnable != null) { runnable.run(); } From 9b522ae38e0f4a1bbcbff7fac075dc3260d679ea Mon Sep 17 00:00:00 2001 From: Jimmy Praet Date: Fri, 6 Sep 2024 23:25:08 +0200 Subject: [PATCH 080/152] Move ORDER BY in getLastStepExecution from DB to java This addresses performance issues with large STEP_EXECUTION table on DB2. Fixes #4657 Signed-off-by: Fabrice Bibonne --- .../batch/core/repository/dao/JdbcStepExecutionDao.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java index 795d8b1b4b..594e2e7eef 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.concurrent.locks.Lock; @@ -97,7 +98,6 @@ public class JdbcStepExecutionDao extends AbstractJdbcBatchMetadataDao implement FROM %PREFIX%JOB_EXECUTION JE JOIN %PREFIX%STEP_EXECUTION SE ON SE.JOB_EXECUTION_ID = JE.JOB_EXECUTION_ID WHERE JE.JOB_INSTANCE_ID = ? AND SE.STEP_NAME = ? - ORDER BY SE.CREATE_TIME DESC, SE.STEP_EXECUTION_ID DESC """; private static final String CURRENT_VERSION_STEP_EXECUTION = """ @@ -117,6 +117,10 @@ SELECT COUNT(*) WHERE STEP_EXECUTION_ID = ? """; + private static final Comparator BY_CREATE_TIME_DESC_ID_DESC = Comparator + .comparing(StepExecution::getCreateTime, Comparator.reverseOrder()) + .thenComparing(StepExecution::getId, Comparator.reverseOrder()); + private int exitMessageLength = DEFAULT_EXIT_MESSAGE_LENGTH; private DataFieldMaxValueIncrementer stepExecutionIncrementer; @@ -348,6 +352,7 @@ public StepExecution getLastStepExecution(JobInstance jobInstance, String stepNa jobExecution.setVersion(rs.getInt(27)); return new StepExecutionRowMapper(jobExecution).mapRow(rs, rowNum); }, jobInstance.getInstanceId(), stepName); + executions.sort(BY_CREATE_TIME_DESC_ID_DESC); if (executions.isEmpty()) { return null; } From 6a7e7c5b3c24739a0dc064aa48b22eaa3608118f Mon Sep 17 00:00:00 2001 From: jojoldu Date: Sat, 24 Aug 2024 23:55:58 +0900 Subject: [PATCH 081/152] change assertTrue (instanceof) -> assertInstanceOf Signed-off-by: Fabrice Bibonne --- .../step/item/FaultTolerantStepFactoryBeanRetryTests.java | 6 ++++-- .../step/item/RepeatOperationsStepFactoryBeanTests.java | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java index 84cb8a0449..1c755304fe 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -61,10 +61,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; /** * @author Dave Syer * @author Mahmoud Ben Hassine + * @author jojoldu * */ class FaultTolerantStepFactoryBeanRetryTests { @@ -134,7 +136,7 @@ void testType() { @SuppressWarnings("cast") @Test void testDefaultValue() throws Exception { - assertTrue(factory.getObject() instanceof Step); + assertInstanceOf(Step.class, factory.getObject()); } @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java index 88c2982aa3..7f5965dd4e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -35,10 +35,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + /** * @author Dave Syer * @author Mahmoud Ben Hassine + * @author jojoldu * */ class RepeatOperationsStepFactoryBeanTests { @@ -66,7 +69,7 @@ void testType() { @Test @SuppressWarnings("cast") void testDefaultValue() throws Exception { - assertTrue(factory.getObject() instanceof Step); + assertInstanceOf(Step.class, factory.getObject()); } @Test From ad7f537f903abfb66caf1c9d7dc1647a6c54c5b1 Mon Sep 17 00:00:00 2001 From: Sheel Prabhakar Date: Sat, 29 Jun 2024 07:50:41 +0530 Subject: [PATCH 082/152] Add DatabaseType POSTGRES for the product name EnterpriseDB Resolves #4627 Signed-off-by: Fabrice Bibonne --- .../org/springframework/batch/support/DatabaseType.java | 5 ++++- .../springframework/batch/support/DatabaseTypeTests.java | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/DatabaseType.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/DatabaseType.java index c4086b4539..d727cdc3cc 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/DatabaseType.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/DatabaseType.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -103,6 +103,9 @@ else if (databaseProductName.contains("AS") && (databaseProductVersion.startsWit databaseProductName = JdbcUtils.commonDatabaseName(databaseProductName); } } + else if (StringUtils.hasText(databaseProductName) && databaseProductName.startsWith("EnterpriseDB")) { + databaseProductName = "PostgreSQL"; + } else { databaseProductName = JdbcUtils.commonDatabaseName(databaseProductName); } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/DatabaseTypeTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/DatabaseTypeTests.java index 9c786e8310..2374153b3b 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/DatabaseTypeTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/DatabaseTypeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2022 the original author or authors. + * Copyright 2008-2024 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. @@ -138,6 +138,12 @@ void testFromMetaDataForPostgres() throws Exception { assertEquals(POSTGRES, DatabaseType.fromMetaData(ds)); } + @Test + void testFromMetaDataForEnterpriseDB() throws Exception { + DataSource ds = DatabaseTypeTestUtils.getMockDataSource("EnterpriseDB"); + assertEquals(POSTGRES, DatabaseType.fromMetaData(ds)); + } + @Test void testFromMetaDataForSybase() throws Exception { DataSource ds = DatabaseTypeTestUtils.getMockDataSource("Adaptive Server Enterprise"); From e3b71d66491e9373170ecac6460e033cab7b3fa9 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 19 Sep 2024 12:01:43 +0200 Subject: [PATCH 083/152] Fix code formatting Signed-off-by: Fabrice Bibonne --- .../core/step/item/FaultTolerantStepFactoryBeanRetryTests.java | 2 +- .../core/step/item/RepeatOperationsStepFactoryBeanTests.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java index 1c755304fe..9f5ec22ea7 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java @@ -136,7 +136,7 @@ void testType() { @SuppressWarnings("cast") @Test void testDefaultValue() throws Exception { - assertInstanceOf(Step.class, factory.getObject()); + assertInstanceOf(Step.class, factory.getObject()); } @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java index 7f5965dd4e..f476e4e72c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/RepeatOperationsStepFactoryBeanTests.java @@ -37,7 +37,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertInstanceOf; - /** * @author Dave Syer * @author Mahmoud Ben Hassine @@ -69,7 +68,7 @@ void testType() { @Test @SuppressWarnings("cast") void testDefaultValue() throws Exception { - assertInstanceOf(Step.class, factory.getObject()); + assertInstanceOf(Step.class, factory.getObject()); } @Test From 632119f3567f28e8a036f96a2e909955cf6650b0 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 27 Sep 2024 10:08:38 +0200 Subject: [PATCH 084/152] Update docker image version in SQLServerJobRepositoryIntegrationTests Signed-off-by: Fabrice Bibonne --- .../repository/SQLServerJobRepositoryIntegrationTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SQLServerJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SQLServerJobRepositoryIntegrationTests.java index 304c7abc66..b8373688f1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SQLServerJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/repository/SQLServerJobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 the original author or authors. + * Copyright 2020-2024 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. @@ -58,7 +58,7 @@ class SQLServerJobRepositoryIntegrationTests { // TODO find the best way to externalize and manage image versions private static final DockerImageName SQLSERVER_IMAGE = DockerImageName - .parse("mcr.microsoft.com/mssql/server:2019-CU11-ubuntu-20.04"); + .parse("mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04"); @Container public static MSSQLServerContainer sqlserver = new MSSQLServerContainer<>(SQLSERVER_IMAGE).acceptLicense(); From 548b65a35c7b0707037da1dadd136be72b880007 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 27 Sep 2024 10:53:52 +0200 Subject: [PATCH 085/152] Remove continuous inspection profile and workflow Signed-off-by: Fabrice Bibonne --- .github/workflows/continuous-inspection.yml | 32 --------------------- pom.xml | 27 ----------------- 2 files changed, 59 deletions(-) delete mode 100644 .github/workflows/continuous-inspection.yml diff --git a/.github/workflows/continuous-inspection.yml b/.github/workflows/continuous-inspection.yml deleted file mode 100644 index f496eba5c9..0000000000 --- a/.github/workflows/continuous-inspection.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Continuous inspection build - -on: - schedule: - - cron: '0 10 * * *' # Once per day at 10am UTC - workflow_dispatch: - -jobs: - code-quality-analysis: - name: code quality analysis report - runs-on: ubuntu-latest - steps: - - name: Checkout source code - uses: actions/checkout@v3 - - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - cache: 'maven' - - - name: Analyse test coverage with Jacoco - run: mvn -P test-coverage verify - - - name: Analyse code quality with Sonar - if: github.repository == 'spring-projects/spring-batch' - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ secrets.SONAR_URL }} - run: mvn sonar:sonar -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_TOKEN - diff --git a/pom.xml b/pom.xml index 2ca9c5905e..688defb10a 100644 --- a/pom.xml +++ b/pom.xml @@ -145,7 +145,6 @@ 3.5.0 3.10.0 3.3.1 - 0.8.12 1.6.0 3.1.3 3.7.1 @@ -325,32 +324,6 @@ - - test-coverage - - - - org.jacoco - jacoco-maven-plugin - ${jacoco-maven-plugin.version} - - - prepare-agent - - prepare-agent - - - - report - - report - - - - - - - artifactory-staging From 0c49e2c50ec8f85675a5d18075a308eb9d874c5a Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 30 Sep 2024 13:20:17 +0200 Subject: [PATCH 086/152] Add workflow_dispatch trigger to the CI build This is needed to manually trigger the workflow without having to push a commit to the main branch. The typical use case is when an upstream dependency causes a build failure in the latest snapshot and we want to test the fix by triggering a new build on github. Signed-off-by: Fabrice Bibonne --- .github/workflows/continuous-integration.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index fb735ac918..37dc5a6925 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -1,6 +1,7 @@ name: CI/CD build on: + workflow_dispatch: push: branches: [ "main" ] From 836b70e9eabfef9deb0669993a2adbb33870c13c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 30 Sep 2024 14:20:56 +0200 Subject: [PATCH 087/152] Update dependencies Signed-off-by: Fabrice Bibonne --- pom.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 688defb10a..6c45bb0b31 100644 --- a/pom.xml +++ b/pom.xml @@ -75,18 +75,18 @@ 3.2.0-SNAPSHOT 3.2.7-SNAPSHOT - 2.17.2 - 1.11.3 + 2.18.0 + 1.12.0 2.11.0 6.6.0.Final - 2.1.1 + 3.0.0 2.1.3 3.1.0 - 3.0.2 + 3.1.0 3.1.0 4.0.11 - 5.1.3 - 5.11.0 + 5.2.0 + 5.11.1 3.0.2 @@ -99,15 +99,15 @@ ${junit-jupiter.version} 3.0 3.26.3 - 5.13.0 + 5.14.1 2.10.0 - 2.16.1 + 2.17.0 2.12.0 2.0.16 2.7.3 2.3.232 - 3.46.1.0 - 10.16.1.1 + 3.46.1.3 + 10.16.1.1 2.21.11 2.37.0 4.0.5 @@ -134,7 +134,7 @@ ${spring-amqp.version} 2.3.2 0.16.0 - 3.0.19 + 3.0.22 0.0.4 From 7c5d4bb9c5431561d5a3448824b4182400c675f0 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 30 Sep 2024 22:20:32 +0200 Subject: [PATCH 088/152] Temporarily disable test that is randomly failing/hanging Signed-off-by: Fabrice Bibonne --- .../step/FaultTolerantStepFactoryBeanIntegrationTests.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java index 99dbc190a5..67d24fcefa 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2023 the original author or authors. + * Copyright 2010-2024 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. @@ -25,6 +25,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.batch.core.BatchStatus; @@ -55,6 +56,7 @@ * Tests for {@link FaultTolerantStepFactoryBean}. */ @SpringJUnitConfig(locations = "/simple-job-launcher-context.xml") +@Disabled("Randomly failing/hanging") // FIXME This test is randomly failing/hanging class FaultTolerantStepFactoryBeanIntegrationTests { private static final int MAX_COUNT = 1000; From f9882f3c56011bdac9b12636bed5afbceeae6d24 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 1 Oct 2024 16:34:56 +0200 Subject: [PATCH 089/152] Add adapters for Java functional interfaces Resolves #4672 Signed-off-by: Fabrice Bibonne --- .../item/function/ConsumerItemWriter.java | 49 ++++++++++++++++ .../PredicateFilteringItemProcessor.java | 49 ++++++++++++++++ .../item/function/SupplierItemReader.java | 48 ++++++++++++++++ .../function/ConsumerItemWriterTests.java | 57 +++++++++++++++++++ .../PredicateFilteringItemProcessorTests.java | 49 ++++++++++++++++ .../function/SupplierItemReaderTests.java | 56 ++++++++++++++++++ 6 files changed, 308 insertions(+) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function/ConsumerItemWriter.java create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function/PredicateFilteringItemProcessor.java create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function/SupplierItemReader.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/ConsumerItemWriterTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/PredicateFilteringItemProcessorTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/SupplierItemReaderTests.java diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function/ConsumerItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function/ConsumerItemWriter.java new file mode 100644 index 0000000000..5095659bbb --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function/ConsumerItemWriter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.function; + +import java.util.function.Consumer; + +import org.springframework.batch.item.Chunk; +import org.springframework.batch.item.ItemWriter; +import org.springframework.util.Assert; + +/** + * Adapter for a {@link Consumer} to an {@link ItemWriter}. + * + * @param type of items to write + * @author Mahmoud Ben Hassine + * @since 5.2 + */ +public class ConsumerItemWriter implements ItemWriter { + + private final Consumer consumer; + + /** + * Create a new {@link ConsumerItemWriter}. + * @param consumer the consumer to use to write items. Must not be {@code null}. + */ + public ConsumerItemWriter(Consumer consumer) { + Assert.notNull(consumer, "A consumer is required"); + this.consumer = consumer; + } + + @Override + public void write(Chunk items) throws Exception { + items.forEach(this.consumer); + } + +} \ No newline at end of file diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function/PredicateFilteringItemProcessor.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function/PredicateFilteringItemProcessor.java new file mode 100644 index 0000000000..553c85a797 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function/PredicateFilteringItemProcessor.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.function; + +import java.util.function.Predicate; + +import org.springframework.batch.item.ItemProcessor; +import org.springframework.util.Assert; + +/** + * A filtering {@link ItemProcessor} that is based on a {@link Predicate}. Items for which + * the predicate returns {@code true} will be filtered. + * + * @param type of item to process + * @author Mahmoud Ben Hassine + * @since 5.2 + */ +public class PredicateFilteringItemProcessor implements ItemProcessor { + + private final Predicate predicate; + + /** + * Create a new {@link PredicateFilteringItemProcessor}. + * @param predicate the predicate to use to filter items. Must not be {@code null}. + */ + public PredicateFilteringItemProcessor(Predicate predicate) { + Assert.notNull(predicate, "A predicate is required"); + this.predicate = predicate; + } + + @Override + public T process(T item) throws Exception { + return this.predicate.test(item) ? null : item; + } + +} \ No newline at end of file diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function/SupplierItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function/SupplierItemReader.java new file mode 100644 index 0000000000..48dd87e89c --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function/SupplierItemReader.java @@ -0,0 +1,48 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.function; + +import java.util.function.Supplier; + +import org.springframework.batch.item.ItemReader; +import org.springframework.util.Assert; + +/** + * Adapter for a {@link Supplier} to an {@link ItemReader}. + * + * @param type of items to read + * @author Mahmoud Ben Hassine + * @since 5.2 + */ +public class SupplierItemReader implements ItemReader { + + private final Supplier supplier; + + /** + * Create a new {@link SupplierItemReader}. + * @param supplier the supplier to use to read items. Must not be {@code null}. + */ + public SupplierItemReader(Supplier supplier) { + Assert.notNull(supplier, "A supplier is required"); + this.supplier = supplier; + } + + @Override + public T read() throws Exception { + return this.supplier.get(); + } + +} \ No newline at end of file diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/ConsumerItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/ConsumerItemWriterTests.java new file mode 100644 index 0000000000..4fef1da57e --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/ConsumerItemWriterTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.function; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.springframework.batch.item.Chunk; + +/** + * Test class for {@link ConsumerItemWriter}. + * + * @author Mahmoud Ben Hassine + */ +class ConsumerItemWriterTests { + + private final List items = new ArrayList<>(); + + private final Consumer consumer = items::add; + + @Test + void testMandatoryConsumer() { + Assertions.assertThrows(IllegalArgumentException.class, () -> new ConsumerItemWriter(null), + "A consumer is required"); + } + + @Test + void testWrite() throws Exception { + // given + Chunk chunk = Chunk.of("foo", "bar"); + ConsumerItemWriter consumerItemWriter = new ConsumerItemWriter<>(this.consumer); + + // when + consumerItemWriter.write(chunk); + + // then + Assertions.assertIterableEquals(chunk, this.items); + } + +} \ No newline at end of file diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/PredicateFilteringItemProcessorTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/PredicateFilteringItemProcessorTests.java new file mode 100644 index 0000000000..aa6b79d456 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/PredicateFilteringItemProcessorTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.function; + +import java.util.function.Predicate; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Test class for {@link PredicateFilteringItemProcessor}. + * + * @author Mahmoud Ben Hassine + */ +class PredicateFilteringItemProcessorTests { + + private final Predicate foos = item -> item.startsWith("foo"); + + @Test + void testMandatoryPredicate() { + Assertions.assertThrows(IllegalArgumentException.class, () -> new PredicateFilteringItemProcessor(null), + "A predicate is required"); + } + + @Test + void testProcess() throws Exception { + // given + PredicateFilteringItemProcessor processor = new PredicateFilteringItemProcessor<>(this.foos); + + // when & then + Assertions.assertNull(processor.process("foo1")); + Assertions.assertNull(processor.process("foo2")); + Assertions.assertEquals("bar", processor.process("bar")); + } + +} \ No newline at end of file diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/SupplierItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/SupplierItemReaderTests.java new file mode 100644 index 0000000000..f7587661d1 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/function/SupplierItemReaderTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.function; + +import java.util.function.Supplier; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Test class for {@link SupplierItemReader}. + * + * @author Mahmoud Ben Hassine + */ +class SupplierItemReaderTests { + + private final Supplier supplier = new Supplier<>() { + private int count = 1; + + @Override + public String get() { + return count <= 2 ? "foo" + count++ : null; + } + }; + + @Test + void testMandatorySupplier() { + Assertions.assertThrows(IllegalArgumentException.class, () -> new SupplierItemReader(null), + "A supplier is required"); + } + + @Test + void testRead() throws Exception { + // given + SupplierItemReader supplierItemReader = new SupplierItemReader<>(supplier); + + // when & then + Assertions.assertEquals("foo1", supplierItemReader.read()); + Assertions.assertEquals("foo2", supplierItemReader.read()); + Assertions.assertNull(supplierItemReader.read()); + } + +} \ No newline at end of file From 403aeb6820c16de4b55d274448a81312f2fc65b5 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 3 Oct 2024 09:36:27 +0200 Subject: [PATCH 090/152] Add composite item reader implementation Resolves #757 Signed-off-by: Fabrice Bibonne --- .../item/support/CompositeItemReader.java | 87 +++++++++++ .../support/CompositeItemReaderTests.java | 110 +++++++++++++ spring-batch-samples/README.md | 11 ++ .../batch/samples/compositereader/README.md | 18 +++ .../samples/compositereader/data/persons1.csv | 2 + .../samples/compositereader/data/persons2.csv | 2 + .../samples/compositereader/sql/data.sql | 2 + .../samples/compositereader/sql/schema.sql | 2 + ...positeItemWriterSampleFunctionalTests.java | 147 ++++++++++++++++++ 9 files changed, 381 insertions(+) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemReaderTests.java create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/samples/compositereader/README.md create mode 100644 spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/data/persons1.csv create mode 100644 spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/data/persons2.csv create mode 100644 spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/sql/data.sql create mode 100644 spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/sql/schema.sql create mode 100644 spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemWriterSampleFunctionalTests.java diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java new file mode 100644 index 0000000000..e9b5a72d07 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java @@ -0,0 +1,87 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.support; + +import java.util.Iterator; +import java.util.List; + +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemStreamException; +import org.springframework.batch.item.ItemStreamReader; + +/** + * Composite reader that delegates reading to a list of {@link ItemStreamReader}s. This + * implementation is not thread-safe. + * + * @author Mahmoud Ben Hassine + * @param type of objects to read + * @since 5.2 + */ +public class CompositeItemReader implements ItemStreamReader { + + private final List> delegates; + + private final Iterator> delegatesIterator; + + private ItemStreamReader currentDelegate; + + /** + * Create a new {@link CompositeItemReader}. + * @param delegates the delegate readers to read data + */ + public CompositeItemReader(List> delegates) { + this.delegates = delegates; + this.delegatesIterator = this.delegates.iterator(); + this.currentDelegate = this.delegatesIterator.hasNext() ? this.delegatesIterator.next() : null; + } + + // TODO: check if we need to open/close delegates on the fly in read() to avoid + // opening resources early for a long time + @Override + public void open(ExecutionContext executionContext) throws ItemStreamException { + for (ItemStreamReader delegate : delegates) { + delegate.open(executionContext); + } + } + + @Override + public T read() throws Exception { + if (this.currentDelegate == null) { + return null; + } + T item = currentDelegate.read(); + if (item == null) { + currentDelegate = this.delegatesIterator.hasNext() ? this.delegatesIterator.next() : null; + return read(); + } + return item; + } + + @Override + public void update(ExecutionContext executionContext) throws ItemStreamException { + if (this.currentDelegate != null) { + this.currentDelegate.update(executionContext); + } + } + + @Override + public void close() throws ItemStreamException { + for (ItemStreamReader delegate : delegates) { + delegate.close(); + } + } + +} \ No newline at end of file diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemReaderTests.java new file mode 100644 index 0000000000..3775c4299c --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/CompositeItemReaderTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.support; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemStreamReader; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +/** + * Test class for {@link CompositeItemReader}. + * + * @author Mahmoud Ben Hassine + */ +public class CompositeItemReaderTests { + + @Test + void testCompositeItemReaderOpen() { + // given + ItemStreamReader reader1 = mock(); + ItemStreamReader reader2 = mock(); + CompositeItemReader compositeItemReader = new CompositeItemReader<>(Arrays.asList(reader1, reader2)); + ExecutionContext executionContext = new ExecutionContext(); + + // when + compositeItemReader.open(executionContext); + + // then + verify(reader1).open(executionContext); + verify(reader2).open(executionContext); + } + + @Test + void testCompositeItemReaderRead() throws Exception { + // given + ItemStreamReader reader1 = mock(); + ItemStreamReader reader2 = mock(); + CompositeItemReader compositeItemReader = new CompositeItemReader<>(Arrays.asList(reader1, reader2)); + when(reader1.read()).thenReturn("foo1", "foo2", null); + when(reader2.read()).thenReturn("bar1", "bar2", null); + + // when & then + compositeItemReader.read(); + verify(reader1, times(1)).read(); + compositeItemReader.read(); + verify(reader1, times(2)).read(); + compositeItemReader.read(); + verify(reader1, times(3)).read(); + + compositeItemReader.read(); + verify(reader2, times(2)).read(); + compositeItemReader.read(); + verify(reader2, times(3)).read(); + compositeItemReader.read(); + verify(reader2, times(3)).read(); + } + + @Test + void testCompositeItemReaderUpdate() { + // given + ItemStreamReader reader1 = mock(); + ItemStreamReader reader2 = mock(); + CompositeItemReader compositeItemReader = new CompositeItemReader<>(Arrays.asList(reader1, reader2)); + ExecutionContext executionContext = new ExecutionContext(); + + // when + compositeItemReader.update(executionContext); + + // then + verify(reader1).update(executionContext); + verifyNoInteractions(reader2); // reader1 is the current delegate in this setup + } + + @Test + void testCompositeItemReaderClose() { + // given + ItemStreamReader reader1 = mock(); + ItemStreamReader reader2 = mock(); + CompositeItemReader compositeItemReader = new CompositeItemReader<>(Arrays.asList(reader1, reader2)); + + // when + compositeItemReader.close(); + + // then + verify(reader1).close(); + verify(reader2).close(); + } + +} \ No newline at end of file diff --git a/spring-batch-samples/README.md b/spring-batch-samples/README.md index 046ca1adea..eb26857fc5 100644 --- a/spring-batch-samples/README.md +++ b/spring-batch-samples/README.md @@ -26,6 +26,7 @@ Here is a list of samples with checks to indicate which features each one demons | [Hello world Job Sample](#hello-world-job-sample) | | | | | | | | | | X | | | [Amqp Job Sample](#amqp-job-sample) | | | | | | | | | | X | | | [BeanWrapperMapper Sample](#beanwrappermapper-sample) | | | | X | | | | | | | | +| [Composite ItemReader Sample](#composite-itemreader-sample) | | | | | | | X | | | | | | [Composite ItemWriter Sample](#composite-itemwriter-sample) | | | | | | | X | | | | | | [Customer Filter Sample](#customer-filter-sample) | | | | | | | | | | | X | | [Reader Writer Adapter Sample](#reader-writer-adapter-sample) | | | | | | | X | | | | | @@ -121,6 +122,16 @@ prototype according to the field names in the file. [BeanWrapperMapper Sample](src/main/java/org/springframework/batch/samples/beanwrapper/README.md) +### Composite ItemReader Sample + +This sample shows how to use a composite item reader to read data with +the same format from different data sources. + +In this sample, data items of type `Person` are read from two flat files +and a relational database table. + +[Composite reader Sample](src/main/java/org/springframework/batch/samples/compositereader/README.md) + ### Composite ItemWriter Sample This shows a common use case using a composite pattern, composing diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/compositereader/README.md b/spring-batch-samples/src/main/java/org/springframework/batch/samples/compositereader/README.md new file mode 100644 index 0000000000..4342fbf054 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/compositereader/README.md @@ -0,0 +1,18 @@ +## Composite ItemReader Sample + +### About + +This sample shows how to use a composite item reader to read data with +the same format from different data sources. + +In this sample, data items of type `Person` are read from two flat files +and a relational database table. + +### Run the sample + +You can run the sample from the command line as following: + +``` +$>cd spring-batch-samples +$>../mvnw -Dtest=CompositeItemReaderSampleFunctionalTests#testJobLaunch test +``` \ No newline at end of file diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/data/persons1.csv b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/data/persons1.csv new file mode 100644 index 0000000000..839754d238 --- /dev/null +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/data/persons1.csv @@ -0,0 +1,2 @@ +1,foo1 +2,foo2 \ No newline at end of file diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/data/persons2.csv b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/data/persons2.csv new file mode 100644 index 0000000000..e5a88e3407 --- /dev/null +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/data/persons2.csv @@ -0,0 +1,2 @@ +3,bar1 +4,bar2 \ No newline at end of file diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/sql/data.sql b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/sql/data.sql new file mode 100644 index 0000000000..6b99ba0b49 --- /dev/null +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/sql/data.sql @@ -0,0 +1,2 @@ +insert into person_source values (5, 'baz1'); +insert into person_source values (6, 'baz2'); \ No newline at end of file diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/sql/schema.sql b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/sql/schema.sql new file mode 100644 index 0000000000..1ab4a13663 --- /dev/null +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/compositereader/sql/schema.sql @@ -0,0 +1,2 @@ +create table person_source (id int primary key, name varchar(20)); +create table person_target (id int primary key, name varchar(20)); \ No newline at end of file diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemWriterSampleFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemWriterSampleFunctionalTests.java new file mode 100644 index 0000000000..8c90257b6e --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/compositereader/CompositeItemWriterSampleFunctionalTests.java @@ -0,0 +1,147 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.samples.compositereader; + +import java.util.Arrays; + +import javax.sql.DataSource; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.item.database.JdbcBatchItemWriter; +import org.springframework.batch.item.database.JdbcCursorItemReader; +import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder; +import org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder; +import org.springframework.batch.item.file.FlatFileItemReader; +import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder; +import org.springframework.batch.item.support.CompositeItemReader; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.jdbc.core.DataClassRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.jdbc.support.JdbcTransactionManager; +import org.springframework.test.jdbc.JdbcTestUtils; + +public class CompositeItemWriterSampleFunctionalTests { + + record Person(int id, String name) { + } + + @Test + void testJobLaunch() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(JobConfiguration.class); + JobLauncher jobLauncher = context.getBean(JobLauncher.class); + Job job = context.getBean(Job.class); + + // when + JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + + // then + Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + JdbcTemplate jdbcTemplate = new JdbcTemplate(context.getBean(DataSource.class)); + int personsCount = JdbcTestUtils.countRowsInTable(jdbcTemplate, "person_target"); + Assertions.assertEquals(6, personsCount); + } + + @Configuration + @EnableBatchProcessing + static class JobConfiguration { + + @Bean + public FlatFileItemReader itemReader1() { + return new FlatFileItemReaderBuilder().name("personItemReader1") + .resource(new ClassPathResource("org/springframework/batch/samples/compositereader/data/persons1.csv")) + .delimited() + .names("id", "name") + .targetType(Person.class) + .build(); + } + + @Bean + public FlatFileItemReader itemReader2() { + return new FlatFileItemReaderBuilder().name("personItemReader2") + .resource(new ClassPathResource("org/springframework/batch/samples/compositereader/data/persons2.csv")) + .delimited() + .names("id", "name") + .targetType(Person.class) + .build(); + } + + @Bean + public JdbcCursorItemReader itemReader3() { + String sql = "select * from person_source"; + return new JdbcCursorItemReaderBuilder().name("personItemReader3") + .dataSource(dataSource()) + .sql(sql) + .rowMapper(new DataClassRowMapper<>(Person.class)) + .build(); + } + + @Bean + public CompositeItemReader itemReader() { + return new CompositeItemReader<>(Arrays.asList(itemReader1(), itemReader2(), itemReader3())); + } + + @Bean + public JdbcBatchItemWriter itemWriter() { + String sql = "insert into person_target (id, name) values (:id, :name)"; + return new JdbcBatchItemWriterBuilder().dataSource(dataSource()).sql(sql).beanMapped().build(); + } + + @Bean + public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager) { + return new JobBuilder("job", jobRepository) + .start(new StepBuilder("step", jobRepository).chunk(5, transactionManager) + .reader(itemReader()) + .writer(itemWriter()) + .build()) + .build(); + } + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL) + .addScript("/org/springframework/batch/core/schema-drop-hsqldb.sql") + .addScript("/org/springframework/batch/core/schema-hsqldb.sql") + .addScript("/org/springframework/batch/samples/compositereader/sql/schema.sql") + .addScript("/org/springframework/batch/samples/compositereader/sql/data.sql") + .build(); + } + + @Bean + public JdbcTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } + + } + +} \ No newline at end of file From 946c31c1a3852f5e2a5c197f8397392f4018cd5c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 10 Oct 2024 12:06:34 +0200 Subject: [PATCH 091/152] Add initial support for MongoDB as job repository Resolves #877 Signed-off-by: Fabrice Bibonne --- pom.xml | 2 +- spring-batch-core/pom.xml | 28 +++ .../support/MongoJobExplorerFactoryBean.java | 68 +++++ .../dao/MongoExecutionContextDao.java | 113 +++++++++ .../repository/dao/MongoJobExecutionDao.java | 154 ++++++++++++ .../repository/dao/MongoJobInstanceDao.java | 171 +++++++++++++ .../dao/MongoSequenceIncrementer.java | 95 +++++++ .../repository/dao/MongoStepExecutionDao.java | 162 ++++++++++++ .../persistence/ExecutionContext.java | 25 ++ .../repository/persistence/ExitStatus.java | 23 ++ .../repository/persistence/JobExecution.java | 160 ++++++++++++ .../repository/persistence/JobInstance.java | 69 +++++ .../repository/persistence/JobParameter.java | 23 ++ .../repository/persistence/StepExecution.java | 238 ++++++++++++++++++ .../converter/JobExecutionConverter.java | 83 ++++++ .../converter/JobInstanceConverter.java | 37 +++ .../converter/JobParameterConverter.java | 40 +++ .../converter/StepExecutionConverter.java | 83 ++++++ .../repository/persistence/package-info.java | 4 + .../MongoJobRepositoryFactoryBean.java | 68 +++++ .../batch/core/schema-drop-mongodb.js | 7 + .../batch/core/schema-mongodb.js | 10 + .../MongoDBJobRepositoryIntegrationTests.java | 180 +++++++++++++ 23 files changed, 1842 insertions(+), 1 deletion(-) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobInstanceDao.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/ExecutionContext.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/ExitStatus.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/JobExecution.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/JobInstance.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/JobParameter.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/StepExecution.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobExecutionConverter.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobInstanceConverter.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobParameterConverter.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/StepExecutionConverter.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/package-info.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MongoJobRepositoryFactoryBean.java create mode 100644 spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mongodb.js create mode 100644 spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java diff --git a/pom.xml b/pom.xml index 6c45bb0b31..94c4409cd8 100644 --- a/pom.xml +++ b/pom.xml @@ -85,7 +85,7 @@ 3.1.0 3.1.0 4.0.11 - 5.2.0 + 5.1.4 5.11.1 diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index af28f85e8d..031433483a 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -96,6 +96,28 @@ ${aspectj.version} true + + org.springframework.data + spring-data-mongodb + ${spring-data-mongodb.version} + true + + + org.slf4j + slf4j-api + + + org.mongodb + mongodb-driver-sync + + + + + org.mongodb + mongodb-driver-sync + ${mongodb-driver-sync.version} + true + @@ -128,6 +150,12 @@ ${testcontainers.version} test + + org.testcontainers + mongodb + ${testcontainers.version} + test + org.mariadb.jdbc mariadb-java-client diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java new file mode 100644 index 0000000000..8b24b7febb --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java @@ -0,0 +1,68 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.explore.support; + +import org.springframework.batch.core.repository.dao.ExecutionContextDao; +import org.springframework.batch.core.repository.dao.JobExecutionDao; +import org.springframework.batch.core.repository.dao.JobInstanceDao; +import org.springframework.batch.core.repository.dao.StepExecutionDao; +import org.springframework.batch.core.repository.dao.MongoExecutionContextDao; +import org.springframework.batch.core.repository.dao.MongoJobExecutionDao; +import org.springframework.batch.core.repository.dao.MongoJobInstanceDao; +import org.springframework.batch.core.repository.dao.MongoStepExecutionDao; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.util.Assert; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class MongoJobExplorerFactoryBean extends AbstractJobExplorerFactoryBean implements InitializingBean { + + private MongoOperations mongoOperations; + + public void setMongoOperations(MongoOperations mongoOperations) { + this.mongoOperations = mongoOperations; + } + + @Override + protected JobInstanceDao createJobInstanceDao() { + return new MongoJobInstanceDao(this.mongoOperations); + } + + @Override + protected JobExecutionDao createJobExecutionDao() { + return new MongoJobExecutionDao(this.mongoOperations); + } + + @Override + protected StepExecutionDao createStepExecutionDao() { + return new MongoStepExecutionDao(this.mongoOperations); + } + + @Override + protected ExecutionContextDao createExecutionContextDao() { + return new MongoExecutionContextDao(this.mongoOperations); + } + + @Override + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + Assert.notNull(this.mongoOperations, "MongoOperations must not be null."); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java new file mode 100644 index 0000000000..485882a163 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java @@ -0,0 +1,113 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.dao; + +import java.util.Collection; +import java.util.Map; + +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; + +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class MongoExecutionContextDao implements ExecutionContextDao { + + private static final String STEP_EXECUTIONS_COLLECTION_NAME = "BATCH_STEP_EXECUTION"; + + private static final String JOB_EXECUTIONS_COLLECTION_NAME = "BATCH_JOB_EXECUTION"; + + private final MongoOperations mongoOperations; + + public MongoExecutionContextDao(MongoOperations mongoOperations) { + this.mongoOperations = mongoOperations; + } + + @Override + public ExecutionContext getExecutionContext(JobExecution jobExecution) { + org.springframework.batch.core.repository.persistence.JobExecution execution = this.mongoOperations.findById( + jobExecution.getId(), org.springframework.batch.core.repository.persistence.JobExecution.class, + JOB_EXECUTIONS_COLLECTION_NAME); + if (execution == null) { + return new ExecutionContext(); + } + return new ExecutionContext(execution.getExecutionContext().map()); + } + + @Override + public ExecutionContext getExecutionContext(StepExecution stepExecution) { + org.springframework.batch.core.repository.persistence.StepExecution execution = this.mongoOperations.findById( + stepExecution.getId(), org.springframework.batch.core.repository.persistence.StepExecution.class, + STEP_EXECUTIONS_COLLECTION_NAME); + if (execution == null) { + return new ExecutionContext(); + } + return new ExecutionContext(execution.getExecutionContext().map()); + } + + @Override + public void saveExecutionContext(JobExecution jobExecution) { + ExecutionContext executionContext = jobExecution.getExecutionContext(); + Query query = query(where("_id").is(jobExecution.getId())); + + Update update = Update.update("executionContext", + new org.springframework.batch.core.repository.persistence.ExecutionContext(executionContext.toMap(), + executionContext.isDirty())); + this.mongoOperations.updateFirst(query, update, + org.springframework.batch.core.repository.persistence.JobExecution.class, + JOB_EXECUTIONS_COLLECTION_NAME); + } + + @Override + public void saveExecutionContext(StepExecution stepExecution) { + ExecutionContext executionContext = stepExecution.getExecutionContext(); + Query query = query(where("_id").is(stepExecution.getId())); + + Update update = Update.update("executionContext", + new org.springframework.batch.core.repository.persistence.ExecutionContext(executionContext.toMap(), + executionContext.isDirty())); + this.mongoOperations.updateFirst(query, update, + org.springframework.batch.core.repository.persistence.StepExecution.class, + STEP_EXECUTIONS_COLLECTION_NAME); + + } + + @Override + public void saveExecutionContexts(Collection stepExecutions) { + for (StepExecution stepExecution : stepExecutions) { + saveExecutionContext(stepExecution); + } + } + + @Override + public void updateExecutionContext(JobExecution jobExecution) { + saveExecutionContext(jobExecution); + } + + @Override + public void updateExecutionContext(StepExecution stepExecution) { + saveExecutionContext(stepExecution); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java new file mode 100644 index 0000000000..c4525970d7 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java @@ -0,0 +1,154 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.dao; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.repository.persistence.converter.JobExecutionConverter; +import org.springframework.batch.core.repository.persistence.converter.JobInstanceConverter; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; + +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class MongoJobExecutionDao implements JobExecutionDao { + + private static final String JOB_EXECUTIONS_COLLECTION_NAME = "BATCH_JOB_EXECUTION"; + + private static final String JOB_EXECUTIONS_SEQUENCE_NAME = "BATCH_JOB_EXECUTION_SEQ"; + + private static final String JOB_INSTANCES_COLLECTION_NAME = "BATCH_JOB_INSTANCE"; + + private final MongoOperations mongoOperations; + + private final JobExecutionConverter jobExecutionConverter = new JobExecutionConverter(); + + private final JobInstanceConverter jobInstanceConverter = new JobInstanceConverter(); + + private DataFieldMaxValueIncrementer jobExecutionIncrementer; + + public MongoJobExecutionDao(MongoOperations mongoOperations) { + this.mongoOperations = mongoOperations; + this.jobExecutionIncrementer = new MongoSequenceIncrementer(mongoOperations, JOB_EXECUTIONS_SEQUENCE_NAME); + } + + public void setJobExecutionIncrementer(DataFieldMaxValueIncrementer jobExecutionIncrementer) { + this.jobExecutionIncrementer = jobExecutionIncrementer; + } + + @Override + public void saveJobExecution(JobExecution jobExecution) { + org.springframework.batch.core.repository.persistence.JobExecution jobExecutionToSave = this.jobExecutionConverter + .fromJobExecution(jobExecution); + long jobExecutionId = this.jobExecutionIncrementer.nextLongValue(); + jobExecutionToSave.setJobExecutionId(jobExecutionId); + this.mongoOperations.insert(jobExecutionToSave, JOB_EXECUTIONS_COLLECTION_NAME); + jobExecution.setId(jobExecutionId); + } + + @Override + public void updateJobExecution(JobExecution jobExecution) { + Query query = query(where("jobExecutionId").is(jobExecution.getId())); + org.springframework.batch.core.repository.persistence.JobExecution jobExecutionToUpdate = this.jobExecutionConverter + .fromJobExecution(jobExecution); + this.mongoOperations.findAndReplace(query, jobExecutionToUpdate, JOB_EXECUTIONS_COLLECTION_NAME); + } + + @Override + public List findJobExecutions(JobInstance jobInstance) { + Query query = query(where("jobInstanceId").is(jobInstance.getId())); + List jobExecutions = this.mongoOperations + .find(query, org.springframework.batch.core.repository.persistence.JobExecution.class, + JOB_EXECUTIONS_COLLECTION_NAME); + return jobExecutions.stream() + .map(jobExecution -> this.jobExecutionConverter.toJobExecution(jobExecution, jobInstance)) + .toList(); + } + + @Override + public JobExecution getLastJobExecution(JobInstance jobInstance) { + Query query = query(where("jobInstanceId").is(jobInstance.getId())); + Sort.Order sortOrder = Sort.Order.desc("jobExecutionId"); + org.springframework.batch.core.repository.persistence.JobExecution jobExecution = this.mongoOperations.findOne( + query.with(Sort.by(sortOrder)), + org.springframework.batch.core.repository.persistence.JobExecution.class, + JOB_EXECUTIONS_COLLECTION_NAME); + return jobExecution != null ? this.jobExecutionConverter.toJobExecution(jobExecution, jobInstance) : null; + } + + @Override + public Set findRunningJobExecutions(String jobName) { + Query query = query(where("jobName").is(jobName)); + List jobInstances = this.mongoOperations + .find(query, org.springframework.batch.core.repository.persistence.JobInstance.class, + JOB_INSTANCES_COLLECTION_NAME) + .stream() + .map(this.jobInstanceConverter::toJobInstance) + .toList(); + Set runningJobExecutions = new HashSet<>(); + for (JobInstance jobInstance : jobInstances) { + query = query( + where("jobInstanceId").is(jobInstance.getId()).and("status").in("STARTING", "STARTED", "STOPPING")); + this.mongoOperations + .find(query, org.springframework.batch.core.repository.persistence.JobExecution.class, + JOB_EXECUTIONS_COLLECTION_NAME) + .stream() + .map(jobExecution -> this.jobExecutionConverter.toJobExecution(jobExecution, jobInstance)) + .forEach(runningJobExecutions::add); + } + return runningJobExecutions; + } + + @Override + public JobExecution getJobExecution(Long executionId) { + org.springframework.batch.core.repository.persistence.JobExecution jobExecution = this.mongoOperations.findById( + executionId, org.springframework.batch.core.repository.persistence.JobExecution.class, + JOB_EXECUTIONS_COLLECTION_NAME); + if (jobExecution == null) { + return null; + } + org.springframework.batch.core.repository.persistence.JobInstance jobInstance = this.mongoOperations.findById( + jobExecution.getJobInstanceId(), + org.springframework.batch.core.repository.persistence.JobInstance.class, JOB_INSTANCES_COLLECTION_NAME); + return this.jobExecutionConverter.toJobExecution(jobExecution, + this.jobInstanceConverter.toJobInstance(jobInstance)); + } + + @Override + public void synchronizeStatus(JobExecution jobExecution) { + Query query = query(where("jobExecutionId").is(jobExecution.getId())); + Update update = Update.update("status", jobExecution.getStatus()); + // TODO the contract mentions to update the version as well. Double check if this + // is needed as the version is not used in the tests following the call sites of + // synchronizeStatus + this.mongoOperations.updateFirst(query, update, + org.springframework.batch.core.repository.persistence.JobExecution.class, + JOB_EXECUTIONS_COLLECTION_NAME); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobInstanceDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobInstanceDao.java new file mode 100644 index 0000000000..b967e35f77 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobInstanceDao.java @@ -0,0 +1,171 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.dao; + +import java.util.List; + +import org.springframework.batch.core.DefaultJobKeyGenerator; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.JobKeyGenerator; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.batch.core.repository.persistence.converter.JobInstanceConverter; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; +import org.springframework.util.Assert; + +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class MongoJobInstanceDao implements JobInstanceDao { + + private static final String COLLECTION_NAME = "BATCH_JOB_INSTANCE"; + + private static final String SEQUENCE_NAME = "BATCH_JOB_INSTANCE_SEQ"; + + private final MongoOperations mongoOperations; + + private DataFieldMaxValueIncrementer jobInstanceIncrementer; + + private JobKeyGenerator jobKeyGenerator = new DefaultJobKeyGenerator(); + + private final JobInstanceConverter jobInstanceConverter = new JobInstanceConverter(); + + public MongoJobInstanceDao(MongoOperations mongoOperations) { + Assert.notNull(mongoOperations, "mongoOperations must not be null."); + this.mongoOperations = mongoOperations; + this.jobInstanceIncrementer = new MongoSequenceIncrementer(mongoOperations, SEQUENCE_NAME); + } + + public void setJobKeyGenerator(JobKeyGenerator jobKeyGenerator) { + this.jobKeyGenerator = jobKeyGenerator; + } + + public void setJobInstanceIncrementer(DataFieldMaxValueIncrementer jobInstanceIncrementer) { + this.jobInstanceIncrementer = jobInstanceIncrementer; + } + + @Override + public JobInstance createJobInstance(String jobName, JobParameters jobParameters) { + Assert.notNull(jobName, "Job name must not be null."); + Assert.notNull(jobParameters, "JobParameters must not be null."); + + Assert.state(getJobInstance(jobName, jobParameters) == null, "JobInstance must not already exist"); + + org.springframework.batch.core.repository.persistence.JobInstance jobInstanceToSave = new org.springframework.batch.core.repository.persistence.JobInstance(); + jobInstanceToSave.setJobName(jobName); + String key = this.jobKeyGenerator.generateKey(jobParameters); + jobInstanceToSave.setJobKey(key); + long instanceId = jobInstanceIncrementer.nextLongValue(); + jobInstanceToSave.setJobInstanceId(instanceId); + this.mongoOperations.insert(jobInstanceToSave, COLLECTION_NAME); + + JobInstance jobInstance = new JobInstance(instanceId, jobName); + jobInstance.incrementVersion(); // TODO is this needed? + return jobInstance; + } + + @Override + public JobInstance getJobInstance(String jobName, JobParameters jobParameters) { + String key = this.jobKeyGenerator.generateKey(jobParameters); + Query query = query(where("jobName").is(jobName).and("jobKey").is(key)); + org.springframework.batch.core.repository.persistence.JobInstance jobInstance = this.mongoOperations + .findOne(query, org.springframework.batch.core.repository.persistence.JobInstance.class, COLLECTION_NAME); + return jobInstance != null ? this.jobInstanceConverter.toJobInstance(jobInstance) : null; + } + + @Override + public JobInstance getJobInstance(Long instanceId) { + Query query = query(where("jobInstanceId").is(instanceId)); + org.springframework.batch.core.repository.persistence.JobInstance jobInstance = this.mongoOperations + .findOne(query, org.springframework.batch.core.repository.persistence.JobInstance.class, COLLECTION_NAME); + return jobInstance != null ? this.jobInstanceConverter.toJobInstance(jobInstance) : null; + } + + @Override + public JobInstance getJobInstance(JobExecution jobExecution) { + return getJobInstance(jobExecution.getJobId()); + } + + @Override + public List getJobInstances(String jobName, int start, int count) { + Query query = query(where("jobName").is(jobName)); + Sort.Order sortOrder = Sort.Order.desc("jobInstanceId"); + List jobInstances = this.mongoOperations + .find(query.with(Sort.by(sortOrder)), + org.springframework.batch.core.repository.persistence.JobInstance.class, COLLECTION_NAME) + .stream() + .toList(); + return jobInstances.subList(start, jobInstances.size()) + .stream() + .map(this.jobInstanceConverter::toJobInstance) + .limit(count) + .toList(); + } + + @Override + public JobInstance getLastJobInstance(String jobName) { + Query query = query(where("jobName").is(jobName)); + Sort.Order sortOrder = Sort.Order.desc("jobInstanceId"); + org.springframework.batch.core.repository.persistence.JobInstance jobInstance = this.mongoOperations.findOne( + query.with(Sort.by(sortOrder)), org.springframework.batch.core.repository.persistence.JobInstance.class, + COLLECTION_NAME); + return jobInstance != null ? this.jobInstanceConverter.toJobInstance(jobInstance) : null; + } + + @Override + public List getJobNames() { + return this.mongoOperations + .findAll(org.springframework.batch.core.repository.persistence.JobInstance.class, COLLECTION_NAME) + .stream() + .map(org.springframework.batch.core.repository.persistence.JobInstance::getJobName) + .toList(); + } + + @Override + public List findJobInstancesByName(String jobName, int start, int count) { + Query query = query(where("jobName").alike(Example.of(jobName))); + Sort.Order sortOrder = Sort.Order.desc("jobInstanceId"); + List jobInstances = this.mongoOperations + .find(query.with(Sort.by(sortOrder)), + org.springframework.batch.core.repository.persistence.JobInstance.class, COLLECTION_NAME) + .stream() + .toList(); + return jobInstances.subList(start, jobInstances.size()) + .stream() + .map(this.jobInstanceConverter::toJobInstance) + .limit(count) + .toList(); + } + + @Override + public long getJobInstanceCount(String jobName) throws NoSuchJobException { + if (!getJobNames().contains(jobName)) { + throw new NoSuchJobException("Job not found " + jobName); + } + Query query = query(where("jobName").is(jobName)); + return this.mongoOperations.count(query, COLLECTION_NAME); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java new file mode 100644 index 0000000000..683d2ad69e --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java @@ -0,0 +1,95 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.dao; + +import org.springframework.dao.DataAccessException; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; + +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; + +// Based on https://www.mongodb.com/blog/post/generating-globally-unique-identifiers-for-use-with-mongodb +// Section: Use a single counter document to generate unique identifiers one at a time + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class MongoSequenceIncrementer implements DataFieldMaxValueIncrementer { + + private final MongoOperations mongoTemplate; + + private final String sequenceName; + + public MongoSequenceIncrementer(MongoOperations mongoTemplate, String sequenceName) { + this.mongoTemplate = mongoTemplate; + this.sequenceName = sequenceName; + } + + @Override + public long nextLongValue() throws DataAccessException { + // TODO optimize + MongoSequence sequence = mongoTemplate.findOne(new Query(), MongoSequence.class, sequenceName); + Query query = query(where("_id").is(sequence.getId())); + Update update = new Update().inc("count", 1); + // The following does not return the modified document + mongoTemplate.findAndModify(query, update, MongoSequence.class, sequenceName); + return mongoTemplate.findOne(new Query(), MongoSequence.class, sequenceName).getCount(); + } + + @Override + public int nextIntValue() throws DataAccessException { + throw new UnsupportedOperationException(); + } + + @Override + public String nextStringValue() throws DataAccessException { + throw new UnsupportedOperationException(); + } + + public static final class MongoSequence { + + private String id; + + private long count; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public long getCount() { + return count; + } + + public void setCount(long count) { + this.count = count; + } + + @Override + public String toString() { + return "MongoSequence{" + "id='" + id + '\'' + ", count=" + count + '}'; + } + + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java new file mode 100644 index 0000000000..44215babd7 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java @@ -0,0 +1,162 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.dao; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; + +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.repository.persistence.converter.JobExecutionConverter; +import org.springframework.batch.core.repository.persistence.converter.StepExecutionConverter; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; + +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class MongoStepExecutionDao implements StepExecutionDao { + + private static final String STEP_EXECUTIONS_COLLECTION_NAME = "BATCH_STEP_EXECUTION"; + + private static final String STEP_EXECUTIONS_SEQUENCE_NAME = "BATCH_STEP_EXECUTION_SEQ"; + + private static final String JOB_EXECUTIONS_COLLECTION_NAME = "BATCH_JOB_EXECUTION"; + + private final StepExecutionConverter stepExecutionConverter = new StepExecutionConverter(); + + private final JobExecutionConverter jobExecutionConverter = new JobExecutionConverter(); + + private final MongoOperations mongoOperations; + + private DataFieldMaxValueIncrementer stepExecutionIncrementer; + + public MongoStepExecutionDao(MongoOperations mongoOperations) { + this.mongoOperations = mongoOperations; + this.stepExecutionIncrementer = new MongoSequenceIncrementer(mongoOperations, STEP_EXECUTIONS_SEQUENCE_NAME); + } + + public void setStepExecutionIncrementer(DataFieldMaxValueIncrementer stepExecutionIncrementer) { + this.stepExecutionIncrementer = stepExecutionIncrementer; + } + + @Override + public void saveStepExecution(StepExecution stepExecution) { + org.springframework.batch.core.repository.persistence.StepExecution stepExecutionToSave = this.stepExecutionConverter + .fromStepExecution(stepExecution); + long stepExecutionId = this.stepExecutionIncrementer.nextLongValue(); + stepExecutionToSave.setStepExecutionId(stepExecutionId); + this.mongoOperations.insert(stepExecutionToSave, STEP_EXECUTIONS_COLLECTION_NAME); + stepExecution.setId(stepExecutionId); + } + + @Override + public void saveStepExecutions(Collection stepExecutions) { + for (StepExecution stepExecution : stepExecutions) { + saveStepExecution(stepExecution); + } + } + + @Override + public void updateStepExecution(StepExecution stepExecution) { + Query query = query(where("stepExecutionId").is(stepExecution.getId())); + org.springframework.batch.core.repository.persistence.StepExecution stepExecutionToUpdate = this.stepExecutionConverter + .fromStepExecution(stepExecution); + this.mongoOperations.findAndReplace(query, stepExecutionToUpdate, STEP_EXECUTIONS_COLLECTION_NAME); + } + + @Override + public StepExecution getStepExecution(JobExecution jobExecution, Long stepExecutionId) { + org.springframework.batch.core.repository.persistence.StepExecution stepExecution = this.mongoOperations + .findById(stepExecutionId, org.springframework.batch.core.repository.persistence.StepExecution.class, + STEP_EXECUTIONS_COLLECTION_NAME); + return stepExecution != null ? this.stepExecutionConverter.toStepExecution(stepExecution, jobExecution) : null; + } + + @Override + public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) { + // TODO optimize the query + // get all step executions + List stepExecutions = new ArrayList<>(); + Query query = query(where("jobInstanceId").is(jobInstance.getId())); + List jobExecutions = this.mongoOperations + .find(query, org.springframework.batch.core.repository.persistence.JobExecution.class, + JOB_EXECUTIONS_COLLECTION_NAME); + for (org.springframework.batch.core.repository.persistence.JobExecution jobExecution : jobExecutions) { + stepExecutions.addAll(jobExecution.getStepExecutions()); + } + // sort step executions by creation date then id (see contract) and return the + // first one + Optional lastStepExecution = stepExecutions + .stream() + .min(Comparator + .comparing(org.springframework.batch.core.repository.persistence.StepExecution::getCreateTime) + .thenComparing(org.springframework.batch.core.repository.persistence.StepExecution::getId)); + if (lastStepExecution.isPresent()) { + org.springframework.batch.core.repository.persistence.StepExecution stepExecution = lastStepExecution.get(); + JobExecution jobExecution = this.jobExecutionConverter.toJobExecution(jobExecutions.stream() + .filter(execution -> execution.getJobExecutionId().equals(stepExecution.getJobExecutionId())) + .findFirst() + .get(), jobInstance); + return this.stepExecutionConverter.toStepExecution(stepExecution, jobExecution); + } + else { + return null; + } + } + + @Override + public void addStepExecutions(JobExecution jobExecution) { + Query query = query(where("jobExecutionId").is(jobExecution.getId())); + List stepExecutions = this.mongoOperations + .find(query, org.springframework.batch.core.repository.persistence.StepExecution.class, + STEP_EXECUTIONS_COLLECTION_NAME) + .stream() + .map(stepExecution -> this.stepExecutionConverter.toStepExecution(stepExecution, jobExecution)) + .toList(); + jobExecution.addStepExecutions(stepExecutions); + } + + @Override + public long countStepExecutions(JobInstance jobInstance, String stepName) { + long count = 0; + // TODO optimize the count query + Query query = query(where("jobInstanceId").is(jobInstance.getId())); + List jobExecutions = this.mongoOperations + .find(query, org.springframework.batch.core.repository.persistence.JobExecution.class, + JOB_EXECUTIONS_COLLECTION_NAME); + for (org.springframework.batch.core.repository.persistence.JobExecution jobExecution : jobExecutions) { + List stepExecutions = jobExecution + .getStepExecutions(); + for (org.springframework.batch.core.repository.persistence.StepExecution stepExecution : stepExecutions) { + if (stepExecution.getName().equals(stepName)) { + count++; + } + } + } + return count; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/ExecutionContext.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/ExecutionContext.java new file mode 100644 index 0000000000..6c3f51b249 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/ExecutionContext.java @@ -0,0 +1,25 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.persistence; + +import java.util.Map; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public record ExecutionContext(Map map, boolean dirty) { +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/ExitStatus.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/ExitStatus.java new file mode 100644 index 0000000000..e149183cfc --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/ExitStatus.java @@ -0,0 +1,23 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.persistence; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public record ExitStatus(String exitCode, String exitDescription) { +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/JobExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/JobExecution.java new file mode 100644 index 0000000000..2a0577417d --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/JobExecution.java @@ -0,0 +1,160 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.persistence; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.batch.core.BatchStatus; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class JobExecution { + + private String id; + + private Long jobExecutionId; + + private Long jobInstanceId; + + private Map> jobParameters = new HashMap<>(); + + private List stepExecutions = new ArrayList<>(); + + private BatchStatus status; + + private LocalDateTime startTime; + + private LocalDateTime createTime; + + private LocalDateTime endTime; + + private LocalDateTime lastUpdated; + + private ExitStatus exitStatus; + + private ExecutionContext executionContext; + + public JobExecution() { + } + + public String getId() { + return id; + } + + public Long getJobInstanceId() { + return jobInstanceId; + } + + public void setJobInstanceId(Long jobInstanceId) { + this.jobInstanceId = jobInstanceId; + } + + public Long getJobExecutionId() { + return jobExecutionId; + } + + public void setJobExecutionId(Long jobExecutionId) { + this.jobExecutionId = jobExecutionId; + } + + public Map> getJobParameters() { + return jobParameters; + } + + public void setJobParameters(Map> jobParameters) { + this.jobParameters = jobParameters; + } + + public List getStepExecutions() { + return stepExecutions; + } + + public void setStepExecutions(List stepExecutions) { + this.stepExecutions = stepExecutions; + } + + public BatchStatus getStatus() { + return status; + } + + public void setStatus(BatchStatus status) { + this.status = status; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public void setStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public LocalDateTime getEndTime() { + return endTime; + } + + public void setEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } + + public LocalDateTime getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(LocalDateTime lastUpdated) { + this.lastUpdated = lastUpdated; + } + + public ExitStatus getExitStatus() { + return exitStatus; + } + + public void setExitStatus(ExitStatus exitStatus) { + this.exitStatus = exitStatus; + } + + public ExecutionContext getExecutionContext() { + return executionContext; + } + + public void setExecutionContext(ExecutionContext executionContext) { + this.executionContext = executionContext; + } + + @Override + public String toString() { + return "JobExecution{" + "id='" + id + '\'' + ", jobExecutionId=" + jobExecutionId + ", jobInstanceId=" + + jobInstanceId + ", jobParameters=" + jobParameters + ", stepExecutions=" + stepExecutions + + ", status=" + status + ", startTime=" + startTime + ", createTime=" + createTime + ", endTime=" + + endTime + ", lastUpdated=" + lastUpdated + ", exitStatus=" + exitStatus + ", executionContext=" + + executionContext + '}'; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/JobInstance.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/JobInstance.java new file mode 100644 index 0000000000..a096be4b78 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/JobInstance.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.persistence; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class JobInstance { + + private String id; + + private Long jobInstanceId; + + private String jobName; + + private String jobKey; + + public JobInstance() { + } + + public String getId() { + return id; + } + + public Long getJobInstanceId() { + return jobInstanceId; + } + + public void setJobInstanceId(Long jobInstanceId) { + this.jobInstanceId = jobInstanceId; + } + + public String getJobName() { + return jobName; + } + + public void setJobName(String jobName) { + this.jobName = jobName; + } + + public String getJobKey() { + return jobKey; + } + + public void setJobKey(String jobKey) { + this.jobKey = jobKey; + } + + @Override + public String toString() { + return "JobInstance{" + "id='" + id + '\'' + ", jobInstanceId=" + jobInstanceId + ", jobName='" + jobName + '\'' + + ", jobKey='" + jobKey + '\'' + '}'; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/JobParameter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/JobParameter.java new file mode 100644 index 0000000000..af1c1f4673 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/JobParameter.java @@ -0,0 +1,23 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.persistence; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public record JobParameter(T value, String type, boolean identifying) { +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/StepExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/StepExecution.java new file mode 100644 index 0000000000..351fe34442 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/StepExecution.java @@ -0,0 +1,238 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.persistence; + +import java.time.LocalDateTime; + +import org.springframework.batch.core.BatchStatus; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class StepExecution { + + private String id; + + private Long stepExecutionId; + + private Long jobExecutionId; + + private String name; + + private BatchStatus status; + + private long readCount; + + private long writeCount; + + private long commitCount; + + private long rollbackCount; + + private long readSkipCount; + + private long processSkipCount; + + private long writeSkipCount; + + private long filterCount; + + private LocalDateTime startTime; + + private LocalDateTime createTime; + + private LocalDateTime endTime; + + private LocalDateTime lastUpdated; + + private ExecutionContext executionContext; + + private ExitStatus exitStatus; + + private boolean terminateOnly; + + public StepExecution() { + } + + public String getId() { + return id; + } + + public Long getStepExecutionId() { + return stepExecutionId; + } + + public void setStepExecutionId(Long stepExecutionId) { + this.stepExecutionId = stepExecutionId; + } + + public Long getJobExecutionId() { + return jobExecutionId; + } + + public void setJobExecutionId(Long jobExecutionId) { + this.jobExecutionId = jobExecutionId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public BatchStatus getStatus() { + return status; + } + + public void setStatus(BatchStatus status) { + this.status = status; + } + + public long getReadCount() { + return readCount; + } + + public void setReadCount(long readCount) { + this.readCount = readCount; + } + + public long getWriteCount() { + return writeCount; + } + + public void setWriteCount(long writeCount) { + this.writeCount = writeCount; + } + + public long getCommitCount() { + return commitCount; + } + + public void setCommitCount(long commitCount) { + this.commitCount = commitCount; + } + + public long getRollbackCount() { + return rollbackCount; + } + + public void setRollbackCount(long rollbackCount) { + this.rollbackCount = rollbackCount; + } + + public long getReadSkipCount() { + return readSkipCount; + } + + public void setReadSkipCount(long readSkipCount) { + this.readSkipCount = readSkipCount; + } + + public long getProcessSkipCount() { + return processSkipCount; + } + + public void setProcessSkipCount(long processSkipCount) { + this.processSkipCount = processSkipCount; + } + + public long getWriteSkipCount() { + return writeSkipCount; + } + + public void setWriteSkipCount(long writeSkipCount) { + this.writeSkipCount = writeSkipCount; + } + + public long getFilterCount() { + return filterCount; + } + + public void setFilterCount(long filterCount) { + this.filterCount = filterCount; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public void setStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + public LocalDateTime getCreateTime() { + return createTime; + } + + public void setCreateTime(LocalDateTime createTime) { + this.createTime = createTime; + } + + public LocalDateTime getEndTime() { + return endTime; + } + + public void setEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } + + public LocalDateTime getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(LocalDateTime lastUpdated) { + this.lastUpdated = lastUpdated; + } + + public ExecutionContext getExecutionContext() { + return executionContext; + } + + public void setExecutionContext(ExecutionContext executionContext) { + this.executionContext = executionContext; + } + + public ExitStatus getExitStatus() { + return exitStatus; + } + + public void setExitStatus(ExitStatus exitStatus) { + this.exitStatus = exitStatus; + } + + public boolean isTerminateOnly() { + return terminateOnly; + } + + public void setTerminateOnly(boolean terminateOnly) { + this.terminateOnly = terminateOnly; + } + + @Override + public String toString() { + return "StepExecution{" + "id='" + id + '\'' + ", stepExecutionId=" + stepExecutionId + ", jobExecutionId='" + + jobExecutionId + '\'' + ", name='" + name + '\'' + ", status=" + status + ", readCount=" + readCount + + ", writeCount=" + writeCount + ", commitCount=" + commitCount + ", rollbackCount=" + rollbackCount + + ", readSkipCount=" + readSkipCount + ", processSkipCount=" + processSkipCount + ", writeSkipCount=" + + writeSkipCount + ", filterCount=" + filterCount + ", startTime=" + startTime + ", createTime=" + + createTime + ", endTime=" + endTime + ", lastUpdated=" + lastUpdated + ", executionContext=" + + executionContext + ", exitStatus=" + exitStatus + ", terminateOnly=" + terminateOnly + '}'; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobExecutionConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobExecutionConverter.java new file mode 100644 index 0000000000..686c48464c --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobExecutionConverter.java @@ -0,0 +1,83 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.persistence.converter; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.repository.persistence.ExecutionContext; +import org.springframework.batch.core.repository.persistence.ExitStatus; +import org.springframework.batch.core.repository.persistence.JobExecution; +import org.springframework.batch.core.repository.persistence.JobParameter; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class JobExecutionConverter { + + private final JobParameterConverter jobParameterConverter = new JobParameterConverter(); + + private final StepExecutionConverter stepExecutionConverter = new StepExecutionConverter(); + + public org.springframework.batch.core.JobExecution toJobExecution(JobExecution source, JobInstance jobInstance) { + Map> parameterMap = new HashMap<>(); + source.getJobParameters() + .forEach((key, value) -> parameterMap.put(key, this.jobParameterConverter.toJobParameter(value))); + org.springframework.batch.core.JobExecution jobExecution = new org.springframework.batch.core.JobExecution( + jobInstance, source.getJobExecutionId(), new JobParameters(parameterMap)); + jobExecution.addStepExecutions(source.getStepExecutions() + .stream() + .map(stepExecution -> this.stepExecutionConverter.toStepExecution(stepExecution, jobExecution)) + .toList()); + jobExecution.setStatus(source.getStatus()); + jobExecution.setStartTime(source.getStartTime()); + jobExecution.setCreateTime(source.getCreateTime()); + jobExecution.setEndTime(source.getEndTime()); + jobExecution.setLastUpdated(source.getLastUpdated()); + jobExecution.setExitStatus(new org.springframework.batch.core.ExitStatus(source.getExitStatus().exitCode(), + source.getExitStatus().exitDescription())); + jobExecution.setExecutionContext( + new org.springframework.batch.item.ExecutionContext(source.getExecutionContext().map())); + return jobExecution; + } + + public JobExecution fromJobExecution(org.springframework.batch.core.JobExecution source) { + JobExecution jobExecution = new JobExecution(); + jobExecution.setJobExecutionId(source.getId()); + jobExecution.setJobInstanceId(source.getJobInstance().getInstanceId()); + Map> parameterMap = new HashMap<>(); + source.getJobParameters() + .getParameters() + .forEach((key, value) -> parameterMap.put(key, this.jobParameterConverter.fromJobParameter(value))); + jobExecution.setJobParameters(parameterMap); + jobExecution.setStepExecutions( + source.getStepExecutions().stream().map(this.stepExecutionConverter::fromStepExecution).toList()); + jobExecution.setStatus(source.getStatus()); + jobExecution.setStartTime(source.getStartTime()); + jobExecution.setCreateTime(source.getCreateTime()); + jobExecution.setEndTime(source.getEndTime()); + jobExecution.setLastUpdated(source.getLastUpdated()); + jobExecution.setExitStatus( + new ExitStatus(source.getExitStatus().getExitCode(), source.getExitStatus().getExitDescription())); + org.springframework.batch.item.ExecutionContext executionContext = source.getExecutionContext(); + jobExecution.setExecutionContext(new ExecutionContext(executionContext.toMap(), executionContext.isDirty())); + return jobExecution; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobInstanceConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobInstanceConverter.java new file mode 100644 index 0000000000..82b3a277de --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobInstanceConverter.java @@ -0,0 +1,37 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.persistence.converter; + +import org.springframework.batch.core.repository.persistence.JobInstance; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class JobInstanceConverter { + + public org.springframework.batch.core.JobInstance toJobInstance(JobInstance source) { + return new org.springframework.batch.core.JobInstance(source.getJobInstanceId(), source.getJobName()); + } + + public JobInstance fromJobInstance(org.springframework.batch.core.JobInstance source) { + JobInstance jobInstance = new JobInstance(); + jobInstance.setJobName(source.getJobName()); + jobInstance.setJobInstanceId(source.getInstanceId()); + return jobInstance; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobParameterConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobParameterConverter.java new file mode 100644 index 0000000000..361c98c36b --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/JobParameterConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.persistence.converter; + +import org.springframework.batch.core.repository.persistence.JobParameter; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class JobParameterConverter { + + public org.springframework.batch.core.JobParameter toJobParameter(JobParameter source) { + try { + return new org.springframework.batch.core.JobParameter<>(source.value(), + (Class) Class.forName(source.type()), source.identifying()); + } + catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public JobParameter fromJobParameter(org.springframework.batch.core.JobParameter source) { + return new JobParameter<>(source.getValue(), source.getType().getName(), source.isIdentifying()); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/StepExecutionConverter.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/StepExecutionConverter.java new file mode 100644 index 0000000000..221e9c50cf --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/converter/StepExecutionConverter.java @@ -0,0 +1,83 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.persistence.converter; + +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.repository.persistence.ExecutionContext; +import org.springframework.batch.core.repository.persistence.ExitStatus; +import org.springframework.batch.core.repository.persistence.StepExecution; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class StepExecutionConverter { + + public org.springframework.batch.core.StepExecution toStepExecution(StepExecution source, + JobExecution jobExecution) { + org.springframework.batch.core.StepExecution stepExecution = new org.springframework.batch.core.StepExecution( + source.getName(), jobExecution, source.getStepExecutionId()); + stepExecution.setStatus(source.getStatus()); + stepExecution.setReadCount(source.getReadCount()); + stepExecution.setWriteCount(source.getWriteCount()); + stepExecution.setCommitCount(source.getCommitCount()); + stepExecution.setRollbackCount(source.getRollbackCount()); + stepExecution.setReadSkipCount(source.getReadSkipCount()); + stepExecution.setProcessSkipCount(source.getProcessSkipCount()); + stepExecution.setWriteSkipCount(source.getWriteSkipCount()); + stepExecution.setFilterCount(source.getFilterCount()); + stepExecution.setStartTime(source.getStartTime()); + stepExecution.setCreateTime(source.getCreateTime()); + stepExecution.setEndTime(source.getEndTime()); + stepExecution.setLastUpdated(source.getLastUpdated()); + stepExecution.setExitStatus(new org.springframework.batch.core.ExitStatus(source.getExitStatus().exitCode(), + source.getExitStatus().exitDescription())); + stepExecution.setExecutionContext( + new org.springframework.batch.item.ExecutionContext(source.getExecutionContext().map())); + if (source.isTerminateOnly()) { + stepExecution.setTerminateOnly(); + } + return stepExecution; + } + + public StepExecution fromStepExecution(org.springframework.batch.core.StepExecution source) { + StepExecution stepExecution = new StepExecution(); + stepExecution.setStepExecutionId(source.getId()); + stepExecution.setJobExecutionId(source.getJobExecutionId()); + stepExecution.setName(source.getStepName()); + stepExecution.setJobExecutionId(source.getJobExecutionId()); + stepExecution.setStatus(source.getStatus()); + stepExecution.setReadCount(source.getReadCount()); + stepExecution.setWriteCount(source.getWriteCount()); + stepExecution.setCommitCount(source.getCommitCount()); + stepExecution.setRollbackCount(source.getRollbackCount()); + stepExecution.setReadSkipCount(source.getReadSkipCount()); + stepExecution.setProcessSkipCount(source.getProcessSkipCount()); + stepExecution.setWriteSkipCount(source.getWriteSkipCount()); + stepExecution.setFilterCount(source.getFilterCount()); + stepExecution.setStartTime(source.getStartTime()); + stepExecution.setCreateTime(source.getCreateTime()); + stepExecution.setEndTime(source.getEndTime()); + stepExecution.setLastUpdated(source.getLastUpdated()); + stepExecution.setExitStatus( + new ExitStatus(source.getExitStatus().getExitCode(), source.getExitStatus().getExitDescription())); + org.springframework.batch.item.ExecutionContext executionContext = source.getExecutionContext(); + stepExecution.setExecutionContext(new ExecutionContext(executionContext.toMap(), executionContext.isDirty())); + stepExecution.setTerminateOnly(source.isTerminateOnly()); + return stepExecution; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/package-info.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/package-info.java new file mode 100644 index 0000000000..2d1a93bd40 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/persistence/package-info.java @@ -0,0 +1,4 @@ +/** + * This package contains the classes of the persistence model. + */ +package org.springframework.batch.core.repository.persistence; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MongoJobRepositoryFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MongoJobRepositoryFactoryBean.java new file mode 100644 index 0000000000..51030cd957 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MongoJobRepositoryFactoryBean.java @@ -0,0 +1,68 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.support; + +import org.springframework.batch.core.repository.dao.ExecutionContextDao; +import org.springframework.batch.core.repository.dao.JobExecutionDao; +import org.springframework.batch.core.repository.dao.JobInstanceDao; +import org.springframework.batch.core.repository.dao.StepExecutionDao; +import org.springframework.batch.core.repository.dao.MongoExecutionContextDao; +import org.springframework.batch.core.repository.dao.MongoJobExecutionDao; +import org.springframework.batch.core.repository.dao.MongoJobInstanceDao; +import org.springframework.batch.core.repository.dao.MongoStepExecutionDao; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.util.Assert; + +/** + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class MongoJobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean implements InitializingBean { + + private MongoOperations mongoOperations; + + public void setMongoOperations(MongoOperations mongoOperations) { + this.mongoOperations = mongoOperations; + } + + @Override + protected JobInstanceDao createJobInstanceDao() { + return new MongoJobInstanceDao(this.mongoOperations); + } + + @Override + protected JobExecutionDao createJobExecutionDao() { + return new MongoJobExecutionDao(this.mongoOperations); + } + + @Override + protected StepExecutionDao createStepExecutionDao() { + return new MongoStepExecutionDao(this.mongoOperations); + } + + @Override + protected ExecutionContextDao createExecutionContextDao() { + return new MongoExecutionContextDao(this.mongoOperations); + } + + @Override + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + Assert.notNull(this.mongoOperations, "MongoOperations must not be null."); + } + +} diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mongodb.js b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mongodb.js new file mode 100644 index 0000000000..6a4d05c67f --- /dev/null +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mongodb.js @@ -0,0 +1,7 @@ +// to execute in MongoShell after changing the database name `db.` as needed +db.getCollection("BATCH_JOB_INSTANCE").drop(); +db.getCollection("BATCH_JOB_EXECUTION").drop(); +db.getCollection("BATCH_STEP_EXECUTION").drop(); +db.getCollection("BATCH_JOB_INSTANCE_SEQ").drop(); +db.getCollection("BATCH_JOB_EXECUTION_SEQ").drop(); +db.getCollection("BATCH_STEP_EXECUTION_SEQ").drop(); diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js new file mode 100644 index 0000000000..d8a3d25715 --- /dev/null +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js @@ -0,0 +1,10 @@ +// to execute in MongoShell after changing the database name `db.` as needed +db.createCollection("BATCH_JOB_INSTANCE"); +db.createCollection("BATCH_JOB_EXECUTION"); +db.createCollection("BATCH_STEP_EXECUTION"); +db.createCollection("BATCH_JOB_INSTANCE_SEQ"); +db.createCollection("BATCH_JOB_EXECUTION_SEQ"); +db.createCollection("BATCH_STEP_EXECUTION_SEQ"); +db.getCollection("BATCH_JOB_INSTANCE_SEQ").insertOne({count : 0}); +db.getCollection("BATCH_JOB_EXECUTION_SEQ").insertOne({count : 0}); +db.getCollection("BATCH_STEP_EXECUTION_SEQ").insertOne({count : 0}); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java new file mode 100644 index 0000000000..3499b51939 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java @@ -0,0 +1,180 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.support; + +import java.time.LocalDateTime; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import org.bson.Document; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.core.explore.support.MongoJobExplorerFactoryBean; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.MongoDatabaseFactory; +import org.springframework.data.mongodb.MongoTransactionManager; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Mahmoud Ben Hassine + */ +@Testcontainers(disabledWithoutDocker = true) +@ExtendWith(SpringExtension.class) +@ContextConfiguration +public class MongoDBJobRepositoryIntegrationTests { + + private static final DockerImageName MONGODB_IMAGE = DockerImageName.parse("mongo:8.0.1"); + + @Container + public static MongoDBContainer mongodb = new MongoDBContainer(MONGODB_IMAGE); + + @Autowired + private MongoTemplate mongoTemplate; + + @BeforeEach + public void setUp() { + mongoTemplate.createCollection("BATCH_JOB_INSTANCE"); + mongoTemplate.createCollection("BATCH_JOB_EXECUTION"); + mongoTemplate.createCollection("BATCH_STEP_EXECUTION"); + mongoTemplate.createCollection("BATCH_JOB_INSTANCE_SEQ"); + mongoTemplate.createCollection("BATCH_JOB_EXECUTION_SEQ"); + mongoTemplate.createCollection("BATCH_STEP_EXECUTION_SEQ"); + mongoTemplate.getCollection("BATCH_JOB_INSTANCE_SEQ").insertOne(new Document("count", 0)); + mongoTemplate.getCollection("BATCH_JOB_EXECUTION_SEQ").insertOne(new Document("count", 0)); + mongoTemplate.getCollection("BATCH_STEP_EXECUTION_SEQ").insertOne(new Document("count", 0)); + } + + @Test + void testJobExecution(@Autowired JobLauncher jobLauncher, @Autowired Job job) throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addString("name", "foo") + .addLocalDateTime("runtime", LocalDateTime.now()) + .toJobParameters(); + + // when + JobExecution jobExecution = jobLauncher.run(job, jobParameters); + + // then + Assertions.assertNotNull(jobExecution); + Assertions.assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + + MongoCollection jobInstancesCollection = mongoTemplate.getCollection("BATCH_JOB_INSTANCE"); + MongoCollection jobExecutionsCollection = mongoTemplate.getCollection("BATCH_JOB_EXECUTION"); + MongoCollection stepExecutionsCollection = mongoTemplate.getCollection("BATCH_STEP_EXECUTION"); + + Assertions.assertEquals(1, jobInstancesCollection.countDocuments()); + Assertions.assertEquals(1, jobExecutionsCollection.countDocuments()); + Assertions.assertEquals(2, stepExecutionsCollection.countDocuments()); + + // dump results for inspection + dump(jobInstancesCollection, "job instance = "); + dump(jobExecutionsCollection, "job execution = "); + dump(stepExecutionsCollection, "step execution = "); + } + + private static void dump(MongoCollection collection, String prefix) { + for (Document document : collection.find()) { + System.out.println(prefix + document.toJson()); + } + } + + @Configuration + @EnableBatchProcessing + static class TestConfiguration { + + @Bean + public JobRepository jobRepository(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) + throws Exception { + MongoJobRepositoryFactoryBean jobRepositoryFactoryBean = new MongoJobRepositoryFactoryBean(); + jobRepositoryFactoryBean.setMongoOperations(mongoTemplate); + jobRepositoryFactoryBean.setTransactionManager(transactionManager); + jobRepositoryFactoryBean.afterPropertiesSet(); + return jobRepositoryFactoryBean.getObject(); + } + + @Bean + public JobExplorer jobExplorer(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) + throws Exception { + MongoJobExplorerFactoryBean jobExplorerFactoryBean = new MongoJobExplorerFactoryBean(); + jobExplorerFactoryBean.setMongoOperations(mongoTemplate); + jobExplorerFactoryBean.setTransactionManager(transactionManager); + jobExplorerFactoryBean.afterPropertiesSet(); + return jobExplorerFactoryBean.getObject(); + } + + @Bean + public MongoDatabaseFactory mongoDatabaseFactory() { + MongoClient mongoClient = MongoClients.create(mongodb.getConnectionString()); + return new SimpleMongoClientDatabaseFactory(mongoClient, "test"); + } + + @Bean + public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory) { + MongoTemplate template = new MongoTemplate(mongoDatabaseFactory); + MappingMongoConverter converter = (MappingMongoConverter) template.getConverter(); + converter.setMapKeyDotReplacement("."); + return template; + } + + @Bean + public MongoTransactionManager transactionManager(MongoDatabaseFactory mongoDatabaseFactory) { + MongoTransactionManager mongoTransactionManager = new MongoTransactionManager(); + mongoTransactionManager.setDatabaseFactory(mongoDatabaseFactory); + mongoTransactionManager.afterPropertiesSet(); + return mongoTransactionManager; + } + + @Bean + public Job job(JobRepository jobRepository, MongoTransactionManager transactionManager) { + return new JobBuilder("job", jobRepository) + .start(new StepBuilder("step1", jobRepository) + .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager) + .build()) + .next(new StepBuilder("step2", jobRepository) + .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager) + .build()) + .build(); + } + + } + +} From 4b23f564cd206dd47d09955632da301fff1b0441 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 10 Oct 2024 18:59:10 +0200 Subject: [PATCH 092/152] Add ability to customize the job parameters converter in the default batch configuration Before this commit, it was impossible to customize the job parameters converter without overriding the entire`jobOperator()` method. This commit makes it possible to override `getJobParametersConverter()` or define a bean of type `JobParametersConverter` to customize the job parameters converter used by the job operator. Resolves #4650 Signed-off-by: Fabrice Bibonne --- .../annotation/BatchRegistrar.java | 6 +++ .../annotation/EnableBatchProcessing.java | 8 +++ .../support/DefaultBatchConfiguration.java | 12 +++++ .../annotation/BatchRegistrarTests.java | 52 +++++++++++++++++++ 4 files changed, 78 insertions(+) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java index 5a680aaa31..d261384ef0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/BatchRegistrar.java @@ -257,6 +257,12 @@ private void registerJobOperator(BeanDefinitionRegistry registry, EnableBatchPro beanDefinitionBuilder.addPropertyReference("jobExplorer", "jobExplorer"); beanDefinitionBuilder.addPropertyReference("jobRegistry", "jobRegistry"); + // set optional properties + String jobParametersConverterRef = batchAnnotation.jobParametersConverterRef(); + if (registry.containsBeanDefinition(jobParametersConverterRef)) { + beanDefinitionBuilder.addPropertyReference("jobParametersConverter", jobParametersConverterRef); + } + registry.registerBeanDefinition("jobOperator", beanDefinitionBuilder.getBeanDefinition()); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java index c4fb3be8d7..44803ad281 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java @@ -28,6 +28,7 @@ import org.springframework.batch.core.configuration.support.ApplicationContextFactory; import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; import org.springframework.batch.core.configuration.support.ScopeConfiguration; +import org.springframework.batch.core.converter.JobParametersConverter; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.support.TaskExecutorJobLauncher; import org.springframework.batch.core.repository.JobRepository; @@ -272,4 +273,11 @@ */ String conversionServiceRef() default "conversionService"; + /** + * Set the {@link JobParametersConverter} to use in the job operator. + * @return the bean name of the job parameters converter to use. Defaults to + * {@literal jobParametersConverter} + */ + String jobParametersConverterRef() default "jobParametersConverter"; + } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java index 17cf0e391a..3f96088938 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java @@ -27,6 +27,8 @@ import org.springframework.batch.core.configuration.BatchConfigurationException; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.converter.DateToStringConverter; +import org.springframework.batch.core.converter.DefaultJobParametersConverter; +import org.springframework.batch.core.converter.JobParametersConverter; import org.springframework.batch.core.converter.LocalDateTimeToStringConverter; import org.springframework.batch.core.converter.LocalDateToStringConverter; import org.springframework.batch.core.converter.LocalTimeToStringConverter; @@ -237,6 +239,7 @@ public JobOperator jobOperator(JobRepository jobRepository, JobExplorer jobExplo jobOperatorFactoryBean.setJobExplorer(jobExplorer); jobOperatorFactoryBean.setJobRegistry(jobRegistry); jobOperatorFactoryBean.setJobLauncher(jobLauncher); + jobOperatorFactoryBean.setJobParametersConverter(getJobParametersConverter()); try { jobOperatorFactoryBean.afterPropertiesSet(); return jobOperatorFactoryBean.getObject(); @@ -465,6 +468,15 @@ protected TaskExecutor getTaskExecutor() { return new SyncTaskExecutor(); } + /** + * Return the {@link JobParametersConverter} to use in the job operator. Defaults to + * {@link DefaultJobParametersConverter} + * @return the {@link JobParametersConverter} to use in the job operator. + */ + protected JobParametersConverter getJobParametersConverter() { + return new DefaultJobParametersConverter(); + } + /** * Return the conversion service to use in the job repository and job explorer. This * service is used to convert job parameters from String literal to typed values and diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java index eb5fc3f2ba..c8ce09889a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/annotation/BatchRegistrarTests.java @@ -28,6 +28,9 @@ import org.springframework.batch.core.JobKeyGenerator; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.support.JobRegistrySmartInitializingSingleton; +import org.springframework.batch.core.converter.DefaultJobParametersConverter; +import org.springframework.batch.core.converter.JobParametersConverter; +import org.springframework.batch.core.converter.JsonJobParametersConverter; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.batch.core.launch.JobOperator; @@ -204,6 +207,31 @@ public void testCustomJobKeyGeneratorConfiguration() { jobKeyGenerator.getClass()); } + @Test + @DisplayName("When no JobParametersConverter is provided the default implementation should be used") + public void testDefaultJobParametersConverterConfiguration() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JobConfiguration.class); + + JobOperator jobOperator = context.getBean(JobOperator.class); + JobParametersConverter jobParametersConverter = (JobParametersConverter) ReflectionTestUtils + .getField(jobOperator, "jobParametersConverter"); + + Assertions.assertEquals(DefaultJobParametersConverter.class, jobParametersConverter.getClass()); + } + + @Test + @DisplayName("When a custom JobParametersConverter implementation is found then it should be used") + public void testCustomJobParametersConverterConfiguration() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( + CustomJobParametersConverterConfiguration.class); + + JobOperator jobOperator = context.getBean(JobOperator.class); + JobParametersConverter jobParametersConverter = (JobParametersConverter) ReflectionTestUtils + .getField(jobOperator, "jobParametersConverter"); + + Assertions.assertEquals(JsonJobParametersConverter.class, jobParametersConverter.getClass()); + } + @Configuration @EnableBatchProcessing public static class JobConfigurationWithoutDataSource { @@ -328,6 +356,30 @@ public String generateKey(Object source) { } + @Configuration + @EnableBatchProcessing + public static class CustomJobParametersConverterConfiguration { + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL) + .addScript("/org/springframework/batch/core/schema-hsqldb.sql") + .generateUniqueName(true) + .build(); + } + + @Bean + public JdbcTransactionManager transactionManager(DataSource dataSource) { + return new JdbcTransactionManager(dataSource); + } + + @Bean + public JobParametersConverter jobParametersConverter() { + return new JsonJobParametersConverter(); + } + + } + private PlatformTransactionManager getTransactionManagerSetOnJobRepository(JobRepository jobRepository) { Advised target = (Advised) jobRepository; // proxy created by // AbstractJobRepositoryFactoryBean From 42fdad023caf7126fa546099678e25852b712a9f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 10 Oct 2024 23:24:11 +0200 Subject: [PATCH 093/152] Deprecate public APIs related to LobHandler Resolves #4588 Signed-off-by: Fabrice Bibonne --- .../core/configuration/annotation/EnableBatchProcessing.java | 2 ++ .../configuration/support/DefaultBatchConfiguration.java | 3 ++- .../batch/core/explore/support/JobExplorerFactoryBean.java | 4 +++- .../batch/core/repository/dao/JdbcExecutionContextDao.java | 5 +++++ .../core/repository/support/JobRepositoryFactoryBean.java | 5 +++-- .../batch/core/configuration/xml/spring-batch.xsd | 1 + 6 files changed, 16 insertions(+), 4 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java index 44803ad281..27239d36c0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.java @@ -241,7 +241,9 @@ /** * The large object handler to use in job repository and job explorer. * @return the bean name of the lob handler to use. Defaults to {@literal lobHandler}. + * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6 */ + @Deprecated(since = "5.2.0", forRemoval = true) String lobHandlerRef() default "lobHandler"; /** diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java index 3f96088938..365af8487c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java @@ -392,8 +392,9 @@ protected Charset getCharset() { * A special handler for large objects. The default is usually fine, except for some * (usually older) versions of Oracle. * @return the {@link LobHandler} to use - * + * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6 */ + @Deprecated(since = "5.2.0", forRemoval = true) protected LobHandler getLobHandler() { return new DefaultLobHandler(); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBean.java index 4d99c15fa8..9d3e24dae5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/JobExplorerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -143,7 +143,9 @@ public void setJobKeyGenerator(JobKeyGenerator jobKeyGenerator) { * {@code null}, which works for most databases. * @param lobHandler Large object handler for saving an * {@link org.springframework.batch.item.ExecutionContext}. + * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6 */ + @Deprecated(since = "5.2.0", forRemoval = true) public void setLobHandler(LobHandler lobHandler) { this.lobHandler = lobHandler; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java index e59c459390..07915965c0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcExecutionContextDao.java @@ -268,6 +268,11 @@ public void deleteExecutionContext(StepExecution stepExecution) { getJdbcTemplate().update(getQuery(DELETE_STEP_EXECUTION_CONTEXT), stepExecution.getId()); } + /** + * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6 + * @param lobHandler the lob handler to use + */ + @Deprecated(since = "5.2.0", forRemoval = true) public void setLobHandler(LobHandler lobHandler) { this.lobHandler = lobHandler; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java index 2d72203d6f..70b0b54436 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/JobRepositoryFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 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. @@ -129,9 +129,10 @@ public void setSerializer(ExecutionContextSerializer serializer) { * (usually older) versions of Oracle. The default is determined from the data base * type. * @param lobHandler the {@link LobHandler} to set - * + * @deprecated Since 5.2 with no replacement. Scheduled for removal in v6 * @see LobHandler */ + @Deprecated(since = "5.2.0", forRemoval = true) public void setLobHandler(LobHandler lobHandler) { this.lobHandler = lobHandler; } diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd index 9879886658..5cf435ab68 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd @@ -270,6 +270,7 @@ From 6ff34d8beb2ae1ebf6e11a044e9245ab3589321d Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 11 Oct 2024 09:07:05 +0200 Subject: [PATCH 094/152] Add blocking queue item reader and writer Resolves #2350 Resolves #2044 Signed-off-by: Fabrice Bibonne --- .../item/queue/BlockingQueueItemReader.java | 63 ++++++++++++++++ .../item/queue/BlockingQueueItemWriter.java | 49 +++++++++++++ .../BlockingQueueItemReaderBuilder.java | 71 +++++++++++++++++++ .../BlockingQueueItemWriterBuilder.java | 53 ++++++++++++++ .../queue/BlockingQueueItemReaderTests.java | 48 +++++++++++++ .../queue/BlockingQueueItemWriterTests.java | 49 +++++++++++++ .../BlockingQueueItemReaderBuilderTests.java | 55 ++++++++++++++ .../BlockingQueueItemWriterBuilderTests.java | 56 +++++++++++++++ 8 files changed, 444 insertions(+) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/BlockingQueueItemReader.java create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/BlockingQueueItemWriter.java create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/builder/BlockingQueueItemReaderBuilder.java create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/builder/BlockingQueueItemWriterBuilder.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/BlockingQueueItemReaderTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/BlockingQueueItemWriterTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/builder/BlockingQueueItemReaderBuilderTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/builder/BlockingQueueItemWriterBuilderTests.java diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/BlockingQueueItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/BlockingQueueItemReader.java new file mode 100644 index 0000000000..e5e411045b --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/BlockingQueueItemReader.java @@ -0,0 +1,63 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.queue; + +import org.springframework.batch.item.ItemReader; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * This is an {@link ItemReader} that reads items from a {@link BlockingQueue}. It stops + * reading (ie returns {@code null}) if no items are available in the queue after a + * configurable timeout. + * + * @param type of items to read. + * @author Mahmoud Ben Hassine + * @since 5.2.0 + */ +public class BlockingQueueItemReader implements ItemReader { + + private final BlockingQueue queue; + + private long timeout = 1L; + + private TimeUnit timeUnit = TimeUnit.SECONDS; + + /** + * Create a new {@link BlockingQueueItemReader}. + * @param queue the queue to read items from + */ + public BlockingQueueItemReader(BlockingQueue queue) { + this.queue = queue; + } + + /** + * Set the reading timeout and time unit. Defaults to 1 second. + * @param timeout the timeout after which the reader stops reading + * @param timeUnit the unit of the timeout + */ + public void setTimeout(long timeout, TimeUnit timeUnit) { + this.timeout = timeout; + this.timeUnit = timeUnit; + } + + @Override + public T read() throws Exception { + return this.queue.poll(this.timeout, this.timeUnit); + } + +} \ No newline at end of file diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/BlockingQueueItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/BlockingQueueItemWriter.java new file mode 100644 index 0000000000..68a667b001 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/BlockingQueueItemWriter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.queue; + +import org.springframework.batch.item.Chunk; +import org.springframework.batch.item.ItemWriter; + +import java.util.concurrent.BlockingQueue; + +/** + * This is an {@link ItemWriter} that writes items to a {@link BlockingQueue}. + * + * @param type of items to write + * @since 5.2.0 + * @author Mahmoud Ben Hassine + */ +public class BlockingQueueItemWriter implements ItemWriter { + + private final BlockingQueue queue; + + /** + * Create a new {@link BlockingQueueItemWriter}. + * @param queue the queue to write items to + */ + public BlockingQueueItemWriter(BlockingQueue queue) { + this.queue = queue; + } + + @Override + public void write(Chunk items) throws Exception { + for (T item : items) { + this.queue.put(item); + } + } + +} \ No newline at end of file diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/builder/BlockingQueueItemReaderBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/builder/BlockingQueueItemReaderBuilder.java new file mode 100644 index 0000000000..9c305ca04f --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/builder/BlockingQueueItemReaderBuilder.java @@ -0,0 +1,71 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.queue.builder; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.springframework.batch.item.queue.BlockingQueueItemReader; +import org.springframework.util.Assert; + +/** + * Builder for {@link BlockingQueueItemReader}. + * + * @param type of items to read + * @since 5.2.0 + * @author Mahmoud Ben Hassine + */ +public class BlockingQueueItemReaderBuilder { + + private BlockingQueue queue; + + private long timeout = 1L; + + private TimeUnit timeUnit = TimeUnit.SECONDS; + + /** + * Set the queue to read items from. + * @param queue the queue to read items from. + * @return this instance of the builder + */ + public BlockingQueueItemReaderBuilder queue(BlockingQueue queue) { + this.queue = queue; + return this; + } + + /** + * Set the reading timeout. Defaults to 1 second. + * @param timeout the reading timeout. + * @return this instance of the builder + */ + public BlockingQueueItemReaderBuilder timeout(long timeout, TimeUnit timeUnit) { + this.timeout = timeout; + this.timeUnit = timeUnit; + return this; + } + + /** + * Create a configured {@link BlockingQueueItemReader}. + * @return a configured {@link BlockingQueueItemReader}. + */ + public BlockingQueueItemReader build() { + Assert.state(this.queue != null, "The blocking queue is required."); + BlockingQueueItemReader blockingQueueItemReader = new BlockingQueueItemReader<>(this.queue); + blockingQueueItemReader.setTimeout(this.timeout, this.timeUnit); + return blockingQueueItemReader; + } + +} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/builder/BlockingQueueItemWriterBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/builder/BlockingQueueItemWriterBuilder.java new file mode 100644 index 0000000000..6e7fe772bd --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/queue/builder/BlockingQueueItemWriterBuilder.java @@ -0,0 +1,53 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.queue.builder; + +import java.util.concurrent.BlockingQueue; + +import org.springframework.batch.item.queue.BlockingQueueItemWriter; +import org.springframework.util.Assert; + +/** + * Builder for a {@link BlockingQueueItemWriter}. + * + * @param type of items to write + * @since 5.2.0 + * @author Mahmoud Ben Hassine + */ +public class BlockingQueueItemWriterBuilder { + + private BlockingQueue queue; + + /** + * Create a new {@link BlockingQueueItemWriterBuilder} + * @param queue the queue to write items to + * @return this instance of the builder + */ + public BlockingQueueItemWriterBuilder queue(BlockingQueue queue) { + this.queue = queue; + return this; + } + + /** + * Create a configured {@link BlockingQueueItemWriter}. + * @return a configured {@link BlockingQueueItemWriter}. + */ + public BlockingQueueItemWriter build() { + Assert.state(this.queue != null, "The blocking queue is required."); + return new BlockingQueueItemWriter<>(this.queue); + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/BlockingQueueItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/BlockingQueueItemReaderTests.java new file mode 100644 index 0000000000..5806e576e3 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/BlockingQueueItemReaderTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.queue; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.springframework.batch.item.queue.builder.BlockingQueueItemReaderBuilder; + +/** + * Test class for {@link BlockingQueueItemReader}. + * + * @author Mahmoud Ben Hassine + */ +class BlockingQueueItemReaderTests { + + @Test + void testRead() throws Exception { + // given + BlockingQueue queue = new ArrayBlockingQueue<>(10); + queue.put("foo"); + BlockingQueueItemReader reader = new BlockingQueueItemReaderBuilder().queue(queue) + .timeout(10, TimeUnit.MILLISECONDS) + .build(); + + // when & then + Assertions.assertEquals("foo", reader.read()); + Assertions.assertNull(reader.read()); + } + +} \ No newline at end of file diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/BlockingQueueItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/BlockingQueueItemWriterTests.java new file mode 100644 index 0000000000..cfd47b26f7 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/BlockingQueueItemWriterTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.queue; + +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import org.junit.jupiter.api.Test; + +import org.springframework.batch.item.Chunk; +import org.springframework.batch.item.queue.builder.BlockingQueueItemWriterBuilder; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test class for {@link BlockingQueueItemWriter}. + * + * @author Mahmoud Ben Hassine + */ +class BlockingQueueItemWriterTests { + + @Test + void testWrite() throws Exception { + // given + BlockingQueue queue = new ArrayBlockingQueue<>(10); + BlockingQueueItemWriter writer = new BlockingQueueItemWriterBuilder().queue(queue).build(); + + // when + writer.write(Chunk.of("foo", "bar")); + + // then + assertTrue(queue.containsAll(List.of("foo", "bar"))); + } + +} \ No newline at end of file diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/builder/BlockingQueueItemReaderBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/builder/BlockingQueueItemReaderBuilderTests.java new file mode 100644 index 0000000000..1676e5051c --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/builder/BlockingQueueItemReaderBuilderTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.queue.builder; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import org.junit.jupiter.api.Test; + +import org.springframework.batch.item.queue.BlockingQueueItemReader; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Test class for {@link BlockingQueueItemReaderBuilder}. + * + * @author Mahmoud Ben Hassine + */ +class BlockingQueueItemReaderBuilderTests { + + @Test + void testMandatoryQueue() { + assertThrows(IllegalStateException.class, () -> new BlockingQueueItemReaderBuilder().build()); + } + + @Test + void testBuildReader() { + // given + BlockingQueue queue = new ArrayBlockingQueue<>(5); + + // when + BlockingQueueItemReader reader = new BlockingQueueItemReaderBuilder().queue(queue).build(); + + // then + assertNotNull(reader); + assertEquals(queue, ReflectionTestUtils.getField(reader, "queue")); + } + +} \ No newline at end of file diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/builder/BlockingQueueItemWriterBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/builder/BlockingQueueItemWriterBuilderTests.java new file mode 100644 index 0000000000..6a8eec4cd8 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/queue/builder/BlockingQueueItemWriterBuilderTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.queue.builder; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import org.junit.jupiter.api.Test; + +import org.springframework.batch.item.queue.BlockingQueueItemReader; +import org.springframework.batch.item.queue.BlockingQueueItemWriter; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Test class for {@link BlockingQueueItemWriterBuilder}. + * + * @author Mahmoud Ben Hassine + */ +class BlockingQueueItemWriterBuilderTests { + + @Test + void testMandatoryQueue() { + assertThrows(IllegalStateException.class, () -> new BlockingQueueItemWriterBuilder().build()); + } + + @Test + void testBuildWriter() { + // given + BlockingQueue queue = new ArrayBlockingQueue<>(5); + + // when + BlockingQueueItemWriter writer = new BlockingQueueItemWriterBuilder().queue(queue).build(); + + // then + assertNotNull(writer); + assertEquals(queue, ReflectionTestUtils.getField(writer, "queue")); + } + +} \ No newline at end of file From 843d1f446249a3bf8599e3ae5c5c325a68b923cd Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 11 Oct 2024 15:06:17 +0200 Subject: [PATCH 095/152] Fix dependencies Signed-off-by: Fabrice Bibonne --- pom.xml | 2 +- spring-batch-core/pom.xml | 4 ++++ spring-batch-infrastructure/pom.xml | 4 ++++ spring-batch-integration/pom.xml | 6 ++++++ spring-batch-samples/pom.xml | 20 ++++++++++++++++++++ 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 94c4409cd8..6c45bb0b31 100644 --- a/pom.xml +++ b/pom.xml @@ -85,7 +85,7 @@ 3.1.0 3.1.0 4.0.11 - 5.1.4 + 5.2.0 5.11.1 diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 031433483a..1c225e6056 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -110,6 +110,10 @@ org.mongodb mongodb-driver-sync + + org.springframework + spring-expression + diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index ba86d44878..1247403e00 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -183,6 +183,10 @@ org.slf4j slf4j-api + + org.springframework + spring-expression + diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index d02bb86b6e..bfc85952bd 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -32,6 +32,12 @@ org.springframework.integration spring-integration-core ${spring-integration.version} + + + org.springframework.retry + spring-retry + + org.springframework diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 49c229c025..57f9ef2a10 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -120,6 +120,20 @@ org.springframework.data spring-data-mongodb ${spring-data-mongodb.version} + + + org.slf4j + slf4j-api + + + org.mongodb + mongodb-driver-sync + + + org.springframework + spring-expression + + org.springframework.data @@ -130,6 +144,12 @@ org.springframework.amqp spring-amqp ${spring-amqp.version} + + + org.springframework.retry + spring-retry + + org.springframework.amqp From 20e79c52c20f1eff45aa500df438efedda1813eb Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 11 Oct 2024 18:49:17 +0200 Subject: [PATCH 096/152] Add initial support for a resourceless job repository Resolves #4679 Signed-off-by: Fabrice Bibonne --- .../support/ResourcelessJobRepository.java | 128 ++++++++++++++++++ .../ResourcelessJobRepositoryTests.java | 90 ++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/ResourcelessJobRepositoryTests.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java new file mode 100644 index 0000000000..9fb6b33dd8 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/ResourcelessJobRepository.java @@ -0,0 +1,128 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.support; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +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.JobRepository; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; + +/** + * A {@link JobRepository} implementation that does not use or store batch meta-data. It + * is intended for use-cases where restartability is not required and where the execution + * context is not involved in any way (like sharing data between steps through the + * execution context, or partitioned steps where partitions meta-data is shared between + * the manager and workers through the execution context, etc).
    + * This implementation holds a single job instance and a corresponding job execution that + * are suitable for one-time jobs executed in their own JVM. This job repository works + * with transactional steps as well as non-transactional steps (in which case, a + * {@link ResourcelessTransactionManager} can be used).
    + * This implementation is not thread-safe and should not be used in any concurrent + * environment. + * + * @since 5.2.0 + * @author Mahmoud Ben Hassine + */ +public class ResourcelessJobRepository implements JobRepository { + + private JobInstance jobInstance; + + private JobExecution jobExecution; + + @Override + public boolean isJobInstanceExists(String jobName, JobParameters jobParameters) { + return false; + } + + @Override + public JobInstance createJobInstance(String jobName, JobParameters jobParameters) { + this.jobInstance = new JobInstance(1L, jobName); + return this.jobInstance; + } + + @Override + public JobExecution createJobExecution(String jobName, JobParameters jobParameters) { + if (this.jobInstance == null) { + createJobInstance(jobName, jobParameters); + } + this.jobExecution = new JobExecution(this.jobInstance, 1L, jobParameters); + return this.jobExecution; + } + + @Override + public void update(JobExecution jobExecution) { + jobExecution.setLastUpdated(LocalDateTime.now()); + this.jobExecution = jobExecution; + } + + @Override + public void add(StepExecution stepExecution) { + this.addAll(Collections.singletonList(stepExecution)); + } + + @Override + public void addAll(Collection stepExecutions) { + this.jobExecution.addStepExecutions(new ArrayList<>(stepExecutions)); + } + + @Override + public void update(StepExecution stepExecution) { + stepExecution.setLastUpdated(LocalDateTime.now()); + if (this.jobExecution.isStopping()) { + stepExecution.setTerminateOnly(); + } + } + + @Override + public void updateExecutionContext(StepExecution stepExecution) { + stepExecution.setLastUpdated(LocalDateTime.now()); + } + + @Override + public void updateExecutionContext(JobExecution jobExecution) { + jobExecution.setLastUpdated(LocalDateTime.now()); + } + + @Override + public StepExecution getLastStepExecution(JobInstance jobInstance, String stepName) { + return this.jobExecution.getStepExecutions() + .stream() + .filter(stepExecution -> stepExecution.getStepName().equals(stepName)) + .findFirst() + .orElse(null); + } + + @Override + public long getStepExecutionCount(JobInstance jobInstance, String stepName) { + return this.jobExecution.getStepExecutions() + .stream() + .filter(stepExecution -> stepExecution.getStepName().equals(stepName)) + .count(); + } + + @Override + public JobExecution getLastJobExecution(String jobName, JobParameters jobParameters) { + return this.jobExecution; + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/ResourcelessJobRepositoryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/ResourcelessJobRepositoryTests.java new file mode 100644 index 0000000000..9e5f6d6386 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/ResourcelessJobRepositoryTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.support; + +import org.junit.jupiter.api.Test; + +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobInstance; +import org.springframework.batch.core.JobParameters; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Test class for {@link ResourcelessJobRepository}. + * + * @author Mahmoud Ben Hassine + */ +class ResourcelessJobRepositoryTests { + + private final ResourcelessJobRepository jobRepository = new ResourcelessJobRepository(); + + @Test + void isJobInstanceExists() { + assertFalse(this.jobRepository.isJobInstanceExists("job", new JobParameters())); + } + + @Test + void createJobInstance() { + // given + String jobName = "job"; + JobParameters jobParameters = new JobParameters(); + + // when + JobInstance jobInstance = this.jobRepository.createJobInstance(jobName, jobParameters); + + // then + assertNotNull(jobInstance); + assertEquals(jobName, jobInstance.getJobName()); + assertEquals(1L, jobInstance.getInstanceId()); + } + + @Test + void createJobExecution() { + // given + String jobName = "job"; + JobParameters jobParameters = new JobParameters(); + + // when + JobExecution jobExecution = this.jobRepository.createJobExecution(jobName, jobParameters); + + // then + assertNotNull(jobExecution); + assertEquals(1L, jobExecution.getId()); + assertEquals(jobName, jobExecution.getJobInstance().getJobName()); + assertEquals(1L, jobExecution.getJobInstance().getInstanceId()); + } + + @Test + void getLastJobExecution() { + // given + String jobName = "job"; + JobParameters jobParameters = new JobParameters(); + this.jobRepository.createJobExecution(jobName, jobParameters); + + // when + JobExecution jobExecution = this.jobRepository.getLastJobExecution(jobName, jobParameters); + + // then + assertNotNull(jobExecution); + assertEquals(1L, jobExecution.getId()); + assertEquals(jobName, jobExecution.getJobInstance().getJobName()); + assertEquals(1L, jobExecution.getJobInstance().getInstanceId()); + } + +} \ No newline at end of file From 5a5b93ba893dbd7c4498880138af0570d776792c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 11 Oct 2024 18:51:16 +0200 Subject: [PATCH 097/152] Deprecate SystemPropertyInitializer Resolves #4680 Signed-off-by: Fabrice Bibonne --- .../batch/support/SystemPropertyInitializer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SystemPropertyInitializer.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SystemPropertyInitializer.java index 08e96b2eb1..ea2ff9bacf 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SystemPropertyInitializer.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SystemPropertyInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2007 the original author or authors. + * Copyright 2006-2024 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. @@ -24,8 +24,9 @@ * exists it is not changed). * * @author Dave Syer - * + * @deprecated since 5.2 with no replacement. */ +@Deprecated(since = "5.2.0", forRemoval = true) public class SystemPropertyInitializer implements InitializingBean { /** From faf71e2e8b7d9ae7885d3816b3afd6d5f994911c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 11 Oct 2024 21:58:00 +0200 Subject: [PATCH 098/152] Fix Spring data MongoDB dependencies Signed-off-by: Fabrice Bibonne --- pom.xml | 2 +- spring-batch-core/pom.xml | 21 ++++++++++++++++++++- spring-batch-infrastructure/pom.xml | 20 +++++++++++++++++++- spring-batch-samples/pom.xml | 20 +++++++++++++++++++- 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 6c45bb0b31..4c33cb1181 100644 --- a/pom.xml +++ b/pom.xml @@ -85,7 +85,7 @@ 3.1.0 3.1.0 4.0.11 - 5.2.0 + 5.2.0 5.11.1 diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 1c225e6056..6ef5cfa1a6 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -106,6 +106,10 @@ org.slf4j slf4j-api + + org.mongodb + mongodb-driver-core + org.mongodb mongodb-driver-sync @@ -114,12 +118,27 @@ org.springframework spring-expression + + org.springframework.data + spring-data-commons +
    + + org.springframework.data + spring-data-commons + ${spring-data-commons.version} + + + org.mongodb + mongodb-driver-core + ${mongodb-driver.version} + true + org.mongodb mongodb-driver-sync - ${mongodb-driver-sync.version} + ${mongodb-driver.version} true diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index 1247403e00..f980ee0bd1 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -171,6 +171,18 @@ org.slf4j slf4j-api + + org.springframework.data + spring-data-commons + + + org.mongodb + mongodb-driver-core + + + org.mongodb + mongodb-driver-sync + @@ -201,10 +213,16 @@ + + org.mongodb + mongodb-driver-core + ${mongodb-driver.version} + true + org.mongodb mongodb-driver-sync - ${mongodb-driver-sync.version} + ${mongodb-driver.version} true diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 57f9ef2a10..9d433e897b 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -125,6 +125,10 @@ org.slf4j slf4j-api + + org.mongodb + mongodb-driver-core + org.mongodb mongodb-driver-sync @@ -133,8 +137,17 @@ org.springframework spring-expression + + org.springframework.data + spring-data-commons + + + org.springframework.data + spring-data-commons + ${spring-data-commons.version} + org.springframework.data spring-data-jpa @@ -181,10 +194,15 @@ jakarta.el ${jakarta.el.version} + + org.mongodb + mongodb-driver-core + ${mongodb-driver.version} + org.mongodb mongodb-driver-sync - ${mongodb-driver-sync.version} + ${mongodb-driver.version} io.prometheus From 50f0a515e274f7b1fa1ffff7aa896e1b4f2cdaba Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sat, 12 Oct 2024 00:30:08 +0200 Subject: [PATCH 099/152] Update What's new section for 5.2.0-M2 Signed-off-by: Fabrice Bibonne --- .../modules/ROOT/pages/whatsnew.adoc | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc index f88f68462f..d472af579f 100644 --- a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc +++ b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc @@ -6,6 +6,11 @@ This section highlights the major changes in Spring Batch 5.2. For the complete Spring Batch 5.2 includes the following features: * xref:whatsnew.adoc#dependencies-upgrade[Dependencies upgrade] +* xref:whatsnew.adoc#mongodb-job-repository-support[MongoDB job repository support] +* xref:whatsnew.adoc#new-resourceless-job-repository[New resourceless job repository] +* xref:whatsnew.adoc#composite-item-reader-implementation[Composite Item Reader implementation] +* xref:whatsnew.adoc#new-adapters-for-java-util-function-apis[New adapters for java.util.function APIs] +* xref:whatsnew.adoc#concurrent-steps-with-blocking-queue-item-reader-and-writer[Concurrent steps with blocking queue item reader and writer] * xref:whatsnew.adoc#query-hints-support[Query hints support in JPA item readers] * xref:whatsnew.adoc#data-class-support[Data class support in JDBC item readers] * xref:whatsnew.adoc#configurable-line-separator-in-recursivecollectionlineaggregator[Configurable line separator in RecursiveCollectionLineAggregator] @@ -25,6 +30,111 @@ In this release, the Spring dependencies are upgraded to the following versions: * Spring Kafka 3.3.0 * Micrometer 1.14.0 +[[mongodb-job-repository-support]] +== MongoDB job repository support + +This release introduces the first NoSQL job repository implementation which is backed by MongoDB. +Similar to relational job repository implementations, Spring Batch comes with a script to create the +necessary collections in MongoDB in order to save and retrieve batch meta-data. + +This implementation requires MongoDB version 4 or later and is based on Spring Data MongoDB. +In order to use this job repository, all you need to do is define a `MongoTemplate` and a +`MongoTransactionManager` which are required by the newly added `MongoDBJobRepositoryFactoryBean`: + +``` +@Bean +public JobRepository jobRepository(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) throws Exception { + MongoJobRepositoryFactoryBean jobRepositoryFactoryBean = new MongoJobRepositoryFactoryBean(); + jobRepositoryFactoryBean.setMongoOperations(mongoTemplate); + jobRepositoryFactoryBean.setTransactionManager(transactionManager); + jobRepositoryFactoryBean.afterPropertiesSet(); + return jobRepositoryFactoryBean.getObject(); +} +``` + +Once the MongoDB job repository defined, you can inject it in any job or step as a regular job repository. +You can find a complete example in the https://github.com/spring-projects/spring-batch/blob/main/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java[MongoDBJobRepositoryIntegrationTests]. + +[[new-resourceless-job-repository]] +== New resourceless job repository + +In v5, the in-memory Map-based job repository implementation was removed for several reasons. +The only job repository implementation that was left in Spring Batch was the JDBC implementation, which requires a data source. +While this works well with in-memory databases like H2 or HSQLDB, requiring a data source was a strong constraint +for many users of our community who used to use the Map-based repository without any additional dependency. + +In this release, we introduce a `JobRepository` implementation that does not use or store batch meta-data in any form +(not even in-memory). It is a "NoOp" implementation that throws away batch meta-data and does not interact with any resource +(hence the name "resourceless job repository", which is named after the "resourceless transaction manager"). + +This implementation is intended for use-cases where restartability is not required and where the execution context is not involved +in any way (like sharing data between steps through the execution context, or partitioned steps where partitions meta-data is +shared between the manager and workers through the execution context, etc). + +This implementation is suitable for one-time jobs executed in their own JVM. It works with transactional steps (configured with +a `DataSourceTransactionManager` for instance) as well as non-transactional steps (configured with a `ResourcelessTransactionManager`). +The implementation is not thread-safe and should not be used in any concurrent environment. + +[[composite-item-reader-implementation]] +== Composite Item Reader implementation + +Similar to the `CompositeItemProcessor` and `CompositeItemWriter`, we introduce a new `CompositeItemReader` implementation +that is designed to read data sequentially from several sources having the same format. This is useful when data is spread +over different resources and writing a custom reader is not an option. + +A `CompositeItemReader` works like other composite artifacts, by delegating the reading operation to regular item readers +in order. Here is a quick example showing a composite reader that reads persons data from a flat file then from a database table: + +``` +@Bean +public FlatFileItemReader itemReader1() { + return new FlatFileItemReaderBuilder() + .name("personFileItemReader") + .resource(new FileSystemResource("persons.csv")) + .delimited() + .names("id", "name") + .targetType(Person.class) + .build(); +} + +@Bean +public JdbcCursorItemReader itemReader2() { + String sql = "select * from persons"; + return new JdbcCursorItemReaderBuilder() + .name("personTableItemReader") + .dataSource(dataSource()) + .sql(sql) + .beanRowMapper(Person.class) + .build(); +} + +@Bean +public CompositeItemReader itemReader() { + return new CompositeItemReader<>(Arrays.asList(itemReader1(), itemReader2())); +} +``` + +[[new-adapters-for-java-util-function-apis]] +== New adapters for java.util.function APIs + +Similar to `FucntionItemProcessor` that adapts a `java.util.function.Function` to an item processor, this release +introduces several new adapters for other `java.util.function` interfaces like `Supplier`, `Consumer` and `Predicate`. + +The newly added adapters are: `SupplierItemReader`, `ConsumerItemWriter` and `PredicateFilteringItemProcessor`. +For more details about these new adapters, please refer to the https://github.com/spring-projects/spring-batch/tree/main/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/function[org.springframework.batch.item.function] package. + +[[concurrent-steps-with-blocking-queue-item-reader-and-writer]] +== Concurrent steps with blocking queue item reader and writer + +The https://en.wikipedia.org/wiki/Staged_event-driven_architecture[staged event-driven architecture] (SEDA) is a +powerful architecture style to process data in stages connected by queues. This style is directly applicable to data +pipelines and easily implemented in Spring Batch thanks to the ability to design jobs as a sequence of steps. + +The only missing piece here is how to read and write data to intermediate queues. This release introduces an item reader +and item writer to read data from and write it to a `BlockingQueue`. With these two new classes, one can design a first step +that prepares data in a queue and a second step that consumes data from the same queue. This way, both steps can run concurrently +to process data efficiently in a non-blocking, event-driven fashion. + [[query-hints-support]] == Query hints support in JPA item readers From e221b9b68af612e067d76354cd12054e4851578c Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sat, 12 Oct 2024 00:31:40 +0200 Subject: [PATCH 100/152] Prepare release 5.2.0-M2 Signed-off-by: Fabrice Bibonne --- pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 4c33cb1181..80679d8864 100644 --- a/pom.xml +++ b/pom.xml @@ -61,19 +61,19 @@ 17 - 6.2.0-SNAPSHOT - 2.0.10-SNAPSHOT - 6.4.0-SNAPSHOT - 1.14.0-SNAPSHOT + 6.2.0-RC1 + 2.0.9 + 6.4.0-M3 + 1.14.0-M3 - 3.4.0-SNAPSHOT - 3.4.0-SNAPSHOT - 3.4.0-SNAPSHOT - 4.4.0-SNAPSHOT - 3.3.0-SNAPSHOT - 3.2.0-SNAPSHOT - 3.2.7-SNAPSHOT + 3.4.0-M1 + 3.4.0-M1 + 3.4.0-M1 + 4.4.0-M1 + 3.3.0-M3 + 3.2.0-M3 + 3.2.6 2.18.0 1.12.0 @@ -92,7 +92,7 @@ 3.0.2 - 1.4.0-SNAPSHOT + 1.4.0-M3 1.4.20 4.13.2 From 3ff745648b397b2b7cbf4af6353fcb8ed65c81b7 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sat, 12 Oct 2024 00:46:40 +0200 Subject: [PATCH 101/152] Release version 5.2.0-M2 Signed-off-by: Fabrice Bibonne --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 80679d8864..47ee147a07 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.0-SNAPSHOT + 5.2.0-M2 pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index 7ad96c8a70..ea12c3c9aa 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M2 spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 6ef5cfa1a6..bf804530b2 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M2 spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index c0e835ea10..cb4ed9387d 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M2 spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index f980ee0bd1..2f924ba9d6 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M2 spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index bfc85952bd..121eaeb8fc 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M2 spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 9d433e897b..250a969e0c 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M2 spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 15bbf21f7b..9aefd49260 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-M2 spring-batch-test Spring Batch Test From 764bfd49c3e0b299ef6325e0eb264c505d5b3aa0 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sat, 12 Oct 2024 00:47:02 +0200 Subject: [PATCH 102/152] Next development version Signed-off-by: Fabrice Bibonne --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 47ee147a07..80679d8864 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.0-M2 + 5.2.0-SNAPSHOT pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index ea12c3c9aa..7ad96c8a70 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index bf804530b2..6ef5cfa1a6 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index cb4ed9387d..c0e835ea10 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index 2f924ba9d6..f980ee0bd1 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index 121eaeb8fc..bfc85952bd 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 250a969e0c..9d433e897b 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 9aefd49260..15bbf21f7b 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-M2 + 5.2.0-SNAPSHOT spring-batch-test Spring Batch Test From 12e23ff8542800e325c34d8cd8922ef465921d65 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sat, 12 Oct 2024 01:31:48 +0200 Subject: [PATCH 103/152] Update Spring dependencies to latest snapshots Signed-off-by: Fabrice Bibonne --- pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 80679d8864..4c33cb1181 100644 --- a/pom.xml +++ b/pom.xml @@ -61,19 +61,19 @@ 17 - 6.2.0-RC1 - 2.0.9 - 6.4.0-M3 - 1.14.0-M3 + 6.2.0-SNAPSHOT + 2.0.10-SNAPSHOT + 6.4.0-SNAPSHOT + 1.14.0-SNAPSHOT - 3.4.0-M1 - 3.4.0-M1 - 3.4.0-M1 - 4.4.0-M1 - 3.3.0-M3 - 3.2.0-M3 - 3.2.6 + 3.4.0-SNAPSHOT + 3.4.0-SNAPSHOT + 3.4.0-SNAPSHOT + 4.4.0-SNAPSHOT + 3.3.0-SNAPSHOT + 3.2.0-SNAPSHOT + 3.2.7-SNAPSHOT 2.18.0 1.12.0 @@ -92,7 +92,7 @@ 3.0.2 - 1.4.0-M3 + 1.4.0-SNAPSHOT 1.4.20 4.13.2 From 9a48788661c181c3514688db5ac1b0d9eb4de115 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sat, 12 Oct 2024 01:42:56 +0200 Subject: [PATCH 104/152] Add latest news in README.md Signed-off-by: Fabrice Bibonne --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3dd6c56f63..857ade356a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# Latest news: [Spring Batch 5.2.0-M2 is available now!](https://spring.io/blog/2024/10/11/spring-batch-5-2-0-m2-is-available-now) + # Spring Batch [![build status](https://github.com/spring-projects/spring-batch/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/spring-projects/spring-batch/actions/workflows/continuous-integration.yml) From de9619e2d4f4e8e7ae9d4b5c4d44736510bdec34 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 22 Oct 2024 11:22:39 +0200 Subject: [PATCH 105/152] Fix last step execution retrieval in MongoStepExecutionDao Resolves https://github.com/spring-projects-experimental/spring-batch-experimental/issues/3 Signed-off-by: Fabrice Bibonne --- .../batch/core/repository/dao/MongoStepExecutionDao.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java index 44215babd7..9b889c1d81 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java @@ -111,6 +111,7 @@ public StepExecution getLastStepExecution(JobInstance jobInstance, String stepNa // first one Optional lastStepExecution = stepExecutions .stream() + .filter(stepExecution -> stepExecution.getName().equals(stepName)) .min(Comparator .comparing(org.springframework.batch.core.repository.persistence.StepExecution::getCreateTime) .thenComparing(org.springframework.batch.core.repository.persistence.StepExecution::getId)); From 20d98078fab7f7b1ec6e21c890c19eca0c9037da Mon Sep 17 00:00:00 2001 From: hyejinggu <118355536+hyejinggu@users.noreply.github.com> Date: Sat, 5 Oct 2024 18:52:24 +0900 Subject: [PATCH 106/152] Fix column types in JdbcStepExecutionDao Resolves #4648 Signed-off-by: Fabrice Bibonne --- .../repository/dao/JdbcStepExecutionDao.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java index 594e2e7eef..5782dd2277 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java @@ -281,9 +281,9 @@ public void updateStepExecution(StepExecution stepExecution) { stepExecution.getWriteSkipCount(), stepExecution.getRollbackCount(), lastUpdated, stepExecution.getId(), stepExecution.getVersion() }; int count = getJdbcTemplate().update(getQuery(UPDATE_STEP_EXECUTION), parameters, - new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.INTEGER, Types.INTEGER, - Types.INTEGER, Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.INTEGER, - Types.INTEGER, Types.INTEGER, Types.INTEGER, Types.TIMESTAMP, Types.BIGINT, + new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.BIGINT, Types.BIGINT, + Types.BIGINT, Types.BIGINT, Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.BIGINT, + Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.TIMESTAMP, Types.BIGINT, Types.INTEGER }); // Avoid concurrent modifications... @@ -396,15 +396,15 @@ public StepExecution mapRow(ResultSet rs, int rowNum) throws SQLException { stepExecution.setStartTime(rs.getTimestamp(3) == null ? null : rs.getTimestamp(3).toLocalDateTime()); stepExecution.setEndTime(rs.getTimestamp(4) == null ? null : rs.getTimestamp(4).toLocalDateTime()); stepExecution.setStatus(BatchStatus.valueOf(rs.getString(5))); - stepExecution.setCommitCount(rs.getInt(6)); - stepExecution.setReadCount(rs.getInt(7)); - stepExecution.setFilterCount(rs.getInt(8)); - stepExecution.setWriteCount(rs.getInt(9)); + stepExecution.setCommitCount(rs.getLong(6)); + stepExecution.setReadCount(rs.getLong(7)); + stepExecution.setFilterCount(rs.getLong(8)); + stepExecution.setWriteCount(rs.getLong(9)); stepExecution.setExitStatus(new ExitStatus(rs.getString(10), rs.getString(11))); - stepExecution.setReadSkipCount(rs.getInt(12)); - stepExecution.setWriteSkipCount(rs.getInt(13)); - stepExecution.setProcessSkipCount(rs.getInt(14)); - stepExecution.setRollbackCount(rs.getInt(15)); + stepExecution.setReadSkipCount(rs.getLong(12)); + stepExecution.setWriteSkipCount(rs.getLong(13)); + stepExecution.setProcessSkipCount(rs.getLong(14)); + stepExecution.setRollbackCount(rs.getLong(15)); stepExecution.setLastUpdated(rs.getTimestamp(16) == null ? null : rs.getTimestamp(16).toLocalDateTime()); stepExecution.setVersion(rs.getInt(17)); stepExecution.setCreateTime(rs.getTimestamp(18) == null ? null : rs.getTimestamp(18).toLocalDateTime()); From cdcc087b2bfdfe97aa035e20aceb2f4d32dfaf1b Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 22 Oct 2024 22:48:00 +0200 Subject: [PATCH 107/152] Fix code formatting Signed-off-by: Fabrice Bibonne --- .../batch/core/repository/dao/JdbcStepExecutionDao.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java index 5782dd2277..b1e46e0c23 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java @@ -283,8 +283,7 @@ public void updateStepExecution(StepExecution stepExecution) { int count = getJdbcTemplate().update(getQuery(UPDATE_STEP_EXECUTION), parameters, new int[] { Types.TIMESTAMP, Types.TIMESTAMP, Types.VARCHAR, Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.VARCHAR, Types.VARCHAR, Types.INTEGER, Types.BIGINT, - Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.TIMESTAMP, Types.BIGINT, - Types.INTEGER }); + Types.BIGINT, Types.BIGINT, Types.BIGINT, Types.TIMESTAMP, Types.BIGINT, Types.INTEGER }); // Avoid concurrent modifications... if (count == 0) { From 50ead233bbb62a35ce052ee1930bc30a6728a2be Mon Sep 17 00:00:00 2001 From: pxzxj Date: Wed, 4 Sep 2024 15:39:37 +0800 Subject: [PATCH 108/152] Fix typo in word AsynchItemWriter Resolves #4649 Signed-off-by: Fabrice Bibonne --- .../ROOT/pages/spring-batch-integration/sub-elements.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-batch-docs/modules/ROOT/pages/spring-batch-integration/sub-elements.adoc b/spring-batch-docs/modules/ROOT/pages/spring-batch-integration/sub-elements.adoc index eac14f4e7c..205a8669e2 100644 --- a/spring-batch-docs/modules/ROOT/pages/spring-batch-integration/sub-elements.adoc +++ b/spring-batch-docs/modules/ROOT/pages/spring-batch-integration/sub-elements.adoc @@ -182,7 +182,7 @@ The following example shows the how to add a step-level listener in XML: Asynchronous Processors help you scale the processing of items. In the asynchronous processor use case, an `AsyncItemProcessor` serves as a dispatcher, executing the logic of the `ItemProcessor` for an item on a new thread. Once the item completes, the `Future` is -passed to the `AsynchItemWriter` to be written. +passed to the `AsyncItemWriter` to be written. Therefore, you can increase performance by using asynchronous item processing, basically letting you implement fork-join scenarios. The `AsyncItemWriter` gathers the results and From c6de8755eaef8937420ad898a838d25724f4fa56 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 23 Oct 2024 05:57:09 +0200 Subject: [PATCH 109/152] Clarify the behaviour of MultiResourceItemWriter with regard to file creation Resolves #4645 Signed-off-by: Fabrice Bibonne --- .../batch/item/file/MultiResourceItemWriter.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemWriter.java index 1480cba407..835abb3527 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/MultiResourceItemWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -36,7 +36,11 @@ *

    * Note that new resources are created only at chunk boundaries i.e. the number of items * written into one resource is between the limit set by - * {@link #setItemCountLimitPerResource(int)} and (limit + chunk size). + *

    + * This writer will create an output file only when there are items to write, which means + * there would be no empty file created if no items are passed (for example when all items + * are filtered or skipped during the processing phase). + *

    * * @param item type * @author Robert Kasanicky From 0334300765fef3123bc239a8b55f2ef1d7050929 Mon Sep 17 00:00:00 2001 From: Tobias Berse Date: Mon, 14 Oct 2024 15:13:17 +0200 Subject: [PATCH 110/152] Allow subclasses of items in CompositeItemReader's generics Resolves #https://github.com/spring-projects-experimental/spring-batch-experimental/issues/2 Signed-off-by: Fabrice Bibonne --- .../batch/item/support/CompositeItemReader.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java index e9b5a72d07..06148a346c 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/CompositeItemReader.java @@ -32,17 +32,17 @@ */ public class CompositeItemReader implements ItemStreamReader { - private final List> delegates; + private final List> delegates; - private final Iterator> delegatesIterator; + private final Iterator> delegatesIterator; - private ItemStreamReader currentDelegate; + private ItemStreamReader currentDelegate; /** * Create a new {@link CompositeItemReader}. * @param delegates the delegate readers to read data */ - public CompositeItemReader(List> delegates) { + public CompositeItemReader(List> delegates) { this.delegates = delegates; this.delegatesIterator = this.delegates.iterator(); this.currentDelegate = this.delegatesIterator.hasNext() ? this.delegatesIterator.next() : null; @@ -52,7 +52,7 @@ public CompositeItemReader(List> delegates) { // opening resources early for a long time @Override public void open(ExecutionContext executionContext) throws ItemStreamException { - for (ItemStreamReader delegate : delegates) { + for (ItemStreamReader delegate : delegates) { delegate.open(executionContext); } } @@ -79,7 +79,7 @@ public void update(ExecutionContext executionContext) throws ItemStreamException @Override public void close() throws ItemStreamException { - for (ItemStreamReader delegate : delegates) { + for (ItemStreamReader delegate : delegates) { delegate.close(); } } From 1a6ac18e248295342d6f2e1fda46ad7c7bb13b09 Mon Sep 17 00:00:00 2001 From: Taeik Lim Date: Sun, 9 Jun 2024 22:22:35 +0900 Subject: [PATCH 111/152] Add '@FunctionalInterface' to JobKeyGenerator Resolves #4613 Signed-off-by: Taeik Lim Signed-off-by: Fabrice Bibonne --- .../java/org/springframework/batch/core/JobKeyGenerator.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobKeyGenerator.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobKeyGenerator.java index 589434b97f..147a26a37c 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobKeyGenerator.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobKeyGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2024 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. @@ -21,9 +21,11 @@ * * @author Michael Minella * @author Mahmoud Ben Hassine + * @author Taeik Lim * @param The type of the source data used to calculate the key. * @since 2.2 */ +@FunctionalInterface public interface JobKeyGenerator { /** From a3e13af4bc2a3bcbc093ae2783e19ac3e2b29012 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 23 Oct 2024 19:49:01 +0200 Subject: [PATCH 112/152] Prepare release 5.2.0-RC1 Signed-off-by: Fabrice Bibonne --- pom.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 4c33cb1181..9aed7c47de 100644 --- a/pom.xml +++ b/pom.xml @@ -61,19 +61,19 @@ 17 - 6.2.0-SNAPSHOT - 2.0.10-SNAPSHOT - 6.4.0-SNAPSHOT - 1.14.0-SNAPSHOT + 6.2.0-RC2 + 2.0.10 + 6.4.0-RC1 + 1.14.0-RC1 - 3.4.0-SNAPSHOT - 3.4.0-SNAPSHOT - 3.4.0-SNAPSHOT - 4.4.0-SNAPSHOT - 3.3.0-SNAPSHOT - 3.2.0-SNAPSHOT - 3.2.7-SNAPSHOT + 3.4.0-RC1 + 3.4.0-RC1 + 3.4.0-RC1 + 4.4.0-RC1 + 3.3.0-RC1 + 3.2.0-RC1 + 3.2.7 2.18.0 1.12.0 @@ -92,7 +92,7 @@ 3.0.2 - 1.4.0-SNAPSHOT + 1.4.0-RC1 1.4.20 4.13.2 From f7d05aded2db08b0cc674a96b6c6b0359221ee8e Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 23 Oct 2024 19:54:39 +0200 Subject: [PATCH 113/152] Release version 5.2.0-RC1 Signed-off-by: Fabrice Bibonne --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 9aed7c47de..47229f031d 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.0-SNAPSHOT + 5.2.0-RC1 pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index 7ad96c8a70..c28427da02 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 6ef5cfa1a6..07212da607 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index c0e835ea10..11691e23de 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index f980ee0bd1..cf4b1c7231 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index bfc85952bd..8897577026 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 9d433e897b..c64de58a34 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 15bbf21f7b..a843c3cf50 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0-RC1 spring-batch-test Spring Batch Test From 0dc87546ead82b2e4d43be7c50b12f973498856e Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 23 Oct 2024 19:54:55 +0200 Subject: [PATCH 114/152] Next development version Signed-off-by: Fabrice Bibonne --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 47229f031d..9aed7c47de 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.0-RC1 + 5.2.0-SNAPSHOT pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index c28427da02..7ad96c8a70 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 07212da607..6ef5cfa1a6 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index 11691e23de..c0e835ea10 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index cf4b1c7231..f980ee0bd1 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index 8897577026..bfc85952bd 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index c64de58a34..9d433e897b 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index a843c3cf50..15bbf21f7b 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-RC1 + 5.2.0-SNAPSHOT spring-batch-test Spring Batch Test From 6cf2b8a4757e8bc5ad10fbab0e1161234d7b2efa Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Mon, 28 Oct 2024 14:36:40 +0100 Subject: [PATCH 115/152] Update latest news in README.md Signed-off-by: Fabrice Bibonne --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 857ade356a..ceaece65f9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# Latest news: [Spring Batch 5.2.0-M2 is available now!](https://spring.io/blog/2024/10/11/spring-batch-5-2-0-m2-is-available-now) +# Latest news + +* October 25, 2024: [Spring Batch 5.2.0-RC1 is out!](https://spring.io/blog/2024/10/25/spring-batch-5-2-0-rc1-is-out) +* October 11, 2024: [Spring Batch 5.2.0-M2 is available now!](https://spring.io/blog/2024/10/11/spring-batch-5-2-0-m2-is-available-now) From 3eeaf7e90de02e9bcf39eb160847f81770987abd Mon Sep 17 00:00:00 2001 From: Ian Date: Sun, 22 Sep 2024 15:26:09 +0900 Subject: [PATCH 116/152] Update skipLimit default value to 10 Resolves #4661 Signed-off-by: Fabrice Bibonne --- .../step/builder/FaultTolerantStepBuilder.java | 5 +++-- .../factory/FaultTolerantStepFactoryBean.java | 7 ++++--- .../builder/FaultTolerantStepBuilderTests.java | 15 ++++++++++++++- .../configuring-skip.adoc | 4 ++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java index 635c54550a..d1c91ce237 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java @@ -90,6 +90,7 @@ * @author Chris Schaefer * @author Michael Minella * @author Mahmoud Ben Hassine + * @author Ian Choi * @since 2.2 */ public class FaultTolerantStepBuilder extends SimpleStepBuilder { @@ -122,7 +123,7 @@ public class FaultTolerantStepBuilder extends SimpleStepBuilder { private final Set> skipListeners = new LinkedHashSet<>(); - private int skipLimit = 0; + private int skipLimit = 10; private SkipPolicy skipPolicy; @@ -306,7 +307,7 @@ public FaultTolerantStepBuilder retryContextCache(RetryContextCache retryC /** * Sets the maximum number of failed items to skip before the step fails. Ignored if * an explicit {@link #skipPolicy(SkipPolicy)} is provided. - * @param skipLimit the skip limit to set + * @param skipLimit the skip limit to set. Default is 10. * @return this for fluent chaining */ public FaultTolerantStepBuilder skipLimit(int skipLimit) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java index 48aed1eae5..cec322ccd4 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/factory/FaultTolerantStepFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -47,6 +47,7 @@ * @author Dave Syer * @author Robert Kasanicky * @author Morten Andersen-Gott + * @author Ian Choi * */ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean { @@ -61,7 +62,7 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean stepBuilder = new FaultTolerantStepBuilder<>( + new StepBuilder("step", new DummyJobRepository())); + + Field field = stepBuilder.getClass().getDeclaredField("skipLimit"); + field.setAccessible(true); + int skipLimit = (int) field.get(stepBuilder); + + assertEquals(10, skipLimit); + } + } diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring-skip.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring-skip.adoc index 16a08cb719..5c5136c825 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring-skip.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/configuring-skip.adoc @@ -31,6 +31,8 @@ public Step step1(JobRepository jobRepository, PlatformTransactionManager transa .build(); } ---- ++ +Note: The `skipLimit` can be explicitly set using the `skipLimit()` method. If not specified, the default skip limit is set to 10. XML:: + @@ -91,6 +93,8 @@ public Step step1(JobRepository jobRepository, PlatformTransactionManager transa .build(); } ---- ++ +Note: The `skipLimit` can be explicitly set using the `skipLimit()` method. If not specified, the default skip limit is set to 10. XML:: + From 5cef5d664a1c364d6d84b9fd41d672540a002be6 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 20 Nov 2024 06:29:57 +0100 Subject: [PATCH 117/152] Refine contribution #4668 Before this commit, an assertion was enforcing that when a skip limit is provided, then at least one skippable exception is defined. Since the default value of skip limit was changed to 10 in fd45d322, that assertion is now replaced with a log message at debug level. Related to #4661 Signed-off-by: Fabrice Bibonne --- .../batch/core/step/builder/FaultTolerantStepBuilder.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java index d1c91ce237..e4c24fb3b0 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java @@ -555,8 +555,11 @@ protected SkipPolicy createSkipPolicy() { map.put(ForceRollbackForWriteSkipException.class, true); LimitCheckingItemSkipPolicy limitCheckingItemSkipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, map); if (skipPolicy == null) { - Assert.state(!(skippableExceptionClasses.isEmpty() && skipLimit > 0), - "If a skip limit is provided then skippable exceptions must also be specified"); + if (skippableExceptionClasses.isEmpty() && skipLimit > 0) { + logger.debug(String.format( + "A skip limit of %s is set but no skippable exceptions are defined. Consider defining skippable exceptions.", + skipLimit)); + } skipPolicy = limitCheckingItemSkipPolicy; } else if (limitCheckingItemSkipPolicy != null) { From c8c04d71edbc0c22e8440c471f2343ae99e85b71 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 02:18:34 +0100 Subject: [PATCH 118/152] Optimize sequence handling in MongoDB job repository Signed-off-by: Fabrice Bibonne --- .../dao/MongoSequenceIncrementer.java | 51 ++++--------------- .../batch/core/schema-drop-mongodb.js | 4 +- .../batch/core/schema-mongodb.js | 20 +++++--- .../MongoDBJobRepositoryIntegrationTests.java | 14 ++--- 4 files changed, 33 insertions(+), 56 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java index 683d2ad69e..db78dc343a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoSequenceIncrementer.java @@ -15,20 +15,20 @@ */ package org.springframework.batch.core.repository.dao; +import com.mongodb.client.model.FindOneAndUpdateOptions; +import com.mongodb.client.model.ReturnDocument; +import org.bson.Document; + import org.springframework.dao.DataAccessException; import org.springframework.data.mongodb.core.MongoOperations; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer; -import static org.springframework.data.mongodb.core.query.Criteria.where; -import static org.springframework.data.mongodb.core.query.Query.query; - // Based on https://www.mongodb.com/blog/post/generating-globally-unique-identifiers-for-use-with-mongodb // Section: Use a single counter document to generate unique identifiers one at a time /** * @author Mahmoud Ben Hassine + * @author Christoph Strobl * @since 5.2.0 */ public class MongoSequenceIncrementer implements DataFieldMaxValueIncrementer { @@ -44,13 +44,11 @@ public MongoSequenceIncrementer(MongoOperations mongoTemplate, String sequenceNa @Override public long nextLongValue() throws DataAccessException { - // TODO optimize - MongoSequence sequence = mongoTemplate.findOne(new Query(), MongoSequence.class, sequenceName); - Query query = query(where("_id").is(sequence.getId())); - Update update = new Update().inc("count", 1); - // The following does not return the modified document - mongoTemplate.findAndModify(query, update, MongoSequence.class, sequenceName); - return mongoTemplate.findOne(new Query(), MongoSequence.class, sequenceName).getCount(); + return mongoTemplate.execute("BATCH_SEQUENCES", + collection -> collection + .findOneAndUpdate(new Document("_id", sequenceName), new Document("$inc", new Document("count", 1)), + new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)) + .getLong("count")); } @Override @@ -63,33 +61,4 @@ public String nextStringValue() throws DataAccessException { throw new UnsupportedOperationException(); } - public static final class MongoSequence { - - private String id; - - private long count; - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public long getCount() { - return count; - } - - public void setCount(long count) { - this.count = count; - } - - @Override - public String toString() { - return "MongoSequence{" + "id='" + id + '\'' + ", count=" + count + '}'; - } - - } - } diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mongodb.js b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mongodb.js index 6a4d05c67f..0213a39df0 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mongodb.js +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-drop-mongodb.js @@ -2,6 +2,4 @@ db.getCollection("BATCH_JOB_INSTANCE").drop(); db.getCollection("BATCH_JOB_EXECUTION").drop(); db.getCollection("BATCH_STEP_EXECUTION").drop(); -db.getCollection("BATCH_JOB_INSTANCE_SEQ").drop(); -db.getCollection("BATCH_JOB_EXECUTION_SEQ").drop(); -db.getCollection("BATCH_STEP_EXECUTION_SEQ").drop(); +db.getCollection("BATCH_SEQUENCES").drop(); diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js index d8a3d25715..e3a971ad8a 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mongodb.js @@ -2,9 +2,17 @@ db.createCollection("BATCH_JOB_INSTANCE"); db.createCollection("BATCH_JOB_EXECUTION"); db.createCollection("BATCH_STEP_EXECUTION"); -db.createCollection("BATCH_JOB_INSTANCE_SEQ"); -db.createCollection("BATCH_JOB_EXECUTION_SEQ"); -db.createCollection("BATCH_STEP_EXECUTION_SEQ"); -db.getCollection("BATCH_JOB_INSTANCE_SEQ").insertOne({count : 0}); -db.getCollection("BATCH_JOB_EXECUTION_SEQ").insertOne({count : 0}); -db.getCollection("BATCH_STEP_EXECUTION_SEQ").insertOne({count : 0}); + +// SEQUENCES +db.createCollection("BATCH_SEQUENCES"); +db.getCollection("BATCH_SEQUENCES").insertOne({_id: "BATCH_JOB_INSTANCE_SEQ", count: Long(0)}); +db.getCollection("BATCH_SEQUENCES").insertOne({_id: "BATCH_JOB_EXECUTION_SEQ", count: Long(0)}); +db.getCollection("BATCH_SEQUENCES").insertOne({_id: "BATCH_STEP_EXECUTION_SEQ", count: Long(0)}); + +// INDICES +db.getCollection("BATCH_JOB_INSTANCE").createIndex("job_name_idx", {"jobName": 1}, {}); +db.getCollection("BATCH_JOB_INSTANCE").createIndex("job_name_key_idx", {"jobName": 1, "jobKey": 1}, {}); +db.getCollection("BATCH_JOB_INSTANCE").createIndex("job_instance_idx", {"jobInstanceId": -1}, {}); +db.getCollection("BATCH_JOB_EXECUTION").createIndex("job_instance_idx", {"jobInstanceId": 1}, {}); +db.getCollection("BATCH_JOB_EXECUTION").createIndex("job_instance_idx", {"jobInstanceId": 1, "status": 1}, {}); +db.getCollection("BATCH_STEP_EXECUTION").createIndex("step_execution_idx", {"stepExecutionId": 1}, {}); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java index 3499b51939..6be4001369 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java @@ -16,6 +16,7 @@ package org.springframework.batch.core.repository.support; import java.time.LocalDateTime; +import java.util.Map; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -75,12 +76,13 @@ public void setUp() { mongoTemplate.createCollection("BATCH_JOB_INSTANCE"); mongoTemplate.createCollection("BATCH_JOB_EXECUTION"); mongoTemplate.createCollection("BATCH_STEP_EXECUTION"); - mongoTemplate.createCollection("BATCH_JOB_INSTANCE_SEQ"); - mongoTemplate.createCollection("BATCH_JOB_EXECUTION_SEQ"); - mongoTemplate.createCollection("BATCH_STEP_EXECUTION_SEQ"); - mongoTemplate.getCollection("BATCH_JOB_INSTANCE_SEQ").insertOne(new Document("count", 0)); - mongoTemplate.getCollection("BATCH_JOB_EXECUTION_SEQ").insertOne(new Document("count", 0)); - mongoTemplate.getCollection("BATCH_STEP_EXECUTION_SEQ").insertOne(new Document("count", 0)); + mongoTemplate.createCollection("BATCH_SEQUENCES"); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_INSTANCE_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_EXECUTION_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_STEP_EXECUTION_SEQ", "count", 0L))); } @Test From 06c49ce998e2f04771728bf3b305436541db8a0f Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 02:20:50 +0100 Subject: [PATCH 119/152] Update MongoDB sample Signed-off-by: Fabrice Bibonne --- .../mongodb/DeletionJobConfiguration.java | 18 +++++----- .../mongodb/InsertionJobConfiguration.java | 16 ++++----- .../samples/mongodb/MongoDBConfiguration.java | 34 +++++++++++++++++-- .../samples/mongodb/MongoDBSampleApp.java | 15 +++++++- 4 files changed, 61 insertions(+), 22 deletions(-) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/DeletionJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/DeletionJobConfiguration.java index 42dcd42d9f..ca50af5af3 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/DeletionJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/DeletionJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2024 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. @@ -20,14 +20,13 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; -import org.springframework.batch.item.data.MongoItemReader; import org.springframework.batch.item.data.MongoItemWriter; -import org.springframework.batch.item.data.builder.MongoItemReaderBuilder; +import org.springframework.batch.item.data.MongoPagingItemReader; import org.springframework.batch.item.data.builder.MongoItemWriterBuilder; +import org.springframework.batch.item.data.builder.MongoPagingItemReaderBuilder; import org.springframework.context.annotation.Bean; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; @@ -38,18 +37,17 @@ /** * This job will remove document "foo3" from collection "person_out" using - * {@link MongoItemWriter#setDelete(boolean)}. + * {@link MongoItemWriter#setMode(MongoItemWriter.Mode)}}. * * @author Mahmoud Ben Hassine */ -@EnableBatchProcessing public class DeletionJobConfiguration { @Bean - public MongoItemReader mongoPersonReader(MongoTemplate mongoTemplate) { + public MongoPagingItemReader mongoPersonReader(MongoTemplate mongoTemplate) { Map sortOptions = new HashMap<>(); sortOptions.put("name", Sort.Direction.DESC); - return new MongoItemReaderBuilder().name("personItemReader") + return new MongoPagingItemReaderBuilder().name("personItemReader") .collection("person_out") .targetType(Person.class) .template(mongoTemplate) @@ -61,14 +59,14 @@ public MongoItemReader mongoPersonReader(MongoTemplate mongoTemplate) { @Bean public MongoItemWriter mongoPersonRemover(MongoTemplate mongoTemplate) { return new MongoItemWriterBuilder().template(mongoTemplate) - .delete(true) + .mode(MongoItemWriter.Mode.REMOVE) .collection("person_out") .build(); } @Bean public Step deletionStep(JobRepository jobRepository, PlatformTransactionManager transactionManager, - MongoItemReader mongoPersonReader, MongoItemWriter mongoPersonRemover) { + MongoPagingItemReader mongoPersonReader, MongoItemWriter mongoPersonRemover) { return new StepBuilder("step", jobRepository).chunk(2, transactionManager) .reader(mongoPersonReader) .writer(mongoPersonRemover) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/InsertionJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/InsertionJobConfiguration.java index 1e1488d50b..8bbf2b0932 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/InsertionJobConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/InsertionJobConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2024 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. @@ -20,14 +20,13 @@ import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; -import org.springframework.batch.item.data.MongoItemReader; +import org.springframework.batch.item.data.MongoPagingItemReader; import org.springframework.batch.item.data.MongoItemWriter; -import org.springframework.batch.item.data.builder.MongoItemReaderBuilder; import org.springframework.batch.item.data.builder.MongoItemWriterBuilder; +import org.springframework.batch.item.data.builder.MongoPagingItemReaderBuilder; import org.springframework.context.annotation.Bean; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; @@ -35,18 +34,17 @@ /** * This job will copy documents from collection "person_in" into collection "person_out" - * using {@link MongoItemReader} and {@link MongoItemWriter}. + * using {@link MongoPagingItemReader} and {@link MongoItemWriter}. * * @author Mahmoud Ben Hassine */ -@EnableBatchProcessing public class InsertionJobConfiguration { @Bean - public MongoItemReader mongoItemReader(MongoTemplate mongoTemplate) { + public MongoPagingItemReader mongoItemReader(MongoTemplate mongoTemplate) { Map sortOptions = new HashMap<>(); sortOptions.put("name", Sort.Direction.DESC); - return new MongoItemReaderBuilder().name("personItemReader") + return new MongoPagingItemReaderBuilder().name("personItemReader") .collection("person_in") .targetType(Person.class) .template(mongoTemplate) @@ -62,7 +60,7 @@ public MongoItemWriter mongoItemWriter(MongoTemplate mongoTemplate) { @Bean public Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager, - MongoItemReader mongoItemReader, MongoItemWriter mongoItemWriter) { + MongoPagingItemReader mongoItemReader, MongoItemWriter mongoItemWriter) { return new StepBuilder("step", jobRepository).chunk(2, transactionManager) .reader(mongoItemReader) .writer(mongoItemWriter) diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBConfiguration.java index 4695a21fd7..45b2994f3a 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBConfiguration.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2024 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. @@ -18,6 +18,11 @@ import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.explore.support.MongoJobExplorerFactoryBean; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.support.MongoJobRepositoryFactoryBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -26,9 +31,11 @@ import org.springframework.data.mongodb.MongoTransactionManager; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; @Configuration @PropertySource("classpath:/org/springframework/batch/samples/mongodb/mongodb-sample.properties") +@EnableBatchProcessing public class MongoDBConfiguration { @Value("${mongodb.host}") @@ -48,7 +55,10 @@ public MongoClient mongoClient() { @Bean public MongoTemplate mongoTemplate(MongoClient mongoClient) { - return new MongoTemplate(mongoClient, "test"); + MongoTemplate mongoTemplate = new MongoTemplate(mongoClient, "test"); + MappingMongoConverter converter = (MappingMongoConverter) mongoTemplate.getConverter(); + converter.setMapKeyDotReplacement("."); + return mongoTemplate; } @Bean @@ -61,4 +71,24 @@ public MongoTransactionManager transactionManager(MongoDatabaseFactory mongoData return new MongoTransactionManager(mongoDatabaseFactory); } + @Bean + public JobRepository jobRepository(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) + throws Exception { + MongoJobRepositoryFactoryBean jobRepositoryFactoryBean = new MongoJobRepositoryFactoryBean(); + jobRepositoryFactoryBean.setMongoOperations(mongoTemplate); + jobRepositoryFactoryBean.setTransactionManager(transactionManager); + jobRepositoryFactoryBean.afterPropertiesSet(); + return jobRepositoryFactoryBean.getObject(); + } + + @Bean + public JobExplorer jobExplorer(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) + throws Exception { + MongoJobExplorerFactoryBean jobExplorerFactoryBean = new MongoJobExplorerFactoryBean(); + jobExplorerFactoryBean.setMongoOperations(mongoTemplate); + jobExplorerFactoryBean.setTransactionManager(transactionManager); + jobExplorerFactoryBean.afterPropertiesSet(); + return jobExplorerFactoryBean.getObject(); + } + } diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBSampleApp.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBSampleApp.java index ae7b53bbb2..7fc8e52f5d 100644 --- a/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBSampleApp.java +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/mongodb/MongoDBSampleApp.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 the original author or authors. + * Copyright 2020-2024 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. @@ -17,6 +17,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Map; import com.mongodb.client.MongoCollection; import org.bson.Document; @@ -45,6 +46,18 @@ public static void main(String[] args) throws Exception { ApplicationContext context = new AnnotationConfigApplicationContext(configurationClasses); MongoTemplate mongoTemplate = context.getBean(MongoTemplate.class); + // create meta-data collections and sequences + mongoTemplate.createCollection("BATCH_JOB_INSTANCE"); + mongoTemplate.createCollection("BATCH_JOB_EXECUTION"); + mongoTemplate.createCollection("BATCH_STEP_EXECUTION"); + mongoTemplate.createCollection("BATCH_SEQUENCES"); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_INSTANCE_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_EXECUTION_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_STEP_EXECUTION_SEQ", "count", 0L))); + // clear collections and insert some documents in "person_in" MongoCollection personsIn = mongoTemplate.getCollection("person_in"); MongoCollection personsOut = mongoTemplate.getCollection("person_out"); From 22868329a8c696959b2a7d2aa9ab882d0ac01ff4 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 03:10:45 +0100 Subject: [PATCH 120/152] Update what is new section Signed-off-by: Fabrice Bibonne --- spring-batch-docs/modules/ROOT/pages/whatsnew.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc index d472af579f..5c94f12831 100644 --- a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc +++ b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc @@ -24,11 +24,11 @@ In this release, the Spring dependencies are upgraded to the following versions: * Spring Framework 6.2.0 * Spring Integration 6.4.0 * Spring Data 3.4.0 -* Spring Retry 2.0.9 -* Spring LDAP 3.2.7 +* Spring Retry 2.0.10 +* Spring LDAP 3.2.8 * Spring AMQP 3.2.0 * Spring Kafka 3.3.0 -* Micrometer 1.14.0 +* Micrometer 1.14.1 [[mongodb-job-repository-support]] == MongoDB job repository support From b7fa7dcb9f2ce2f75ac3c18ed08261d768cb12a3 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 03:10:55 +0100 Subject: [PATCH 121/152] Update javadocs Signed-off-by: Fabrice Bibonne --- .../core/explore/support/MongoJobExplorerFactoryBean.java | 8 ++++++++ .../repository/support/MongoJobRepositoryFactoryBean.java | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java index 8b24b7febb..c9e38e76f8 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/explore/support/MongoJobExplorerFactoryBean.java @@ -25,9 +25,17 @@ import org.springframework.batch.core.repository.dao.MongoStepExecutionDao; import org.springframework.beans.factory.InitializingBean; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.util.Assert; /** + * This factory bean creates a job explorer backed by MongoDB. It requires a mongo + * template and a mongo transaction manager. The mongo template must be configured + * with a {@link MappingMongoConverter} having a {@code MapKeyDotReplacement} set to a non + * null value. See {@code MongoDBJobRepositoryIntegrationTests} for an example. This is + * required to support execution context keys containing dots (like "step.type" or + * "batch.version") + * * @author Mahmoud Ben Hassine * @since 5.2.0 */ diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MongoJobRepositoryFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MongoJobRepositoryFactoryBean.java index 51030cd957..721272dde4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MongoJobRepositoryFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/support/MongoJobRepositoryFactoryBean.java @@ -25,9 +25,17 @@ import org.springframework.batch.core.repository.dao.MongoStepExecutionDao; import org.springframework.beans.factory.InitializingBean; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; import org.springframework.util.Assert; /** + * This factory bean creates a job repository backed by MongoDB. It requires a mongo + * template and a mongo transaction manager. The mongo template must be configured + * with a {@link MappingMongoConverter} having a {@code MapKeyDotReplacement} set to a non + * null value. See {@code MongoDBJobRepositoryIntegrationTests} for an example. This is + * required to support execution context keys containing dots (like "step.type" or + * "batch.version") + * * @author Mahmoud Ben Hassine * @since 5.2.0 */ From f0fab4f1a42ecfe31912834a4eeba4c994ed7a32 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 03:24:20 +0100 Subject: [PATCH 122/152] Prepare release 5.2.0 Signed-off-by: Fabrice Bibonne --- pom.xml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 9aed7c47de..178363fde9 100644 --- a/pom.xml +++ b/pom.xml @@ -61,19 +61,19 @@ 17 - 6.2.0-RC2 + 6.2.0 2.0.10 - 6.4.0-RC1 - 1.14.0-RC1 + 6.4.0 + 1.14.1 - 3.4.0-RC1 - 3.4.0-RC1 - 3.4.0-RC1 - 4.4.0-RC1 - 3.3.0-RC1 - 3.2.0-RC1 - 3.2.7 + 3.4.0 + 3.4.0 + 3.4.0 + 4.4.0 + 3.3.0 + 3.2.0 + 3.2.8 2.18.0 1.12.0 @@ -92,7 +92,7 @@ 3.0.2 - 1.4.0-RC1 + 1.4.0 1.4.20 4.13.2 From 03e7f019943da41b4a6c3c73085e5f77fb3a8a91 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 09:32:29 +0100 Subject: [PATCH 123/152] Release version 5.2.0 Signed-off-by: Fabrice Bibonne --- .github/workflows/maven-central-release.yml | 5 +---- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 9 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.github/workflows/maven-central-release.yml b/.github/workflows/maven-central-release.yml index bce60ac9ea..ff04db2c99 100644 --- a/.github/workflows/maven-central-release.yml +++ b/.github/workflows/maven-central-release.yml @@ -66,7 +66,7 @@ jobs: wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-integration/$RELEASE_VERSION/spring-batch-integration-$RELEASE_VERSION-sources.jar - name: Sign artifacts and release them to Maven Central - uses: jvalkeal/nexus-sync@v0 + uses: spring-io/nexus-sync-action@main id: nexus with: url: ${{ secrets.OSSRH_URL }} @@ -78,6 +78,3 @@ jobs: close: true release: true generate-checksums: true - pgp-sign: true - pgp-sign-passphrase: ${{ secrets.GPG_PASSPHRASE }} - pgp-sign-private-key: ${{ secrets.GPG_PRIVATE_KEY }} diff --git a/pom.xml b/pom.xml index 178363fde9..4596889dcb 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.0-SNAPSHOT + 5.2.0 pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index 7ad96c8a70..3d62534774 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 6ef5cfa1a6..c48672fbd1 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index c0e835ea10..a3bc3d1d0a 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index f980ee0bd1..c308dc2da8 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index bfc85952bd..43a25e9d34 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 9d433e897b..4631c458f9 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 15bbf21f7b..100026ee21 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0-SNAPSHOT + 5.2.0 spring-batch-test Spring Batch Test From 7ed1efcef1e03bb743fc5faeda789c3511e9d510 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 21 Nov 2024 09:32:57 +0100 Subject: [PATCH 124/152] Next development version Signed-off-by: Fabrice Bibonne --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 4596889dcb..6955f0060f 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.0 + 5.2.1-SNAPSHOT pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index 3d62534774..913818fe6e 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index c48672fbd1..33d0eb9890 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index a3bc3d1d0a..5f6d82fd17 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index c308dc2da8..e37bec53b2 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index 43a25e9d34..dbbc1e7c87 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 4631c458f9..7bb4812ccc 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 100026ee21..8ac987b12f 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.0 + 5.2.1-SNAPSHOT spring-batch-test Spring Batch Test From 269ca0f9cf3f935c9e83f87d99b1011f6632c5a6 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Sat, 23 Nov 2024 14:29:22 +0100 Subject: [PATCH 125/152] Update latest news in README.md Signed-off-by: Fabrice Bibonne --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ceaece65f9..3c0d3fdc22 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Latest news +* November 20, 2024: [Spring Batch 5.2.0 goes GA!](https://spring.io/blog/2024/11/20/spring-batch-5-2-0-goes-ga) * October 25, 2024: [Spring Batch 5.2.0-RC1 is out!](https://spring.io/blog/2024/10/25/spring-batch-5-2-0-rc1-is-out) * October 11, 2024: [Spring Batch 5.2.0-M2 is available now!](https://spring.io/blog/2024/10/11/spring-batch-5-2-0-m2-is-available-now) +* September 18, 2024: [Spring Batch 5.2.0-M1 is out!](https://spring.io/blog/2024/09/18/spring-batch-5-2-0-m1-is-out) @@ -227,4 +229,4 @@ Please see our [code of conduct](https://github.com/spring-projects/.github/blob # License -Spring Batch is Open Source software released under the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0.html). \ No newline at end of file +Spring Batch is Open Source software released under the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0.html). From 573deabe3dd8b77dafc135c964bb61a39cc8bb16 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 26 Nov 2024 11:46:04 +0100 Subject: [PATCH 126/152] Add link to latest blog post to the latest news section in README.md Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3c0d3fdc22..7110ad6524 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Latest news +* November 24, 2024: [Bootiful Spring Boot 3.4: Spring Batch](https://spring.io/blog/2024/11/24/bootiful-34-batch) * November 20, 2024: [Spring Batch 5.2.0 goes GA!](https://spring.io/blog/2024/11/20/spring-batch-5-2-0-goes-ga) * October 25, 2024: [Spring Batch 5.2.0-RC1 is out!](https://spring.io/blog/2024/10/25/spring-batch-5-2-0-rc1-is-out) * October 11, 2024: [Spring Batch 5.2.0-M2 is available now!](https://spring.io/blog/2024/10/11/spring-batch-5-2-0-m2-is-available-now) From 996ee537b09a249c7f744aec89829d230fabc222 Mon Sep 17 00:00:00 2001 From: Uli <98144814+uli-f@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:23:08 +1100 Subject: [PATCH 127/152] fixed class name and grammatical error in docs whatsnew Signed-off-by: Fabrice Bibonne --- spring-batch-docs/modules/ROOT/pages/whatsnew.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc index 5c94f12831..22635de973 100644 --- a/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc +++ b/spring-batch-docs/modules/ROOT/pages/whatsnew.adoc @@ -39,7 +39,7 @@ necessary collections in MongoDB in order to save and retrieve batch meta-data. This implementation requires MongoDB version 4 or later and is based on Spring Data MongoDB. In order to use this job repository, all you need to do is define a `MongoTemplate` and a -`MongoTransactionManager` which are required by the newly added `MongoDBJobRepositoryFactoryBean`: +`MongoTransactionManager` which are required by the newly added `MongoJobRepositoryFactoryBean`: ``` @Bean @@ -130,7 +130,7 @@ The https://en.wikipedia.org/wiki/Staged_event-driven_architecture[staged event- powerful architecture style to process data in stages connected by queues. This style is directly applicable to data pipelines and easily implemented in Spring Batch thanks to the ability to design jobs as a sequence of steps. -The only missing piece here is how to read and write data to intermediate queues. This release introduces an item reader +The only missing piece here is how to read data from and write data to intermediate queues. This release introduces an item reader and item writer to read data from and write it to a `BlockingQueue`. With these two new classes, one can design a first step that prepares data in a queue and a second step that consumes data from the same queue. This way, both steps can run concurrently to process data efficiently in a non-blocking, event-driven fashion. From 6860fff799731c5b7d5e09f1f05fd290576d0ae3 Mon Sep 17 00:00:00 2001 From: Tran Ngoc Nhan Date: Tue, 29 Oct 2024 23:44:14 +0700 Subject: [PATCH 128/152] Update reference link Signed-off-by: Fabrice Bibonne --- .../modules/ROOT/pages/spring-batch-integration.adoc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/spring-batch-integration.adoc b/spring-batch-docs/modules/ROOT/pages/spring-batch-integration.adoc index 45a9fe3cd8..e47243c999 100644 --- a/spring-batch-docs/modules/ROOT/pages/spring-batch-integration.adoc +++ b/spring-batch-docs/modules/ROOT/pages/spring-batch-integration.adoc @@ -32,11 +32,8 @@ provide methods to distribute workloads over a number of workers. This section covers the following key concepts: [role="xmlContent"] -* <> +* xref:spring-batch-integration/namespace-support.adoc[Namespace Support] * xref:spring-batch-integration/launching-jobs-through-messages.adoc[Launching Batch Jobs through Messages] * xref:spring-batch-integration/sub-elements.adoc#providing-feedback-with-informational-messages[Providing Feedback with Informational Messages] * xref:spring-batch-integration/sub-elements.adoc#asynchronous-processors[Asynchronous Processors] -* xref:spring-batch-integration/sub-elements.adoc#externalizing-batch-process-execution[Externalizing Batch Process Execution] - -[[namespace-support]] -[role="xmlContent"] +* xref:spring-batch-integration/sub-elements.adoc#externalizing-batch-process-execution[Externalizing Batch Process Execution] \ No newline at end of file From cbbceb9a7998b112fe5b3191c191842b6a0121d4 Mon Sep 17 00:00:00 2001 From: Taeik Lim Date: Sun, 9 Jun 2024 22:41:06 +0900 Subject: [PATCH 129/152] Remove repeating 'the the' in docs Signed-off-by: Taeik Lim Signed-off-by: Fabrice Bibonne --- .../core/configuration/support/DefaultBatchConfiguration.java | 4 ++-- .../core/configuration/xml/StepParserStepFactoryBean.java | 2 +- .../batch/core/configuration/xml/spring-batch-2.0.xsd | 2 +- .../batch/core/configuration/xml/spring-batch-2.1.xsd | 2 +- .../batch/core/configuration/xml/spring-batch-2.2.xsd | 2 +- .../batch/core/configuration/xml/spring-batch-3.0.xsd | 2 +- .../batch/core/configuration/xml/spring-batch.xsd | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java index 365af8487c..67df9fd41f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/DefaultBatchConfiguration.java @@ -461,9 +461,9 @@ protected String getDatabaseType() throws MetaDataAccessException { } /** - * Return the {@link TaskExecutor} to use in the the job launcher. Defaults to + * Return the {@link TaskExecutor} to use in the job launcher. Defaults to * {@link SyncTaskExecutor}. - * @return the {@link TaskExecutor} to use in the the job launcher. + * @return the {@link TaskExecutor} to use in the job launcher. */ protected TaskExecutor getTaskExecutor() { return new SyncTaskExecutor(); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java index cbad5b1cee..7b18458ee7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java @@ -893,7 +893,7 @@ public void setKeyGenerator(KeyGenerator keyGenerator) { /** * * Public setter for the capacity of the cache in the retry policy. If there are more - * items than the specified capacity, the the step fails without being skipped or + * items than the specified capacity, the step fails without being skipped or * recovered, and an exception is thrown. This guards against inadvertent infinite * loops generated by item identity problems.
    *
    diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd index 9f8241f3d1..07f613bed1 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.0.xsd @@ -182,7 +182,7 @@ diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd index 559c74a748..7f0b739f15 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd @@ -230,7 +230,7 @@ ref" is not required, and only needs to be specified explicitly diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.2.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.2.xsd index df341d1b29..8871bfbb51 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.2.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.2.xsd @@ -230,7 +230,7 @@ ref" is not required, and only needs to be specified explicitly diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-3.0.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-3.0.xsd index 3857e27962..2946e125cb 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-3.0.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-3.0.xsd @@ -245,7 +245,7 @@ diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd index 5cf435ab68..1c5b20f37c 100644 --- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd +++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch.xsd @@ -246,7 +246,7 @@ From eddbb7c0a2d98fdd7bd20b13f411adb42b941881 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Fri, 29 Nov 2024 10:52:01 +0100 Subject: [PATCH 130/152] Fix job execution retrieval by id for MongoDB Resolves #4722 Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- .../dao/MongoExecutionContextDao.java | 15 +- .../repository/dao/MongoJobExecutionDao.java | 12 +- .../repository/dao/MongoStepExecutionDao.java | 3 +- .../MongoDBIntegrationTestConfiguration.java | 98 +++++++++++ .../MongoDBJobExplorerIntegrationTests.java | 114 +++++++++++++ .../MongoDBJobRepositoryIntegrationTests.java | 89 +--------- ...goExecutionContextDaoIntegrationTests.java | 158 ++++++++++++++++++ 7 files changed, 396 insertions(+), 93 deletions(-) create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java index 485882a163..7b3e80294b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoExecutionContextDao.java @@ -16,7 +16,6 @@ package org.springframework.batch.core.repository.dao; import java.util.Collection; -import java.util.Map; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.StepExecution; @@ -46,8 +45,9 @@ public MongoExecutionContextDao(MongoOperations mongoOperations) { @Override public ExecutionContext getExecutionContext(JobExecution jobExecution) { - org.springframework.batch.core.repository.persistence.JobExecution execution = this.mongoOperations.findById( - jobExecution.getId(), org.springframework.batch.core.repository.persistence.JobExecution.class, + Query query = query(where("jobExecutionId").is(jobExecution.getId())); + org.springframework.batch.core.repository.persistence.JobExecution execution = this.mongoOperations.findOne( + query, org.springframework.batch.core.repository.persistence.JobExecution.class, JOB_EXECUTIONS_COLLECTION_NAME); if (execution == null) { return new ExecutionContext(); @@ -57,8 +57,9 @@ public ExecutionContext getExecutionContext(JobExecution jobExecution) { @Override public ExecutionContext getExecutionContext(StepExecution stepExecution) { - org.springframework.batch.core.repository.persistence.StepExecution execution = this.mongoOperations.findById( - stepExecution.getId(), org.springframework.batch.core.repository.persistence.StepExecution.class, + Query query = query(where("stepExecutionId").is(stepExecution.getId())); + org.springframework.batch.core.repository.persistence.StepExecution execution = this.mongoOperations.findOne( + query, org.springframework.batch.core.repository.persistence.StepExecution.class, STEP_EXECUTIONS_COLLECTION_NAME); if (execution == null) { return new ExecutionContext(); @@ -69,7 +70,7 @@ public ExecutionContext getExecutionContext(StepExecution stepExecution) { @Override public void saveExecutionContext(JobExecution jobExecution) { ExecutionContext executionContext = jobExecution.getExecutionContext(); - Query query = query(where("_id").is(jobExecution.getId())); + Query query = query(where("jobExecutionId").is(jobExecution.getId())); Update update = Update.update("executionContext", new org.springframework.batch.core.repository.persistence.ExecutionContext(executionContext.toMap(), @@ -82,7 +83,7 @@ public void saveExecutionContext(JobExecution jobExecution) { @Override public void saveExecutionContext(StepExecution stepExecution) { ExecutionContext executionContext = stepExecution.getExecutionContext(); - Query query = query(where("_id").is(stepExecution.getId())); + Query query = query(where("stepExecutionId").is(stepExecution.getId())); Update update = Update.update("executionContext", new org.springframework.batch.core.repository.persistence.ExecutionContext(executionContext.toMap(), diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java index c4525970d7..90d3326a9a 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoJobExecutionDao.java @@ -126,15 +126,17 @@ public Set findRunningJobExecutions(String jobName) { @Override public JobExecution getJobExecution(Long executionId) { - org.springframework.batch.core.repository.persistence.JobExecution jobExecution = this.mongoOperations.findById( - executionId, org.springframework.batch.core.repository.persistence.JobExecution.class, + Query jobExecutionQuery = query(where("jobExecutionId").is(executionId)); + org.springframework.batch.core.repository.persistence.JobExecution jobExecution = this.mongoOperations.findOne( + jobExecutionQuery, org.springframework.batch.core.repository.persistence.JobExecution.class, JOB_EXECUTIONS_COLLECTION_NAME); if (jobExecution == null) { return null; } - org.springframework.batch.core.repository.persistence.JobInstance jobInstance = this.mongoOperations.findById( - jobExecution.getJobInstanceId(), - org.springframework.batch.core.repository.persistence.JobInstance.class, JOB_INSTANCES_COLLECTION_NAME); + Query jobInstanceQuery = query(where("jobInstanceId").is(jobExecution.getJobInstanceId())); + org.springframework.batch.core.repository.persistence.JobInstance jobInstance = this.mongoOperations.findOne( + jobInstanceQuery, org.springframework.batch.core.repository.persistence.JobInstance.class, + JOB_INSTANCES_COLLECTION_NAME); return this.jobExecutionConverter.toJobExecution(jobExecution, this.jobInstanceConverter.toJobInstance(jobInstance)); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java index 9b889c1d81..ec9067fe61 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/MongoStepExecutionDao.java @@ -89,8 +89,9 @@ public void updateStepExecution(StepExecution stepExecution) { @Override public StepExecution getStepExecution(JobExecution jobExecution, Long stepExecutionId) { + Query query = query(where("stepExecutionId").is(stepExecutionId)); org.springframework.batch.core.repository.persistence.StepExecution stepExecution = this.mongoOperations - .findById(stepExecutionId, org.springframework.batch.core.repository.persistence.StepExecution.class, + .findOne(query, org.springframework.batch.core.repository.persistence.StepExecution.class, STEP_EXECUTIONS_COLLECTION_NAME); return stepExecution != null ? this.stepExecutionConverter.toStepExecution(stepExecution, jobExecution) : null; } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java new file mode 100644 index 0000000000..015a90e034 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java @@ -0,0 +1,98 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.support; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.explore.support.MongoJobExplorerFactoryBean; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.MongoDatabaseFactory; +import org.springframework.data.mongodb.MongoTransactionManager; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; + +/** + * @author Mahmoud Ben Hassine + */ +@Configuration +@EnableBatchProcessing +class MongoDBIntegrationTestConfiguration { + + @Bean + public JobRepository jobRepository(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) + throws Exception { + MongoJobRepositoryFactoryBean jobRepositoryFactoryBean = new MongoJobRepositoryFactoryBean(); + jobRepositoryFactoryBean.setMongoOperations(mongoTemplate); + jobRepositoryFactoryBean.setTransactionManager(transactionManager); + jobRepositoryFactoryBean.afterPropertiesSet(); + return jobRepositoryFactoryBean.getObject(); + } + + @Bean + public JobExplorer jobExplorer(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) + throws Exception { + MongoJobExplorerFactoryBean jobExplorerFactoryBean = new MongoJobExplorerFactoryBean(); + jobExplorerFactoryBean.setMongoOperations(mongoTemplate); + jobExplorerFactoryBean.setTransactionManager(transactionManager); + jobExplorerFactoryBean.afterPropertiesSet(); + return jobExplorerFactoryBean.getObject(); + } + + @Bean + public MongoDatabaseFactory mongoDatabaseFactory(@Value("${mongo.connectionString}") String connectionString) { + MongoClient mongoClient = MongoClients.create(connectionString); + return new SimpleMongoClientDatabaseFactory(mongoClient, "test"); + } + + @Bean + public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory) { + MongoTemplate template = new MongoTemplate(mongoDatabaseFactory); + MappingMongoConverter converter = (MappingMongoConverter) template.getConverter(); + converter.setMapKeyDotReplacement("."); + return template; + } + + @Bean + public MongoTransactionManager transactionManager(MongoDatabaseFactory mongoDatabaseFactory) { + MongoTransactionManager mongoTransactionManager = new MongoTransactionManager(); + mongoTransactionManager.setDatabaseFactory(mongoDatabaseFactory); + mongoTransactionManager.afterPropertiesSet(); + return mongoTransactionManager; + } + + @Bean + public Job job(JobRepository jobRepository, MongoTransactionManager transactionManager) { + return new JobBuilder("job", jobRepository) + .start(new StepBuilder("step1", jobRepository) + .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager) + .build()) + .next(new StepBuilder("step2", jobRepository) + .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager) + .build()) + .build(); + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java new file mode 100644 index 0000000000..a6ed1c9bb9 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java @@ -0,0 +1,114 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.support; + +import java.time.LocalDateTime; +import java.util.Map; + +import org.bson.Document; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +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.StepExecution; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig(MongoDBIntegrationTestConfiguration.class) +public class MongoDBJobExplorerIntegrationTests { + + private static final DockerImageName MONGODB_IMAGE = DockerImageName.parse("mongo:8.0.1"); + + @Container + public static MongoDBContainer mongodb = new MongoDBContainer(MONGODB_IMAGE); + + @DynamicPropertySource + static void setMongoDbConnectionString(DynamicPropertyRegistry registry) { + registry.add("mongo.connectionString", mongodb::getConnectionString); + } + + @BeforeAll + static void setUp(@Autowired MongoTemplate mongoTemplate) { + mongoTemplate.createCollection("BATCH_JOB_INSTANCE"); + mongoTemplate.createCollection("BATCH_JOB_EXECUTION"); + mongoTemplate.createCollection("BATCH_STEP_EXECUTION"); + mongoTemplate.createCollection("BATCH_SEQUENCES"); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_INSTANCE_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_EXECUTION_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_STEP_EXECUTION_SEQ", "count", 0L))); + } + + @Test + void testGetJobExecutionById(@Autowired JobLauncher jobLauncher, @Autowired Job job, + @Autowired JobExplorer jobExplorer) throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addString("name", "testGetJobExecutionById") + .addLocalDateTime("runtime", LocalDateTime.now()) + .toJobParameters(); + JobExecution jobExecution = jobLauncher.run(job, jobParameters); + + // when + JobExecution actual = jobExplorer.getJobExecution(jobExecution.getId()); + + // then + assertNotNull(actual); + assertNotNull(actual.getJobInstance()); + assertEquals(jobExecution.getJobId(), actual.getJobId()); + assertFalse(actual.getExecutionContext().isEmpty()); + } + + @Test + void testGetStepExecutionByIds(@Autowired JobLauncher jobLauncher, @Autowired Job job, + @Autowired JobExplorer jobExplorer) throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addString("name", "testGetStepExecutionByIds") + .addLocalDateTime("runtime", LocalDateTime.now()) + .toJobParameters(); + JobExecution jobExecution = jobLauncher.run(job, jobParameters); + StepExecution stepExecution = jobExecution.getStepExecutions().stream().findFirst().orElseThrow(); + + // when + StepExecution actual = jobExplorer.getStepExecution(jobExecution.getId(), stepExecution.getId()); + + // then + assertNotNull(actual); + assertEquals(stepExecution.getId(), actual.getId()); + assertFalse(actual.getExecutionContext().isEmpty()); + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java index 6be4001369..b45aa7bd19 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java @@ -18,14 +18,14 @@ import java.time.LocalDateTime; import java.util.Map; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; import com.mongodb.client.MongoCollection; import org.bson.Document; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -36,31 +36,15 @@ import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; -import org.springframework.batch.core.explore.JobExplorer; -import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.batch.core.repository.JobRepository; -import org.springframework.batch.core.step.builder.StepBuilder; -import org.springframework.batch.core.explore.support.MongoJobExplorerFactoryBean; -import org.springframework.batch.repeat.RepeatStatus; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.mongodb.MongoDatabaseFactory; -import org.springframework.data.mongodb.MongoTransactionManager; import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; -import org.springframework.data.mongodb.core.convert.MappingMongoConverter; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; /** * @author Mahmoud Ben Hassine */ @Testcontainers(disabledWithoutDocker = true) -@ExtendWith(SpringExtension.class) -@ContextConfiguration +@SpringJUnitConfig(MongoDBIntegrationTestConfiguration.class) public class MongoDBJobRepositoryIntegrationTests { private static final DockerImageName MONGODB_IMAGE = DockerImageName.parse("mongo:8.0.1"); @@ -68,6 +52,11 @@ public class MongoDBJobRepositoryIntegrationTests { @Container public static MongoDBContainer mongodb = new MongoDBContainer(MONGODB_IMAGE); + @DynamicPropertySource + static void setMongoDbConnectionString(DynamicPropertyRegistry registry) { + registry.add("mongo.connectionString", mongodb::getConnectionString); + } + @Autowired private MongoTemplate mongoTemplate; @@ -119,64 +108,4 @@ private static void dump(MongoCollection collection, String prefix) { } } - @Configuration - @EnableBatchProcessing - static class TestConfiguration { - - @Bean - public JobRepository jobRepository(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) - throws Exception { - MongoJobRepositoryFactoryBean jobRepositoryFactoryBean = new MongoJobRepositoryFactoryBean(); - jobRepositoryFactoryBean.setMongoOperations(mongoTemplate); - jobRepositoryFactoryBean.setTransactionManager(transactionManager); - jobRepositoryFactoryBean.afterPropertiesSet(); - return jobRepositoryFactoryBean.getObject(); - } - - @Bean - public JobExplorer jobExplorer(MongoTemplate mongoTemplate, MongoTransactionManager transactionManager) - throws Exception { - MongoJobExplorerFactoryBean jobExplorerFactoryBean = new MongoJobExplorerFactoryBean(); - jobExplorerFactoryBean.setMongoOperations(mongoTemplate); - jobExplorerFactoryBean.setTransactionManager(transactionManager); - jobExplorerFactoryBean.afterPropertiesSet(); - return jobExplorerFactoryBean.getObject(); - } - - @Bean - public MongoDatabaseFactory mongoDatabaseFactory() { - MongoClient mongoClient = MongoClients.create(mongodb.getConnectionString()); - return new SimpleMongoClientDatabaseFactory(mongoClient, "test"); - } - - @Bean - public MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDatabaseFactory) { - MongoTemplate template = new MongoTemplate(mongoDatabaseFactory); - MappingMongoConverter converter = (MappingMongoConverter) template.getConverter(); - converter.setMapKeyDotReplacement("."); - return template; - } - - @Bean - public MongoTransactionManager transactionManager(MongoDatabaseFactory mongoDatabaseFactory) { - MongoTransactionManager mongoTransactionManager = new MongoTransactionManager(); - mongoTransactionManager.setDatabaseFactory(mongoDatabaseFactory); - mongoTransactionManager.afterPropertiesSet(); - return mongoTransactionManager; - } - - @Bean - public Job job(JobRepository jobRepository, MongoTransactionManager transactionManager) { - return new JobBuilder("job", jobRepository) - .start(new StepBuilder("step1", jobRepository) - .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager) - .build()) - .next(new StepBuilder("step2", jobRepository) - .tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED, transactionManager) - .build()) - .build(); - } - - } - } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java new file mode 100644 index 0000000000..7b71ca8505 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java @@ -0,0 +1,158 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.repository.support; + +import java.time.LocalDateTime; +import java.util.Map; + +import org.bson.Document; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +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.StepExecution; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.dao.ExecutionContextDao; +import org.springframework.batch.core.repository.dao.MongoExecutionContextDao; +import org.springframework.batch.core.repository.support.MongoExecutionContextDaoIntegrationTests.ExecutionContextDaoConfiguration; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig({ MongoDBIntegrationTestConfiguration.class, ExecutionContextDaoConfiguration.class }) +public class MongoExecutionContextDaoIntegrationTests { + + private static final DockerImageName MONGODB_IMAGE = DockerImageName.parse("mongo:8.0.1"); + + @Container + public static MongoDBContainer mongodb = new MongoDBContainer(MONGODB_IMAGE); + + @DynamicPropertySource + static void setMongoDbConnectionString(DynamicPropertyRegistry registry) { + registry.add("mongo.connectionString", mongodb::getConnectionString); + } + + @BeforeAll + static void setUp(@Autowired MongoTemplate mongoTemplate) { + mongoTemplate.createCollection("BATCH_JOB_INSTANCE"); + mongoTemplate.createCollection("BATCH_JOB_EXECUTION"); + mongoTemplate.createCollection("BATCH_STEP_EXECUTION"); + mongoTemplate.createCollection("BATCH_SEQUENCES"); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_INSTANCE_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_JOB_EXECUTION_SEQ", "count", 0L))); + mongoTemplate.getCollection("BATCH_SEQUENCES") + .insertOne(new Document(Map.of("_id", "BATCH_STEP_EXECUTION_SEQ", "count", 0L))); + } + + @Test + void testGetJobExecutionWithEmptyResult(@Autowired ExecutionContextDao executionContextDao) { + // given + JobExecution jobExecution = new JobExecution(12345678L); + + // when + ExecutionContext actual = executionContextDao.getExecutionContext(jobExecution); + + // then + assertNotNull(actual); + assertTrue(actual.isEmpty()); + } + + @Test + void testSaveJobExecution(@Autowired JobLauncher jobLauncher, @Autowired Job job, + @Autowired ExecutionContextDao executionContextDao) throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addString("name", "testSaveJobExecution") + .addLocalDateTime("runtime", LocalDateTime.now()) + .toJobParameters(); + JobExecution jobExecution = jobLauncher.run(job, jobParameters); + + // when + jobExecution.getExecutionContext().putString("foo", "bar"); + executionContextDao.saveExecutionContext(jobExecution); + ExecutionContext actual = executionContextDao.getExecutionContext(jobExecution); + + // then + assertTrue(actual.containsKey("foo")); + assertEquals("bar", actual.get("foo")); + } + + @Test + void testGetStepExecutionWithEmptyResult(@Autowired ExecutionContextDao executionContextDao) { + // given + JobExecution jobExecution = new JobExecution(12345678L); + StepExecution stepExecution = new StepExecution("step", jobExecution, 23456789L); + + // when + ExecutionContext actual = executionContextDao.getExecutionContext(stepExecution); + + // then + assertNotNull(actual); + assertTrue(actual.isEmpty()); + } + + @Test + void testSaveStepExecution(@Autowired JobLauncher jobLauncher, @Autowired Job job, + @Autowired ExecutionContextDao executionContextDao) throws Exception { + // given + JobParameters jobParameters = new JobParametersBuilder().addString("name", "testSaveJobExecution") + .addLocalDateTime("runtime", LocalDateTime.now()) + .toJobParameters(); + JobExecution jobExecution = jobLauncher.run(job, jobParameters); + StepExecution stepExecution = jobExecution.getStepExecutions().stream().findFirst().orElseThrow(); + + // when + stepExecution.getExecutionContext().putString("foo", "bar"); + executionContextDao.saveExecutionContext(stepExecution); + ExecutionContext actual = executionContextDao.getExecutionContext(stepExecution); + + // then + assertTrue(actual.containsKey("foo")); + assertEquals("bar", actual.get("foo")); + } + + @Configuration + static class ExecutionContextDaoConfiguration { + + @Bean + ExecutionContextDao executionContextDao(MongoOperations mongoOperations) { + return new MongoExecutionContextDao(mongoOperations); + } + + } + +} From 87c4d041fa51862cf7ababaac4df2e56a1f215c9 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Sun, 24 Nov 2024 03:24:30 +0100 Subject: [PATCH 131/152] Fix parsing of the `stop` element in step XML Previously, users were forced to set the attribute `exit-code` of the element to `""` as a work-around to prevent failing restarts. Resolves #1287 Signed-off-by: Fabrice Bibonne --- .../configuration/xml/AbstractFlowParser.java | 5 +- .../StopAndRestartFailedJobParserTests.java | 4 +- .../xml/StopAndRestartJobParserTests.java | 4 +- ...startWithCustomExitCodeJobParserTests.java | 69 +++++++++++++++++++ .../xml/StopCustomStatusJobParserTests.java | 4 +- .../xml/StopIncompleteJobParserTests.java | 4 +- .../configuration/xml/StopJobParserTests.java | 4 +- ...AndRestartFailedJobParserTests-context.xml | 2 +- .../StopAndRestartJobParserTests-context.xml | 2 +- ...thCustomExitCodeJobParserTests-context.xml | 16 +++++ ...StopCustomStatusJobParserTests-context.xml | 2 +- .../StopIncompleteJobParserTests-context.xml | 2 +- .../xml/StopJobParserTests-context.xml | 2 +- 13 files changed, 97 insertions(+), 23 deletions(-) create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests.java create mode 100644 spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests-context.xml diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java index 03ec94b23e..876a9ed2ce 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/AbstractFlowParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -409,8 +409,7 @@ protected static Collection createTransition(FlowExecutionStatus endBuilder.addConstructorArgValue(abandon); - String nextOnEnd = exitCodeExists ? null : next; - endState = getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), null, nextOnEnd); + endState = getStateTransitionReference(parserContext, endBuilder.getBeanDefinition(), null, next); next = endName; } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests.java index b8fe6dedfb..525e2ba2a1 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -33,8 +33,6 @@ * */ @SpringJUnitConfig -// FIXME this test fails when upgrading the batch xsd from 2.2 to 3.0: -// https://github.com/spring-projects/spring-batch/issues/1287 class StopAndRestartFailedJobParserTests extends AbstractJobParserTests { @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests.java index 1702b6f1a3..6f30120f30 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -29,8 +29,6 @@ * */ @SpringJUnitConfig -// FIXME this test fails when upgrading the batch xsd from 2.2 to 3.0: -// https://github.com/spring-projects/spring-batch/issues/1287 class StopAndRestartJobParserTests extends AbstractJobParserTests { @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests.java new file mode 100644 index 0000000000..375e21bb27 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.core.configuration.xml; + +import org.junit.jupiter.api.Test; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.StepExecution; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Henning Pöttker + */ +@SpringJUnitConfig +class StopAndRestartWithCustomExitCodeJobParserTests extends AbstractJobParserTests { + + @Test + void testStopIncomplete() throws Exception { + + // + // First Launch + // + JobExecution jobExecution = createJobExecution(); + job.execute(jobExecution); + assertEquals(1, stepNamesList.size()); + assertEquals("[s1]", stepNamesList.toString()); + + assertEquals(BatchStatus.STOPPED, jobExecution.getStatus()); + assertEquals("CUSTOM", jobExecution.getExitStatus().getExitCode()); + + StepExecution stepExecution1 = getStepExecution(jobExecution, "s1"); + assertEquals(BatchStatus.COMPLETED, stepExecution1.getStatus()); + assertEquals(ExitStatus.COMPLETED.getExitCode(), stepExecution1.getExitStatus().getExitCode()); + + // + // Second Launch + // + stepNamesList.clear(); + jobExecution = createJobExecution(); + job.execute(jobExecution); + assertEquals(1, stepNamesList.size()); // step1 is not executed + assertEquals("[s2]", stepNamesList.toString()); + + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + assertEquals(ExitStatus.COMPLETED, jobExecution.getExitStatus()); + + StepExecution stepExecution2 = getStepExecution(jobExecution, "s2"); + assertEquals(BatchStatus.COMPLETED, stepExecution2.getStatus()); + assertEquals(ExitStatus.COMPLETED, stepExecution2.getExitStatus()); + + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests.java index f0fe245e14..e824cd75d9 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -29,8 +29,6 @@ * */ @SpringJUnitConfig -// FIXME this test fails when upgrading the batch xsd from 2.2 to 3.0: -// https://github.com/spring-projects/spring-batch/issues/1287 class StopCustomStatusJobParserTests extends AbstractJobParserTests { @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests.java index e6ddfee766..b5e4b8183a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -29,8 +29,6 @@ * */ @SpringJUnitConfig -// FIXME this test fails when upgrading the batch xsd from 2.2 to 3.0: -// https://github.com/spring-projects/spring-batch/issues/1287 class StopIncompleteJobParserTests extends AbstractJobParserTests { @Test diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopJobParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopJobParserTests.java index b2f0d75c71..f05c0b6cba 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopJobParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StopJobParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -34,8 +34,6 @@ * */ @SpringJUnitConfig -// FIXME this test fails when upgrading the batch xsd from 2.2 to 3.0: -// https://github.com/spring-projects/spring-batch/issues/1287 class StopJobParserTests extends AbstractJobParserTests { @Test diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests-context.xml index 189d63ce13..97df1bef5e 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartFailedJobParserTests-context.xml @@ -2,7 +2,7 @@ diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests-context.xml index fe7ed075ed..de0face964 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartJobParserTests-context.xml @@ -1,7 +1,7 @@ diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests-context.xml new file mode 100644 index 0000000000..dba05231c4 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopAndRestartWithCustomExitCodeJobParserTests-context.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests-context.xml index fbb7f4c6a0..93b0a1b4ea 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopCustomStatusJobParserTests-context.xml @@ -1,7 +1,7 @@ diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests-context.xml index ca269dec17..080f44a374 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopIncompleteJobParserTests-context.xml @@ -1,7 +1,7 @@ diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopJobParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopJobParserTests-context.xml index 0f67bf801d..5be5d43f6b 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopJobParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/StopJobParserTests-context.xml @@ -1,7 +1,7 @@ From 3d2022dd364e0ef71b79d997390ad91ea54335cf Mon Sep 17 00:00:00 2001 From: xeounxzxu Date: Sat, 26 Oct 2024 11:12:52 +0900 Subject: [PATCH 132/152] Add test coverage of `isNonDefaultExitStatus` in ExitStatusTests Issue #4690 Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- .../batch/core/ExitStatusTests.java | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/ExitStatusTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/ExitStatusTests.java index 3a450aa993..907ea62ff8 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/ExitStatusTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/ExitStatusTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -15,14 +15,21 @@ */ package org.springframework.batch.core; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import org.springframework.util.SerializationUtils; + import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Test; -import org.springframework.util.SerializationUtils; - /** * @author Dave Syer * @author Mahmoud Ben Hassine @@ -186,4 +193,29 @@ void testSerializable() { assertEquals(status.getExitCode(), clone.getExitCode()); } + @ParameterizedTest + @MethodSource("provideKnownExitStatuses") + public void testIsNonDefaultExitStatusShouldReturnTrue(ExitStatus status) { + boolean result = ExitStatus.isNonDefaultExitStatus(status); + assertTrue(result); + } + + @ParameterizedTest + @MethodSource("provideCustomExitStatuses") + public void testIsNonDefaultExitStatusShouldReturnFalse(ExitStatus status) { + boolean result = ExitStatus.isNonDefaultExitStatus(status); + assertFalse(result); + } + + private static Stream provideKnownExitStatuses() { + return Stream.of(Arguments.of((ExitStatus) null), Arguments.of(new ExitStatus(null)), + Arguments.of(ExitStatus.COMPLETED), Arguments.of(ExitStatus.EXECUTING), Arguments.of(ExitStatus.FAILED), + Arguments.of(ExitStatus.NOOP), Arguments.of(ExitStatus.STOPPED), Arguments.of(ExitStatus.UNKNOWN)); + } + + private static Stream provideCustomExitStatuses() { + return Stream.of(Arguments.of(new ExitStatus("CUSTOM")), Arguments.of(new ExitStatus("SUCCESS")), + Arguments.of(new ExitStatus("DONE"))); + } + } From 38ec3ad09b53522fd88435a4b5143d7244057f17 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Sat, 23 Nov 2024 23:42:46 +0100 Subject: [PATCH 133/152] Update implementations of `PagingQueryProvider` Replaces the implementation of `DerbyPagingQueryProvider` with that corresponding to DB2 and adds an integration test that failed with the previous implementation. Deprecates `SqlWindowingPagingQueryProvider` for removal, which was effectively only used by `DerbyPagingQueryProvider`. Resolves #4733 Signed-off-by: Fabrice Bibonne --- .../support/Db2PagingQueryProvider.java | 9 +- .../support/DerbyPagingQueryProvider.java | 71 +++----------- .../support/SqlServerPagingQueryProvider.java | 7 +- .../SqlWindowingPagingQueryProvider.java | 4 +- .../support/SybasePagingQueryProvider.java | 7 +- ...byPagingQueryProviderIntegrationTests.java | 98 +++++++++++++++++++ .../DerbyPagingQueryProviderTests.java | 83 ++-------------- .../SqlWindowingPagingQueryProviderTests.java | 3 +- .../support/query-provider-fixture.sql | 20 ++++ 9 files changed, 151 insertions(+), 151 deletions(-) create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/resources/org/springframework/batch/item/database/support/query-provider-fixture.sql diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/Db2PagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/Db2PagingQueryProvider.java index f29f868190..660eb430b9 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/Db2PagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/Db2PagingQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2021 the original author or authors. + * Copyright 2006-2024 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. @@ -27,7 +27,7 @@ * @author Mahmoud Ben Hassine * @since 2.0 */ -public class Db2PagingQueryProvider extends SqlWindowingPagingQueryProvider { +public class Db2PagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override public String generateFirstPageQuery(int pageSize) { @@ -44,11 +44,6 @@ public String generateRemainingPagesQuery(int pageSize) { } } - @Override - protected Object getSubQueryAlias() { - return "AS TMP_SUB "; - } - private String buildLimitClause(int pageSize) { return new StringBuilder().append("FETCH FIRST ").append(pageSize).append(" ROWS ONLY").toString(); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java index b2f4ab422c..015454f90e 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/DerbyPagingQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -16,76 +16,37 @@ package org.springframework.batch.item.database.support; -import java.sql.DatabaseMetaData; -import javax.sql.DataSource; - import org.springframework.batch.item.database.PagingQueryProvider; -import org.springframework.dao.InvalidDataAccessResourceUsageException; -import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.util.StringUtils; /** - * Derby implementation of a {@link PagingQueryProvider} using standard SQL:2003 windowing - * functions. These features are supported starting with Apache Derby version 10.4.1.3. - *

    - * As the OVER() function does not support the ORDER BY clause a sub query is instead used - * to order the results before the ROW_NUM restriction is applied + * Derby implementation of a {@link PagingQueryProvider} using database specific features. * * @author Thomas Risberg * @author David Thexton * @author Michael Minella + * @author Henning Pöttker * @since 2.0 */ -public class DerbyPagingQueryProvider extends SqlWindowingPagingQueryProvider { - - private static final String MINIMAL_DERBY_VERSION = "10.4.1.3"; +public class DerbyPagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override - public void init(DataSource dataSource) throws Exception { - super.init(dataSource); - String version = JdbcUtils.extractDatabaseMetaData(dataSource, DatabaseMetaData::getDatabaseProductVersion); - if (!isDerbyVersionSupported(version)) { - throw new InvalidDataAccessResourceUsageException( - "Apache Derby version " + version + " is not supported by this class, Only version " - + MINIMAL_DERBY_VERSION + " or later is supported"); - } - } - - // derby version numbering is M.m.f.p [ {alpha|beta} ] see - // https://db.apache.org/derby/papers/versionupgrade.html#Basic+Numbering+Scheme - private boolean isDerbyVersionSupported(String version) { - String[] minimalVersionParts = MINIMAL_DERBY_VERSION.split("\\."); - String[] versionParts = version.split("[\\. ]"); - for (int i = 0; i < minimalVersionParts.length; i++) { - int minimalVersionPart = Integer.parseInt(minimalVersionParts[i]); - int versionPart = Integer.parseInt(versionParts[i]); - if (versionPart < minimalVersionPart) { - return false; - } - else if (versionPart > minimalVersionPart) { - return true; - } - } - return true; + public String generateFirstPageQuery(int pageSize) { + return SqlPagingQueryUtils.generateLimitSqlQuery(this, false, buildLimitClause(pageSize)); } @Override - protected String getOrderedQueryAlias() { - return "TMP_ORDERED"; - } - - @Override - protected String getOverClause() { - return ""; - } - - @Override - protected String getOverSubstituteClauseStart() { - return " FROM (SELECT " + getSelectClause(); + public String generateRemainingPagesQuery(int pageSize) { + if (StringUtils.hasText(getGroupClause())) { + return SqlPagingQueryUtils.generateLimitGroupedSqlQuery(this, buildLimitClause(pageSize)); + } + else { + return SqlPagingQueryUtils.generateLimitSqlQuery(this, true, buildLimitClause(pageSize)); + } } - @Override - protected String getOverSubstituteClauseEnd() { - return " ) AS " + getOrderedQueryAlias(); + private String buildLimitClause(int pageSize) { + return new StringBuilder("FETCH FIRST ").append(pageSize).append(" ROWS ONLY").toString(); } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java index 51b879ed2a..b1c79763b1 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProvider.java @@ -28,7 +28,7 @@ * @author Mahmoud Ben Hassine * @since 2.0 */ -public class SqlServerPagingQueryProvider extends SqlWindowingPagingQueryProvider { +public class SqlServerPagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override public String generateFirstPageQuery(int pageSize) { @@ -45,11 +45,6 @@ public String generateRemainingPagesQuery(int pageSize) { } } - @Override - protected Object getSubQueryAlias() { - return "AS TMP_SUB "; - } - private String buildTopClause(int pageSize) { return new StringBuilder().append("TOP ").append(pageSize).toString(); } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java index 1f75726aaf..00e0d04711 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -26,7 +26,9 @@ * @author Thomas Risberg * @author Michael Minella * @since 2.0 + * @deprecated since 5.2.1 with no replacement. Scheduled for removal in 6.0. */ +@Deprecated(forRemoval = true) public class SqlWindowingPagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java index af69169139..ade0af5266 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/database/support/SybasePagingQueryProvider.java @@ -28,7 +28,7 @@ * @author Mahmoud Ben Hassine * @since 2.0 */ -public class SybasePagingQueryProvider extends SqlWindowingPagingQueryProvider { +public class SybasePagingQueryProvider extends AbstractSqlPagingQueryProvider { @Override public String generateFirstPageQuery(int pageSize) { @@ -45,11 +45,6 @@ public String generateRemainingPagesQuery(int pageSize) { } } - @Override - protected Object getSubQueryAlias() { - return ""; - } - private String buildTopClause(int pageSize) { return new StringBuilder().append("TOP ").append(pageSize).toString(); } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..fb3820c61c --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java @@ -0,0 +1,98 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.batch.item.database.Order; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Henning Pöttker + */ +class DerbyPagingQueryProviderIntegrationTests { + + private static EmbeddedDatabase embeddedDatabase; + + private static JdbcTemplate jdbcTemplate; + + @BeforeAll + static void setUp() { + embeddedDatabase = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.DERBY) + .addScript("/org/springframework/batch/item/database/support/query-provider-fixture.sql") + .generateUniqueName(true) + .build(); + jdbcTemplate = new JdbcTemplate(embeddedDatabase); + } + + @AfterAll + static void tearDown() { + if (embeddedDatabase != null) { + embeddedDatabase.shutdown(); + } + } + + @Test + void testWithoutGrouping() { + var queryProvider = new DerbyPagingQueryProvider(); + queryProvider.setSelectClause("ID, STRING"); + queryProvider.setFromClause("TEST_TABLE"); + Map sortKeys = new HashMap<>(); + sortKeys.put("ID", Order.ASCENDING); + queryProvider.setSortKeys(sortKeys); + + List firstPage = jdbcTemplate.query(queryProvider.generateFirstPageQuery(2), MAPPER); + assertEquals(List.of(new Item(1, "Spring"), new Item(2, "Batch")), firstPage); + + List secondPage = jdbcTemplate.query(queryProvider.generateRemainingPagesQuery(2), MAPPER, 2); + assertEquals(List.of(new Item(3, "Infrastructure")), secondPage); + } + + @Test + void testWithGrouping() { + var queryProvider = new DerbyPagingQueryProvider(); + queryProvider.setSelectClause("STRING"); + queryProvider.setFromClause("GROUPING_TEST_TABLE"); + queryProvider.setGroupClause("STRING"); + Map sortKeys = new HashMap<>(); + sortKeys.put("STRING", Order.ASCENDING); + queryProvider.setSortKeys(sortKeys); + + List firstPage = jdbcTemplate.queryForList(queryProvider.generateFirstPageQuery(2), String.class); + assertEquals(List.of("Batch", "Infrastructure"), firstPage); + + List secondPage = jdbcTemplate.queryForList(queryProvider.generateRemainingPagesQuery(2), String.class, + "Infrastructure"); + assertEquals(List.of("Spring"), secondPage); + } + + private record Item(Integer id, String string) { + } + + private static final RowMapper MAPPER = (rs, rowNum) -> new Item(rs.getInt("id"), rs.getString("string")); + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java index 5bd891ddfa..c93f979c9b 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -15,20 +15,9 @@ */ package org.springframework.batch.item.database.support; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.sql.Connection; -import java.sql.DatabaseMetaData; - -import javax.sql.DataSource; import org.junit.jupiter.api.Test; -import org.springframework.batch.item.database.Order; -import org.springframework.dao.InvalidDataAccessResourceUsageException; /** * @author Thomas Risberg @@ -41,43 +30,10 @@ class DerbyPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests pagingQueryProvider = new DerbyPagingQueryProvider(); } - @Test - void testInit() throws Exception { - DataSource ds = mock(); - Connection con = mock(); - DatabaseMetaData dmd = mock(); - when(dmd.getDatabaseProductVersion()).thenReturn("10.4.1.3"); - when(con.getMetaData()).thenReturn(dmd); - when(ds.getConnection()).thenReturn(con); - pagingQueryProvider.init(ds); - } - - @Test - void testInitWithRecentVersion() throws Exception { - DataSource ds = mock(); - Connection con = mock(); - DatabaseMetaData dmd = mock(); - when(dmd.getDatabaseProductVersion()).thenReturn("10.10.1.1"); - when(con.getMetaData()).thenReturn(dmd); - when(ds.getConnection()).thenReturn(con); - pagingQueryProvider.init(ds); - } - - @Test - void testInitWithUnsupportedVersion() throws Exception { - DataSource ds = mock(); - Connection con = mock(); - DatabaseMetaData dmd = mock(); - when(dmd.getDatabaseProductVersion()).thenReturn("10.2.9.9"); - when(con.getMetaData()).thenReturn(dmd); - when(ds.getConnection()).thenReturn(con); - assertThrows(InvalidDataAccessResourceUsageException.class, () -> pagingQueryProvider.init(ds)); - } - @Test @Override void testGenerateFirstPageQuery() { - String sql = "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 ORDER BY id ASC"; + String sql = "SELECT id, name, age FROM foo WHERE bar = 1 ORDER BY id ASC FETCH FIRST 100 ROWS ONLY"; String s = pagingQueryProvider.generateFirstPageQuery(pageSize); assertEquals(sql, s); } @@ -85,60 +41,37 @@ void testGenerateFirstPageQuery() { @Test @Override void testGenerateRemainingPagesQuery() { - String sql = "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 AND ((id > ?)) ORDER BY id ASC"; + String sql = "SELECT id, name, age FROM foo WHERE (bar = 1) AND ((id > ?)) ORDER BY id ASC FETCH FIRST 100 ROWS ONLY"; String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); assertEquals(sql, s); } - /** - * Older versions of Derby don't allow order by in the sub select. This should work - * with 10.6.1 and above. - */ @Test @Override - void testQueryContainsSortKey() { - String s = pagingQueryProvider.generateFirstPageQuery(pageSize).toLowerCase(); - assertTrue(s.contains("id asc"), "Wrong query: " + s); - } - - /** - * Older versions of Derby don't allow order by in the sub select. This should work - * with 10.6.1 and above. - */ - @Test - @Override - void testQueryContainsSortKeyDesc() { - pagingQueryProvider.getSortKeys().put("id", Order.DESCENDING); - String s = pagingQueryProvider.generateFirstPageQuery(pageSize).toLowerCase(); - assertTrue(s.contains("id desc"), "Wrong query: " + s); - } - - @Override - @Test void testGenerateFirstPageQueryWithGroupBy() { pagingQueryProvider.setGroupClause("dep"); - String sql = "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 ORDER BY id ASC"; + String sql = "SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep ORDER BY id ASC FETCH FIRST 100 ROWS ONLY"; String s = pagingQueryProvider.generateFirstPageQuery(pageSize); assertEquals(sql, s); } - @Override @Test + @Override void testGenerateRemainingPagesQueryWithGroupBy() { pagingQueryProvider.setGroupClause("dep"); - String sql = "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 AND ((id > ?)) ORDER BY id ASC"; + String sql = "SELECT * FROM (SELECT id, name, age FROM foo WHERE bar = 1 GROUP BY dep) AS MAIN_QRY WHERE ((id > ?)) ORDER BY id ASC FETCH FIRST 100 ROWS ONLY"; String s = pagingQueryProvider.generateRemainingPagesQuery(pageSize); assertEquals(sql, s); } @Override String getFirstPageSqlWithMultipleSortKeys() { - return "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 ORDER BY name ASC, id DESC"; + return "SELECT id, name, age FROM foo WHERE bar = 1 ORDER BY name ASC, id DESC FETCH FIRST 100 ROWS ONLY"; } @Override String getRemainingSqlWithMultipleSortKeys() { - return "SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT id, name, age FROM foo WHERE bar = 1 ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 100 AND ((name > ?) OR (name = ? AND id < ?)) ORDER BY name ASC, id DESC"; + return "SELECT id, name, age FROM foo WHERE (bar = 1) AND ((name > ?) OR (name = ? AND id < ?)) ORDER BY name ASC, id DESC FETCH FIRST 100 ROWS ONLY"; } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java index bac04788f5..cd58aecfb9 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlWindowingPagingQueryProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2024 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. @@ -25,6 +25,7 @@ */ class SqlWindowingPagingQueryProviderTests extends AbstractSqlPagingQueryProviderTests { + @SuppressWarnings("removal") SqlWindowingPagingQueryProviderTests() { pagingQueryProvider = new SqlWindowingPagingQueryProvider(); } diff --git a/spring-batch-infrastructure/src/test/resources/org/springframework/batch/item/database/support/query-provider-fixture.sql b/spring-batch-infrastructure/src/test/resources/org/springframework/batch/item/database/support/query-provider-fixture.sql new file mode 100644 index 0000000000..f320010978 --- /dev/null +++ b/spring-batch-infrastructure/src/test/resources/org/springframework/batch/item/database/support/query-provider-fixture.sql @@ -0,0 +1,20 @@ +CREATE TABLE TEST_TABLE ( + ID INTEGER NOT NULL, + STRING VARCHAR(16) NOT NULL +); + +INSERT INTO TEST_TABLE (ID, STRING) VALUES (1, 'Spring'); +INSERT INTO TEST_TABLE (ID, STRING) VALUES (2, 'Batch'); +INSERT INTO TEST_TABLE (ID, STRING) VALUES (3, 'Infrastructure'); + +CREATE TABLE GROUPING_TEST_TABLE ( + ID INTEGER NOT NULL, + STRING VARCHAR(16) NOT NULL +); + +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (1, 'Spring'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (2, 'Batch'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (3, 'Batch'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (4, 'Infrastructure'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (5, 'Infrastructure'); +INSERT INTO GROUPING_TEST_TABLE (ID, STRING) VALUES (6, 'Infrastructure'); \ No newline at end of file From 0c704781605478ef04b4d88feb4d5b67fe553eed Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Sun, 24 Nov 2024 18:19:19 +0100 Subject: [PATCH 134/152] Add integration tests for `PagingQueryProvider` Adds testcontainers based tests for DB2, MySQL, MariaDB, Postgres, Sql Server and Oracle Database, as well as standard tests for HSQL and SQLite. Signed-off-by: Fabrice Bibonne --- spring-batch-infrastructure/pom.xml | 78 +++++++++++++++++ ...ctPagingQueryProviderIntegrationTests.java | 81 ++++++++++++++++++ ...b2PagingQueryProviderIntegrationTests.java | 69 +++++++++++++++ ...byPagingQueryProviderIntegrationTests.java | 84 ++++--------------- ...qlPagingQueryProviderIntegrationTests.java | 50 +++++++++++ ...DBPagingQueryProviderIntegrationTests.java | 65 ++++++++++++++ ...qlPagingQueryProviderIntegrationTests.java | 66 +++++++++++++++ ...lePagingQueryProviderIntegrationTests.java | 75 +++++++++++++++++ ...esPagingQueryProviderIntegrationTests.java | 65 ++++++++++++++ ...erPagingQueryProviderIntegrationTests.java | 66 +++++++++++++++ ...tePagingQueryProviderIntegrationTests.java | 57 +++++++++++++ 11 files changed, 690 insertions(+), 66 deletions(-) create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/AbstractPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/Db2PagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HsqlPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MySqlPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/OraclePagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/PostgresPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProviderIntegrationTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlitePagingQueryProviderIntegrationTests.java diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index e37bec53b2..1823e8b1ff 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -347,6 +347,84 @@ ${derby.version} test + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + com.mysql + mysql-connector-j + ${mysql-connector-j.version} + test + + + org.testcontainers + mysql + ${testcontainers.version} + test + + + org.testcontainers + oracle-xe + ${testcontainers.version} + test + + + com.oracle.database.jdbc + ojdbc10 + ${oracle.version} + test + + + org.mariadb.jdbc + mariadb-java-client + ${mariadb-java-client.version} + test + + + org.testcontainers + mariadb + ${testcontainers.version} + test + + + org.postgresql + postgresql + ${postgresql.version} + test + + + org.testcontainers + postgresql + ${testcontainers.version} + test + + + com.ibm.db2 + jcc + ${db2.version} + test + + + org.testcontainers + db2 + ${testcontainers.version} + test + + + org.testcontainers + mssqlserver + ${testcontainers.version} + test + + + com.microsoft.sqlserver + mssql-jdbc + ${sqlserver.version} + test + com.thoughtworks.xstream xstream diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/AbstractPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/AbstractPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..15f3ced073 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/AbstractPagingQueryProviderIntegrationTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.sql.DataSource; + +import org.junit.jupiter.api.Test; +import org.springframework.batch.item.database.Order; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Henning Pöttker + */ +abstract class AbstractPagingQueryProviderIntegrationTests { + + private final JdbcTemplate jdbcTemplate; + + private final AbstractSqlPagingQueryProvider queryProvider; + + AbstractPagingQueryProviderIntegrationTests(DataSource dataSource, AbstractSqlPagingQueryProvider queryProvider) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + this.queryProvider = queryProvider; + } + + @Test + void testWithoutGrouping() { + queryProvider.setSelectClause("ID, STRING"); + queryProvider.setFromClause("TEST_TABLE"); + Map sortKeys = new HashMap<>(); + sortKeys.put("ID", Order.ASCENDING); + queryProvider.setSortKeys(sortKeys); + + List firstPage = jdbcTemplate.query(queryProvider.generateFirstPageQuery(2), MAPPER); + assertEquals(List.of(new Item(1, "Spring"), new Item(2, "Batch")), firstPage); + + List secondPage = jdbcTemplate.query(queryProvider.generateRemainingPagesQuery(2), MAPPER, 2); + assertEquals(List.of(new Item(3, "Infrastructure")), secondPage); + } + + @Test + void testWithGrouping() { + queryProvider.setSelectClause("STRING"); + queryProvider.setFromClause("GROUPING_TEST_TABLE"); + queryProvider.setGroupClause("STRING"); + Map sortKeys = new HashMap<>(); + sortKeys.put("STRING", Order.ASCENDING); + queryProvider.setSortKeys(sortKeys); + + List firstPage = jdbcTemplate.queryForList(queryProvider.generateFirstPageQuery(2), String.class); + assertEquals(List.of("Batch", "Infrastructure"), firstPage); + + List secondPage = jdbcTemplate.queryForList(queryProvider.generateRemainingPagesQuery(2), String.class, + "Infrastructure"); + assertEquals(List.of("Spring"), secondPage); + } + + private record Item(Integer id, String string) { + } + + private static final RowMapper MAPPER = (rs, rowNum) -> new Item(rs.getInt("id"), rs.getString("string")); + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/Db2PagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/Db2PagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..19d876b9d1 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/Db2PagingQueryProviderIntegrationTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import com.ibm.db2.jcc.DB2SimpleDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.Db2Container; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +class Db2PagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + // TODO find the best way to externalize and manage image versions + private static final DockerImageName DB2_IMAGE = DockerImageName.parse("ibmcom/db2:11.5.5.1"); + + @Container + public static Db2Container db2 = new Db2Container(DB2_IMAGE).acceptLicense(); + + Db2PagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new Db2PagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + DB2SimpleDataSource dataSource = new DB2SimpleDataSource(); + dataSource.setDatabaseName(db2.getDatabaseName()); + dataSource.setUser(db2.getUsername()); + dataSource.setPassword(db2.getPassword()); + dataSource.setDriverType(4); + dataSource.setServerName(db2.getHost()); + dataSource.setPortNumber(db2.getMappedPort(Db2Container.DB2_PORT)); + dataSource.setSslConnection(false); + return dataSource; + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java index fb3820c61c..9a06de9369 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/DerbyPagingQueryProviderIntegrationTests.java @@ -15,84 +15,36 @@ */ package org.springframework.batch.item.database.support; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import javax.sql.DataSource; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.springframework.batch.item.database.Order; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** * @author Henning Pöttker */ -class DerbyPagingQueryProviderIntegrationTests { - - private static EmbeddedDatabase embeddedDatabase; - - private static JdbcTemplate jdbcTemplate; - - @BeforeAll - static void setUp() { - embeddedDatabase = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.DERBY) - .addScript("/org/springframework/batch/item/database/support/query-provider-fixture.sql") - .generateUniqueName(true) - .build(); - jdbcTemplate = new JdbcTemplate(embeddedDatabase); - } - - @AfterAll - static void tearDown() { - if (embeddedDatabase != null) { - embeddedDatabase.shutdown(); - } - } - - @Test - void testWithoutGrouping() { - var queryProvider = new DerbyPagingQueryProvider(); - queryProvider.setSelectClause("ID, STRING"); - queryProvider.setFromClause("TEST_TABLE"); - Map sortKeys = new HashMap<>(); - sortKeys.put("ID", Order.ASCENDING); - queryProvider.setSortKeys(sortKeys); +@SpringJUnitConfig +class DerbyPagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { - List firstPage = jdbcTemplate.query(queryProvider.generateFirstPageQuery(2), MAPPER); - assertEquals(List.of(new Item(1, "Spring"), new Item(2, "Batch")), firstPage); - - List secondPage = jdbcTemplate.query(queryProvider.generateRemainingPagesQuery(2), MAPPER, 2); - assertEquals(List.of(new Item(3, "Infrastructure")), secondPage); + DerbyPagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new DerbyPagingQueryProvider()); } - @Test - void testWithGrouping() { - var queryProvider = new DerbyPagingQueryProvider(); - queryProvider.setSelectClause("STRING"); - queryProvider.setFromClause("GROUPING_TEST_TABLE"); - queryProvider.setGroupClause("STRING"); - Map sortKeys = new HashMap<>(); - sortKeys.put("STRING", Order.ASCENDING); - queryProvider.setSortKeys(sortKeys); - - List firstPage = jdbcTemplate.queryForList(queryProvider.generateFirstPageQuery(2), String.class); - assertEquals(List.of("Batch", "Infrastructure"), firstPage); + @Configuration + static class TestConfiguration { - List secondPage = jdbcTemplate.queryForList(queryProvider.generateRemainingPagesQuery(2), String.class, - "Infrastructure"); - assertEquals(List.of("Spring"), secondPage); - } + @Bean + public DataSource dataSource() throws Exception { + return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.DERBY) + .addScript("/org/springframework/batch/item/database/support/query-provider-fixture.sql") + .generateUniqueName(true) + .build(); + } - private record Item(Integer id, String string) { } - private static final RowMapper MAPPER = (rs, rowNum) -> new Item(rs.getInt("id"), rs.getString("string")); - } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HsqlPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HsqlPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..f0ce2f3821 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/HsqlPagingQueryProviderIntegrationTests.java @@ -0,0 +1,50 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +/** + * @author Henning Pöttker + */ +@SpringJUnitConfig +class HsqlPagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + HsqlPagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new HsqlPagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL) + .addScript("/org/springframework/batch/item/database/support/query-provider-fixture.sql") + .generateUniqueName(true) + .build(); + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..e96aeb1242 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MariaDBPagingQueryProviderIntegrationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import org.mariadb.jdbc.MariaDbDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +class MariaDBPagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + // TODO find the best way to externalize and manage image versions + private static final DockerImageName MARIADB_IMAGE = DockerImageName.parse("mariadb:10.9.3"); + + @Container + public static MariaDBContainer mariaDBContainer = new MariaDBContainer<>(MARIADB_IMAGE); + + MariaDBPagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new MySqlPagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + MariaDbDataSource datasource = new MariaDbDataSource(); + datasource.setUrl(mariaDBContainer.getJdbcUrl()); + datasource.setUser(mariaDBContainer.getUsername()); + datasource.setPassword(mariaDBContainer.getPassword()); + return datasource; + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MySqlPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MySqlPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..4b1da2044b --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/MySqlPagingQueryProviderIntegrationTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import com.mysql.cj.jdbc.MysqlDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +class MySqlPagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + // TODO find the best way to externalize and manage image versions + private static final DockerImageName MYSQL_IMAGE = DockerImageName.parse("mysql:8.0.31"); + + @Container + public static MySQLContainer mysql = new MySQLContainer<>(MYSQL_IMAGE); + + MySqlPagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new MySqlPagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + MysqlDataSource datasource = new MysqlDataSource(); + datasource.setURL(mysql.getJdbcUrl()); + datasource.setUser(mysql.getUsername()); + datasource.setPassword(mysql.getPassword()); + datasource.setUseSSL(false); + return datasource; + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/OraclePagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/OraclePagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..23d767c384 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/OraclePagingQueryProviderIntegrationTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import oracle.jdbc.pool.OracleDataSource; +import org.junit.jupiter.api.Disabled; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.OracleContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * Official Docker images for Oracle are not publicly available. Oracle support is tested + * semi-manually for the moment: 1. Build a docker image for oracle/database:11.2.0.2-xe: + * ... + * 2. Run the test `testJobExecution` + * + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +@Disabled("Official Docker images for Oracle are not publicly available") +class OraclePagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + // TODO find the best way to externalize and manage image versions + private static final DockerImageName ORACLE_IMAGE = DockerImageName.parse("oracle/database:11.2.0.2-xe"); + + @Container + public static OracleContainer oracle = new OracleContainer(ORACLE_IMAGE); + + OraclePagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new OraclePagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + OracleDataSource oracleDataSource = new OracleDataSource(); + oracleDataSource.setUser(oracle.getUsername()); + oracleDataSource.setPassword(oracle.getPassword()); + oracleDataSource.setDatabaseName(oracle.getDatabaseName()); + oracleDataSource.setServerName(oracle.getHost()); + oracleDataSource.setPortNumber(oracle.getOraclePort()); + return oracleDataSource; + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/PostgresPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/PostgresPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..44798f79fa --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/PostgresPagingQueryProviderIntegrationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import org.postgresql.ds.PGSimpleDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +class PostgresPagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + // TODO find the best way to externalize and manage image versions + private static final DockerImageName POSTGRESQL_IMAGE = DockerImageName.parse("postgres:13.3"); + + @Container + public static PostgreSQLContainer postgres = new PostgreSQLContainer<>(POSTGRESQL_IMAGE); + + PostgresPagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new PostgresPagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + PGSimpleDataSource datasource = new PGSimpleDataSource(); + datasource.setURL(postgres.getJdbcUrl()); + datasource.setUser(postgres.getUsername()); + datasource.setPassword(postgres.getPassword()); + return datasource; + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..21bc1eede6 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlServerPagingQueryProviderIntegrationTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import javax.sql.DataSource; + +import com.microsoft.sqlserver.jdbc.SQLServerDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.testcontainers.containers.MSSQLServerContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * @author Henning Pöttker + */ +@Testcontainers(disabledWithoutDocker = true) +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +class SqlServerPagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + // TODO find the best way to externalize and manage image versions + private static final DockerImageName SQLSERVER_IMAGE = DockerImageName + .parse("mcr.microsoft.com/mssql/server:2022-CU14-ubuntu-22.04"); + + @Container + public static MSSQLServerContainer sqlserver = new MSSQLServerContainer<>(SQLSERVER_IMAGE).acceptLicense(); + + SqlServerPagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new SqlServerPagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + SQLServerDataSource dataSource = new SQLServerDataSource(); + dataSource.setUser(sqlserver.getUsername()); + dataSource.setPassword(sqlserver.getPassword()); + dataSource.setURL(sqlserver.getJdbcUrl()); + return dataSource; + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlitePagingQueryProviderIntegrationTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlitePagingQueryProviderIntegrationTests.java new file mode 100644 index 0000000000..db6826c832 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/database/support/SqlitePagingQueryProviderIntegrationTests.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.item.database.support; + +import java.nio.file.Path; +import javax.sql.DataSource; + +import org.junit.jupiter.api.io.TempDir; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.sqlite.SQLiteDataSource; + +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * @author Henning Pöttker + */ +@SpringJUnitConfig +@Sql(scripts = "query-provider-fixture.sql", executionPhase = BEFORE_TEST_CLASS) +class SqlitePagingQueryProviderIntegrationTests extends AbstractPagingQueryProviderIntegrationTests { + + @TempDir + private static Path TEMP_DIR; + + SqlitePagingQueryProviderIntegrationTests(@Autowired DataSource dataSource) { + super(dataSource, new SqlitePagingQueryProvider()); + } + + @Configuration + static class TestConfiguration { + + @Bean + public DataSource dataSource() throws Exception { + SQLiteDataSource dataSource = new SQLiteDataSource(); + dataSource.setUrl("jdbc:sqlite:" + TEMP_DIR.resolve("spring-batch.sqlite")); + return dataSource; + } + + } + +} From f2795781f258377da692c905196165f3868ab78e Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Fri, 6 Dec 2024 17:41:00 +0100 Subject: [PATCH 135/152] Add state reset on close of `AbstractPaginatedDataItemReader` Resolves #1086 Signed-off-by: Fabrice Bibonne --- .../data/AbstractPaginatedDataItemReader.java | 10 +++++++++- .../item/data/MongoPagingItemReaderTests.java | 18 +++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/AbstractPaginatedDataItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/AbstractPaginatedDataItemReader.java index c7982e506d..043e54b7ba 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/AbstractPaginatedDataItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/data/AbstractPaginatedDataItemReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 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. @@ -103,6 +103,14 @@ protected void doOpen() throws Exception { @Override protected void doClose() throws Exception { + this.lock.lock(); + try { + this.page = 0; + this.results = null; + } + finally { + this.lock.unlock(); + } } @Override diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoPagingItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoPagingItemReaderTests.java index 16552fd947..3593ab49cf 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoPagingItemReaderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/data/MongoPagingItemReaderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2023 the original author or authors. + * Copyright 2013-2024 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. @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -34,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -347,4 +349,18 @@ void testSortThrowsExceptionWhenInvokedWithNull() { .withMessage("Sorts must not be null"); } + @Test + void testClose() throws Exception { + // given + when(template.find(any(), any())).thenReturn(List.of("string")); + reader.read(); + + // when + reader.close(); + + // then + assertEquals(0, reader.page); + assertNull(reader.results); + } + } From 5cbc1666837a33bea126eec34c13886f8e70d8a4 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 10:34:42 +0100 Subject: [PATCH 136/152] Add note about not scoping step beans with job scope Resolves #3900 Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- spring-batch-docs/modules/ROOT/pages/step/late-binding.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/step/late-binding.adoc b/spring-batch-docs/modules/ROOT/pages/step/late-binding.adoc index ceb0d390aa..879464ef21 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/late-binding.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/late-binding.adoc @@ -201,8 +201,8 @@ The following example shows how to access the `ExecutionContext` in XML: NOTE: Any bean that uses late binding must be declared with `scope="step"`. See xref:step/late-binding.adoc#step-scope[Step Scope] for more information. -A `Step` bean should not be step-scoped. If late binding is needed in a step -definition, the components of that step (tasklet, item reader or writer, and so on) +A `Step` bean should not be step-scoped or job-scoped. If late binding is needed in a step +definition, then the components of that step (tasklet, item reade/writer, completion policy, and so on) are the ones that should be scoped instead. NOTE: If you use Spring 3.0 (or above), the expressions in step-scoped beans are in the From 7c10fab07cade817ea915d5452d2cb1c221c8a2a Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 12:03:46 +0100 Subject: [PATCH 137/152] Clarify wildcard usage to select input files with Java configuration Resolves #4707 Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- .../ROOT/pages/readers-and-writers/multi-file-input.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/multi-file-input.adoc b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/multi-file-input.adoc index 08307e720d..cf81b7a417 100644 --- a/spring-batch-docs/modules/ROOT/pages/readers-and-writers/multi-file-input.adoc +++ b/spring-batch-docs/modules/ROOT/pages/readers-and-writers/multi-file-input.adoc @@ -24,10 +24,10 @@ The following example shows how to read files with wildcards in Java: [source, java] ---- @Bean -public MultiResourceItemReader multiResourceReader() { +public MultiResourceItemReader multiResourceReader(@Value("classpath:data/input/file-*.txt") Resource[] resources) { return new MultiResourceItemReaderBuilder() .delegate(flatFileItemReader()) - .resources(resources()) + .resources(resources) .build(); } ---- From 10b1571882fb1d1db3da051557227d186ee9abb4 Mon Sep 17 00:00:00 2001 From: Yizheng Wang Date: Tue, 24 Sep 2024 12:44:26 +0800 Subject: [PATCH 138/152] Add unit tests for ExecutionContext#get methods Related to #718 Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- .../batch/item/ExecutionContextTests.java | 76 +++++++++++++++++-- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/ExecutionContextTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/ExecutionContextTests.java index fe4fd5bb76..96e19dfc43 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/ExecutionContextTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/ExecutionContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2023 the original author or authors. + * Copyright 2006-2024 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. @@ -15,18 +15,24 @@ */ package org.springframework.batch.item; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import org.springframework.util.SerializationUtils; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.Serializable; - -import org.junit.jupiter.api.Test; -import org.springframework.util.SerializationUtils; - /** * @author Lucas Ward * @author Mahmoud Ben Hassine @@ -196,4 +202,62 @@ public boolean equals(Object obj) { } + @DisplayName("testGetByType") + @Test + void givenAList_whenGettingAccordingToListType_thenReturnCorrectObject() { + // given - a list + String key = "aListObject"; + List value = List.of("value1", "value2"); + context.put(key, value); + // when - getting according to list type + @SuppressWarnings("unchecked") + List result = (List) context.get(key, List.class); + // then - return the correct list + assertEquals(result, value); + assertEquals(result.get(0), value.get(0)); + assertEquals(result.get(1), value.get(1)); + } + + @DisplayName("testGetNullByDefaultParam") + @Test + void givenANonExistingKey_whenGettingTheNullList_thenReturnNull() { + // given - a non existing key + String key = "aListObjectButNull"; + // when - getting according to the key + @SuppressWarnings("unchecked") + List result = (List) context.get(key, List.class, null); + List result2 = (List) context.get(key, List.class); + // then - return the defined null list + assertNull(result); + assertNull(result2); + } + + @DisplayName("testGetNullByNotNullDefaultParam") + @Test + void givenAnNullList_whenGettingNullWithNonNullDefault_thenReturnDefinedDefaultValue() { + // given - a non existing key + String key = "aListObjectButNull"; + List defaultValue = new ArrayList<>(); + defaultValue.add("value1"); + @SuppressWarnings("unchecked") + // when - getting according to list type and default value + List result = (List) context.get(key, List.class, defaultValue); + // then - return defined default value + assertNotNull(result); + assertEquals(result, defaultValue); + assertEquals(result.get(0), defaultValue.get(0)); + } + + @DisplayName("testGetWithWrongType") + @Test + void givenAList_whenGettingWithWrongType_thenThrowClassCastException() { + // given - another normal list + String key = "anotherListObject"; + List value = List.of("value1", "value2", "value3"); + context.put(key, value); + // when - getting according to map type + // then - throw exception + assertThrows(ClassCastException.class, () -> context.get(key, Map.class)); + } + } From c6e1b653dd675bd0087f774b4038a0baa8a0d132 Mon Sep 17 00:00:00 2001 From: Seungrae Date: Tue, 4 Jun 2024 23:43:19 +0900 Subject: [PATCH 139/152] Fix closing tag for listeners in documentation Signed-off-by: Fabrice Bibonne --- spring-batch-docs/modules/ROOT/pages/job/configuring.adoc | 4 ++-- .../chunk-oriented-processing/inheriting-from-parent.adoc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc b/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc index 57f9bbb48c..b6916acafe 100644 --- a/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc +++ b/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc @@ -251,7 +251,7 @@ it with its own list of listeners to produce a - + @@ -259,7 +259,7 @@ it with its own list of listeners to produce a - + ---- diff --git a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/inheriting-from-parent.adoc b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/inheriting-from-parent.adoc index 00b59bf54a..fd56acbfb9 100644 --- a/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/inheriting-from-parent.adoc +++ b/spring-batch-docs/modules/ROOT/pages/step/chunk-oriented-processing/inheriting-from-parent.adoc @@ -93,7 +93,7 @@ In the following example, the `Step` "concreteStep3", is created with two listen - + @@ -102,7 +102,7 @@ In the following example, the `Step` "concreteStep3", is created with two listen - + ---- From b89933284e225c86c590c78589c0ba58c5318a05 Mon Sep 17 00:00:00 2001 From: Seungrae Date: Tue, 4 Jun 2024 23:52:21 +0900 Subject: [PATCH 140/152] Fix incorrect link Signed-off-by: Fabrice Bibonne --- spring-batch-docs/modules/ROOT/pages/job/configuring.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc b/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc index b6916acafe..c7aaa78828 100644 --- a/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc +++ b/spring-batch-docs/modules/ROOT/pages/job/configuring.adoc @@ -264,7 +264,7 @@ it with its own list of listeners to produce a ---- [role="xmlContent"] -See the section on <> +See the section on xref:step/chunk-oriented-processing/inheriting-from-parent.adoc[Inheriting from a Parent Step] for more detailed information. [[jobparametersvalidator]] From 2f2ddb2fce067e3a177c9fad4dd4275ed92798cf Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 20:18:41 +0100 Subject: [PATCH 141/152] Prepare release 5.2.1 Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- pom.xml | 58 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/pom.xml b/pom.xml index 6955f0060f..db8a32d1ff 100644 --- a/pom.xml +++ b/pom.xml @@ -61,73 +61,73 @@ 17 - 6.2.0 - 2.0.10 - 6.4.0 - 1.14.1 + 6.2.1 + 2.0.11 + 6.4.1 + 1.14.2 - 3.4.0 - 3.4.0 - 3.4.0 - 4.4.0 - 3.3.0 - 3.2.0 - 3.2.8 + 3.4.1 + 3.4.1 + 3.4.1 + 4.4.1 + 3.3.1 + 3.2.1 + 3.2.9 - 2.18.0 + 2.18.2 1.12.0 2.11.0 - 6.6.0.Final + 6.6.3.Final 3.0.0 2.1.3 3.1.0 3.1.0 3.1.0 - 4.0.11 - 5.2.0 - 5.11.1 + 4.0.13 + 5.2.1 + 5.11.4 3.0.2 - 1.4.0 + 1.4.1 1.4.20 4.13.2 ${junit-jupiter.version} 3.0 3.26.3 - 5.14.1 + 5.14.2 2.10.0 - 2.17.0 - 2.12.0 + 2.18.0 + 2.13.0 2.0.16 - 2.7.3 + 2.7.4 2.3.232 - 3.46.1.3 + 3.47.1.0 10.16.1.1 2.21.11 - 2.37.0 + 2.38.0 4.0.5 - 2.23.1 - 8.0.1.Final + 2.24.3 + 8.0.2.Final 6.0.1 4.0.2 2.0.1 4.0.2 2.0.3 - 7.0.0 + 7.1.0 1.9.22.1 - 9.0.0 - 3.4.1 + 9.1.0 + 3.5.1 42.7.4 11.5.9.0 19.24.0.0 11.2.3.jre17 1.3.1 - 1.20.1 + 1.20.4 1.5.3 From 6c860dd9c317f21cb9cfba7040e7a23a142fba0b Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 21:25:24 +0100 Subject: [PATCH 142/152] Update nexus sync action in maven central release process Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- .github/workflows/maven-central-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven-central-release.yml b/.github/workflows/maven-central-release.yml index ff04db2c99..32c08fd2f2 100644 --- a/.github/workflows/maven-central-release.yml +++ b/.github/workflows/maven-central-release.yml @@ -66,7 +66,7 @@ jobs: wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-integration/$RELEASE_VERSION/spring-batch-integration-$RELEASE_VERSION-sources.jar - name: Sign artifacts and release them to Maven Central - uses: spring-io/nexus-sync-action@main + uses: jvalkeal/nexus-sync@v0.0.2 id: nexus with: url: ${{ secrets.OSSRH_URL }} From 48e891e4cc3c4223a98ea90f96b92566e308554d Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 21:28:41 +0100 Subject: [PATCH 143/152] Update maven-central-release.yml Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- .github/workflows/maven-central-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven-central-release.yml b/.github/workflows/maven-central-release.yml index 32c08fd2f2..9d39900983 100644 --- a/.github/workflows/maven-central-release.yml +++ b/.github/workflows/maven-central-release.yml @@ -66,7 +66,7 @@ jobs: wget --user="$ARTIFACTORY_USERNAME" --password="$ARTIFACTORY_PASSWORD" $ARTIFACTORY_URL/spring-batch-integration/$RELEASE_VERSION/spring-batch-integration-$RELEASE_VERSION-sources.jar - name: Sign artifacts and release them to Maven Central - uses: jvalkeal/nexus-sync@v0.0.2 + uses: jvalkeal/nexus-sync@v0 id: nexus with: url: ${{ secrets.OSSRH_URL }} From a405919496105fac3492b7848e1d11db0a2e0689 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 21:34:31 +0100 Subject: [PATCH 144/152] Add pgp signing in maven-central-release.yml Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- .github/workflows/maven-central-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/maven-central-release.yml b/.github/workflows/maven-central-release.yml index 9d39900983..bce60ac9ea 100644 --- a/.github/workflows/maven-central-release.yml +++ b/.github/workflows/maven-central-release.yml @@ -78,3 +78,6 @@ jobs: close: true release: true generate-checksums: true + pgp-sign: true + pgp-sign-passphrase: ${{ secrets.GPG_PASSPHRASE }} + pgp-sign-private-key: ${{ secrets.GPG_PRIVATE_KEY }} From 7f8321442bbef352c20c059ea899100de87896c1 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 21:38:26 +0100 Subject: [PATCH 145/152] Release version 5.2.1 Signed-off-by: Fabrice Bibonne --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index db8a32d1ff..3ad58f51f7 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.1-SNAPSHOT + 5.2.1 pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index 913818fe6e..d5a013c25d 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 33d0eb9890..447807636d 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index 5f6d82fd17..ccaeb1ae84 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index 1823e8b1ff..fc411fe428 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index dbbc1e7c87..05649454c0 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 7bb4812ccc..3fefe28a8c 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index 8ac987b12f..cf8b468ccd 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1-SNAPSHOT + 5.2.1 spring-batch-test Spring Batch Test From f16ac00586265b5382bb11358e7bf7b4da332360 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Wed, 18 Dec 2024 21:38:44 +0100 Subject: [PATCH 146/152] Next development version Signed-off-by: Fabrice Bibonne --- pom.xml | 2 +- spring-batch-bom/pom.xml | 2 +- spring-batch-core/pom.xml | 2 +- spring-batch-docs/pom.xml | 2 +- spring-batch-infrastructure/pom.xml | 2 +- spring-batch-integration/pom.xml | 2 +- spring-batch-samples/pom.xml | 2 +- spring-batch-test/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 3ad58f51f7..348c8d6771 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ designed to enable the development of robust batch applications vital for the daily operations of enterprise systems. Spring Batch is part of the Spring Portfolio. - 5.2.1 + 5.2.2-SNAPSHOT pom https://projects.spring.io/spring-batch diff --git a/spring-batch-bom/pom.xml b/spring-batch-bom/pom.xml index d5a013c25d..a9680681b6 100644 --- a/spring-batch-bom/pom.xml +++ b/spring-batch-bom/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-bom pom diff --git a/spring-batch-core/pom.xml b/spring-batch-core/pom.xml index 447807636d..5a4187bb5d 100644 --- a/spring-batch-core/pom.xml +++ b/spring-batch-core/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-core jar diff --git a/spring-batch-docs/pom.xml b/spring-batch-docs/pom.xml index ccaeb1ae84..d31d9dd8cd 100644 --- a/spring-batch-docs/pom.xml +++ b/spring-batch-docs/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-docs Spring Batch Docs diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index fc411fe428..dd208f8379 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-infrastructure jar diff --git a/spring-batch-integration/pom.xml b/spring-batch-integration/pom.xml index 05649454c0..a57e5f58c5 100644 --- a/spring-batch-integration/pom.xml +++ b/spring-batch-integration/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-integration Spring Batch Integration diff --git a/spring-batch-samples/pom.xml b/spring-batch-samples/pom.xml index 3fefe28a8c..9bf671940f 100644 --- a/spring-batch-samples/pom.xml +++ b/spring-batch-samples/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-samples jar diff --git a/spring-batch-test/pom.xml b/spring-batch-test/pom.xml index cf8b468ccd..ab9e8b20ba 100644 --- a/spring-batch-test/pom.xml +++ b/spring-batch-test/pom.xml @@ -4,7 +4,7 @@ org.springframework.batch spring-batch - 5.2.1 + 5.2.2-SNAPSHOT spring-batch-test Spring Batch Test From 6eb2da766128fc16cb19a99a84b57923dbd53c11 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Thu, 19 Dec 2024 05:59:30 +0100 Subject: [PATCH 147/152] Update latest news in README.md Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7110ad6524..5cc775db30 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ # Latest news +* December 18, 2024: [Spring Batch 5.1.3 and 5.2.1 available now](https://spring.io/blog/2024/12/18/spring-batch-5-1-3-and-5-2-1-available-now) * November 24, 2024: [Bootiful Spring Boot 3.4: Spring Batch](https://spring.io/blog/2024/11/24/bootiful-34-batch) -* November 20, 2024: [Spring Batch 5.2.0 goes GA!](https://spring.io/blog/2024/11/20/spring-batch-5-2-0-goes-ga) -* October 25, 2024: [Spring Batch 5.2.0-RC1 is out!](https://spring.io/blog/2024/10/25/spring-batch-5-2-0-rc1-is-out) -* October 11, 2024: [Spring Batch 5.2.0-M2 is available now!](https://spring.io/blog/2024/10/11/spring-batch-5-2-0-m2-is-available-now) -* September 18, 2024: [Spring Batch 5.2.0-M1 is out!](https://spring.io/blog/2024/09/18/spring-batch-5-2-0-m1-is-out) +* November 20, 2024: [Spring Batch 5.2.0 goes GA!](https://spring.io/blog/2024/11/20/spring-batch-5-2-0-goes-ga) From 89c710009634af501b641a0fbfcc9575a5d6fcf0 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 7 Jan 2025 12:07:32 +0100 Subject: [PATCH 148/152] Add sample job based on PetClinic application Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- spring-batch-samples/README.md | 11 +++ .../batch/samples/petclinic/Owner.java | 19 +++++ .../OwnersExportJobConfiguration.java | 71 ++++++++++++++++++ .../batch/samples/petclinic/README.md | 21 ++++++ .../samples/common/business-schema-hsqldb.sql | 23 ++++++ .../samples/petclinic/job/ownersExportJob.xml | 42 +++++++++++ .../PetClinicJobFunctionalTests.java | 73 +++++++++++++++++++ 7 files changed, 260 insertions(+) create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/Owner.java create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java create mode 100644 spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/README.md create mode 100644 spring-batch-samples/src/main/resources/org/springframework/batch/samples/petclinic/job/ownersExportJob.xml create mode 100644 spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java diff --git a/spring-batch-samples/README.md b/spring-batch-samples/README.md index eb26857fc5..770c7fd938 100644 --- a/spring-batch-samples/README.md +++ b/spring-batch-samples/README.md @@ -61,6 +61,7 @@ The IO Sample Job has a number of special instances that show different IO featu | [multiResource Sample](#multiresource-input-output-job) | x | | | | | | | x | | x | | x | | [XML Input Output Sample](#xml-input-output) | | | x | | | | | | | | | | | [MongoDB sample](#mongodb-sample) | | | | | x | | | | x | | | | +| [PetClinic sample](#petclinic-sample) | | | | | x | x | | | | | | | ### Common Sample Source Structures @@ -615,6 +616,16 @@ $>docker run --name mongodb --rm -d -p 27017:27017 mongo Once MongoDB is up and running, run the `org.springframework.batch.samples.mongodb.MongoDBSampleApp` class without any argument to start the sample. +### PetClinic sample + +This sample uses the [PetClinic Spring application](https://github.com/spring-projects/spring-petclinic) to show how to use +Spring Batch to export data from a relational database table to a flat file. + +The job in this sample is a single-step job that exports data from the `owners` table +to a flat file named `owners.csv`. + +[PetClinic Sample](src/main/java/org/springframework/batch/samples/petclinic/README.md) + ### Adhoc Loop and JMX Sample This job is simply an infinite loop. It runs forever so it is diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/Owner.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/Owner.java new file mode 100644 index 0000000000..7a66d7d296 --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/Owner.java @@ -0,0 +1,19 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.samples.petclinic; + +public record Owner(int id, String firstname, String lastname, String address, String city, String telephone) { +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java new file mode 100644 index 0000000000..4a27ffb23f --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/OwnersExportJobConfiguration.java @@ -0,0 +1,71 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.samples.petclinic; + +import javax.sql.DataSource; + +import org.springframework.batch.core.Job; +import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.job.builder.JobBuilder; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.item.database.JdbcCursorItemReader; +import org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder; +import org.springframework.batch.item.file.FlatFileItemWriter; +import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder; +import org.springframework.batch.samples.common.DataSourceConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.io.FileSystemResource; +import org.springframework.jdbc.core.DataClassRowMapper; +import org.springframework.jdbc.support.JdbcTransactionManager; + +@Configuration +@EnableBatchProcessing +@Import(DataSourceConfiguration.class) +public class OwnersExportJobConfiguration { + + @Bean + public JdbcCursorItemReader ownersReader(DataSource dataSource) { + return new JdbcCursorItemReaderBuilder().name("ownersReader") + .sql("SELECT * FROM OWNERS") + .dataSource(dataSource) + .rowMapper(new DataClassRowMapper<>(Owner.class)) + .build(); + } + + @Bean + public FlatFileItemWriter ownersWriter() { + return new FlatFileItemWriterBuilder().name("ownersWriter") + .resource(new FileSystemResource("owners.csv")) + .delimited() + .names("id", "firstname", "lastname", "address", "city", "telephone") + .build(); + } + + @Bean + public Job job(JobRepository jobRepository, JdbcTransactionManager transactionManager, + JdbcCursorItemReader ownersReader, FlatFileItemWriter ownersWriter) { + return new JobBuilder("ownersExportJob", jobRepository) + .start(new StepBuilder("ownersExportStep", jobRepository).chunk(5, transactionManager) + .reader(ownersReader) + .writer(ownersWriter) + .build()) + .build(); + } + +} diff --git a/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/README.md b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/README.md new file mode 100644 index 0000000000..12be08e09b --- /dev/null +++ b/spring-batch-samples/src/main/java/org/springframework/batch/samples/petclinic/README.md @@ -0,0 +1,21 @@ +# PetClinic Job + +## About the sample + +This sample uses the [PetClinic Spring application](https://github.com/spring-projects/spring-petclinic) to show how to use +Spring Batch to export data from a relational database table to a flat file. + +The job in this sample is a single-step job that exports data from the `owners` table +to a flat file named `owners.csv`. + +## Run the sample + +You can run the sample from the command line as following: + +``` +$>cd spring-batch-samples +# Launch the sample using the XML configuration +$>../mvnw -Dtest=PetClinicJobFunctionalTests#testLaunchJobWithXmlConfiguration test +# Launch the sample using the Java configuration +$>../mvnw -Dtest=PetClinicJobFunctionalTests#testLaunchJobWithJavaConfiguration test +``` \ No newline at end of file diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql index b02b0b89a5..ffd6823049 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql @@ -100,3 +100,26 @@ CREATE TABLE ERROR_LOG ( STEP_NAME CHAR(20) , MESSAGE VARCHAR(300) NOT NULL ) ; + +-- PetClinic sample tables + +CREATE TABLE OWNERS ( + ID INTEGER IDENTITY PRIMARY KEY, + FIRSTNAME VARCHAR(30), + LASTNAME VARCHAR(30), + ADDRESS VARCHAR(255), + CITY VARCHAR(80), + TELEPHONE VARCHAR(20) +); + +INSERT INTO OWNERS VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); +INSERT INTO OWNERS VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); +INSERT INTO OWNERS VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); +INSERT INTO OWNERS VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); +INSERT INTO OWNERS VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); +INSERT INTO OWNERS VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); +INSERT INTO OWNERS VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); +INSERT INTO OWNERS VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); +INSERT INTO OWNERS VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); +INSERT INTO OWNERS VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); + diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/petclinic/job/ownersExportJob.xml b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/petclinic/job/ownersExportJob.xml new file mode 100644 index 0000000000..0247f5511f --- /dev/null +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/petclinic/job/ownersExportJob.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java new file mode 100644 index 0000000000..5f790cba9f --- /dev/null +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.samples.petclinic; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.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.launch.JobLauncher; +import org.springframework.batch.test.JobLauncherTestUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SpringJUnitConfig(locations = { "/simple-job-launcher-context.xml", + "/org/springframework/batch/samples/petclinic/job/ownersExportJob.xml" }) +class PetClinicJobFunctionalTests { + + @Autowired + private JobLauncherTestUtils jobLauncherTestUtils; + + @BeforeEach + public void setup() throws IOException { + Files.deleteIfExists(Paths.get("owners.csv")); + } + + @Test + void testLaunchJobWithXmlConfiguration() throws Exception { + // when + JobExecution jobExecution = jobLauncherTestUtils.launchJob(); + + // then + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + + @Test + void testLaunchJobWithJavaConfiguration() throws Exception { + // given + ApplicationContext context = new AnnotationConfigApplicationContext(OwnersExportJobConfiguration.class); + JobLauncher jobLauncher = context.getBean(JobLauncher.class); + Job job = context.getBean(Job.class); + + // when + JobExecution jobExecution = jobLauncher.run(job, new JobParameters()); + + // then + assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus()); + } + +} From 396eac345b71f6516d601b2737def0f510f8284a Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Tue, 7 Jan 2025 13:20:22 +0100 Subject: [PATCH 149/152] Fix tests Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- .../batch/samples/common/business-schema-hsqldb.sql | 1 + .../batch/samples/petclinic/PetClinicJobFunctionalTests.java | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql index ffd6823049..52a8c890f0 100644 --- a/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql +++ b/spring-batch-samples/src/main/resources/org/springframework/batch/samples/common/business-schema-hsqldb.sql @@ -9,6 +9,7 @@ DROP TABLE PLAYERS IF EXISTS; DROP TABLE GAMES IF EXISTS; DROP TABLE PLAYER_SUMMARY IF EXISTS; DROP TABLE ERROR_LOG IF EXISTS; +DROP TABLE OWNERS IF EXISTS; -- Autogenerated: do not edit this file diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java b/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java index 5f790cba9f..dc8bfce26b 100644 --- a/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java +++ b/spring-batch-samples/src/test/java/org/springframework/batch/samples/petclinic/PetClinicJobFunctionalTests.java @@ -19,6 +19,7 @@ import java.nio.file.Files; import java.nio.file.Paths; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,7 +44,8 @@ class PetClinicJobFunctionalTests { private JobLauncherTestUtils jobLauncherTestUtils; @BeforeEach - public void setup() throws IOException { + @AfterEach + public void deleteOwnersFile() throws IOException { Files.deleteIfExists(Paths.get("owners.csv")); } From 3aaa0df2ef6bec61f76ca905a6e3691de8335cbf Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 24 Jan 2025 17:24:43 +0100 Subject: [PATCH 150/152] Add dco.yml Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- .github/dco.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/dco.yml diff --git a/.github/dco.yml b/.github/dco.yml new file mode 100644 index 0000000000..0c4b142e9a --- /dev/null +++ b/.github/dco.yml @@ -0,0 +1,2 @@ +require: + members: false From 0a530e06e24f568528b5bec65b4cbb54d15b4fdb Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 24 Jan 2025 17:31:48 +0100 Subject: [PATCH 151/152] Update CONTRIBUTING.md to include details about the DCO Signed-off-by: Mahmoud Ben Hassine Signed-off-by: Fabrice Bibonne --- CONTRIBUTING.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 779b711d58..c6ad7d3a70 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,9 +26,11 @@ about how to report issues. Not sure what a *pull request* is, or how to submit one? Take a look at the excellent [GitHub help documentation][] first. Please create a new issue *before* submitting a pull request unless the change is truly trivial, e.g. typo fixes, removing compiler warnings, etc. -### Sign the contributor license agreement +### Sign-off commits according to the Developer Certificate of Origin -If you have not previously done so, please fill out and submit the [Contributor License Agreement](https://cla.pivotal.io/sign/spring). +All commits must include a Signed-off-by trailer at the end of each commit message to indicate that the contributor agrees to the [Developer Certificate of Origin](https://developercertificate.org). + +For additional details, please refer to the blog post [Hello DCO, Goodbye CLA: Simplifying Contributions to Spring](https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring). ### Fork the Repository From 1c28daccf0958e2cdcfd1a784e3f7110e73881e4 Mon Sep 17 00:00:00 2001 From: Mahmoud Ben Hassine Date: Fri, 31 Jan 2025 12:39:36 +0100 Subject: [PATCH 152/152] Add test cases to cover key generation for empty identifying job parameters set Related to #4755 Signed-off-by: Fabrice Bibonne --- .../core/DefaultJobKeyGeneratorTests.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/DefaultJobKeyGeneratorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/DefaultJobKeyGeneratorTests.java index 74e280f1ef..f5e9983011 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/DefaultJobKeyGeneratorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/DefaultJobKeyGeneratorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2025 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. @@ -65,4 +65,22 @@ void testCreateJobKeyOrdering() { assertEquals(key1, key2); } + @Test + public void testCreateJobKeyForEmptyParameters() { + JobParameters jobParameters1 = new JobParameters(); + JobParameters jobParameters2 = new JobParameters(); + String key1 = jobKeyGenerator.generateKey(jobParameters1); + String key2 = jobKeyGenerator.generateKey(jobParameters2); + assertEquals(key1, key2); + } + + @Test + public void testCreateJobKeyForEmptyParametersAndNonIdentifying() { + JobParameters jobParameters1 = new JobParameters(); + JobParameters jobParameters2 = new JobParametersBuilder().addString("name", "foo", false).toJobParameters(); + String key1 = jobKeyGenerator.generateKey(jobParameters1); + String key2 = jobKeyGenerator.generateKey(jobParameters2); + assertEquals(key1, key2); + } + }