From 82d9c15b5ad6387454d1e08476b1e48f7e2976a9 Mon Sep 17 00:00:00 2001 From: Henning Poettker Date: Fri, 29 Nov 2024 10:52:01 +0100 Subject: [PATCH] Fix job execution retrieval by id for MongoDB Resolves #4722 Signed-off-by: Mahmoud Ben Hassine --- .../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); + } + + } + +}