From 7b058d6772120b4586ed2c2d6252468f2ec5a609 Mon Sep 17 00:00:00 2001 From: Andrii Kulminskyi Date: Fri, 13 Sep 2024 09:27:57 +0300 Subject: [PATCH] FINERACT-2067: NPE business date partition step COB --- .../JobExecutionContextCopyListener.java | 60 +++++++++++ .../cob/loan/LoanCOBManagerConfiguration.java | 8 +- .../fineract/cob/loan/LoanCOBPartitioner.java | 4 +- .../JobExecutionContextCopyListenerTest.java | 99 +++++++++++++++++++ 4 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/listener/JobExecutionContextCopyListener.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/cob/listener/JobExecutionContextCopyListenerTest.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/JobExecutionContextCopyListener.java b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/JobExecutionContextCopyListener.java new file mode 100644 index 000000000..021ff8884 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/JobExecutionContextCopyListener.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.listener; + +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.BooleanUtils; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.StepExecutionListener; +import org.springframework.batch.item.ExecutionContext; + +/** + * {@link StepExecutionListener} to copy values from Job execution context into Step execution context. + */ +@Slf4j +public class JobExecutionContextCopyListener implements StepExecutionListener { + + private final List stepExecutionKeys; + + public JobExecutionContextCopyListener(List stepExecutionKeys) { + this.stepExecutionKeys = stepExecutionKeys; + } + + /** + * Method to copy values from Job execution context into Step execution context before step execution. + * + * @param stepExecution + * the step to be executed. + */ + @Override + public void beforeStep(final StepExecution stepExecution) { + log.debug("Before step: copying job execution context to step [{}]", stepExecution.getStepName()); + + final ExecutionContext stepExecutionContext = stepExecution.getExecutionContext(); + final ExecutionContext jobExecutionContext = stepExecution.getJobExecution().getExecutionContext(); + + jobExecutionContext.entrySet().forEach(jobExecutionContextEntry -> { + if (stepExecutionKeys.contains(jobExecutionContextEntry.getKey()) + && BooleanUtils.isFalse(stepExecutionContext.containsKey(jobExecutionContextEntry.getKey()))) { + stepExecutionContext.put(jobExecutionContextEntry.getKey(), jobExecutionContextEntry.getValue()); + } + }); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java index fd04315e3..4cbbd6df0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBManagerConfiguration.java @@ -20,16 +20,19 @@ package org.apache.fineract.cob.loan; import static org.apache.fineract.cob.loan.LoanCOBConstant.JOB_NAME; +import java.util.List; import org.apache.fineract.cob.COBBusinessStepService; import org.apache.fineract.cob.common.CustomJobParameterResolver; import org.apache.fineract.cob.conditions.BatchManagerCondition; import org.apache.fineract.cob.listener.COBExecutionListenerRunner; +import org.apache.fineract.cob.listener.JobExecutionContextCopyListener; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.infrastructure.jobs.service.JobName; import org.apache.fineract.infrastructure.springbatch.PropertyService; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.JobScope; +import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.launch.JobOperator; @@ -78,7 +81,7 @@ public class LoanCOBManagerConfiguration { private CustomJobParameterResolver customJobParameterResolver; @Bean - @JobScope + @StepScope public LoanCOBPartitioner partitioner() { return new LoanCOBPartitioner(propertyService, cobBusinessStepService, retrieveLoanIdService, jobOperator, jobExplorer, LoanCOBConstant.NUMBER_OF_DAYS_BEHIND); @@ -88,7 +91,8 @@ public class LoanCOBManagerConfiguration { public Step loanCOBStep() { return stepBuilderFactory.get(LoanCOBConstant.LOAN_COB_PARTITIONER_STEP) .partitioner(LoanCOBConstant.LOAN_COB_WORKER_STEP, partitioner()).pollInterval(propertyService.getPollInterval(JOB_NAME)) - .outputChannel(outboundRequests).build(); + .listener(new JobExecutionContextCopyListener(List.of("BusinessDate", "IS_CATCH_UP"))).outputChannel(outboundRequests) + .build(); } @Bean diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java index 4b405bf21..508e9d089 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBPartitioner.java @@ -58,10 +58,10 @@ public class LoanCOBPartitioner implements Partitioner { private final Long numberOfDays; - @Value("#{jobExecutionContext['BusinessDate']}") + @Value("#{stepExecutionContext['BusinessDate']}") @Setter private LocalDate businessDate; - @Value("#{jobExecutionContext['IS_CATCH_UP']}") + @Value("#{stepExecutionContext['IS_CATCH_UP']}") @Setter private Boolean isCatchUp; diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/listener/JobExecutionContextCopyListenerTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/listener/JobExecutionContextCopyListenerTest.java new file mode 100644 index 000000000..a3de55342 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/listener/JobExecutionContextCopyListenerTest.java @@ -0,0 +1,99 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.cob.listener; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.item.ExecutionContext; + +@ExtendWith(MockitoExtension.class) +public class JobExecutionContextCopyListenerTest { + + private static final List STEP_EXECUTION_KEYS = List.of("BusinessDate", "IS_CATCH_UP"); + + private final JobExecutionContextCopyListener jobExecutionContextCopyListener = new JobExecutionContextCopyListener( + List.of("BusinessDate", "IS_CATCH_UP")); + + @Test + public void verifyJobExecutionContextMapIsCopiedToEmptyStepExecutionContextMap() { + Map jobExecutionContextMap = createRandomHashmap(); + jobExecutionContextMap.put("BusinessDate", "value"); + jobExecutionContextMap.put("IS_CATCH_UP", "value"); + final JobExecution jobExecution = new JobExecution(1L); + jobExecution.setExecutionContext(new ExecutionContext(jobExecutionContextMap)); + final StepExecution stepExecution = new StepExecution("someStep", jobExecution); + + jobExecutionContextCopyListener.beforeStep(stepExecution); + + assertEquals(stepExecution.getExecutionContext().size(), 2); + stepExecution.getExecutionContext().toMap().forEach((key, value) -> { + assertTrue(STEP_EXECUTION_KEYS.contains(key)); + assertEquals(value, jobExecutionContextMap.get(key)); + }); + } + + @Test + public void verifyStepExecutionContextMapNotOverwritten() { + Map jobExecutionContextMap = new HashMap<>(); + jobExecutionContextMap.put("BusinessDate", "BusinessDate value"); + jobExecutionContextMap.put("IS_CATCH_UP", "IS_CATCH_UP value"); + Map expectedStepExecutionContextMap = new HashMap<>(); + expectedStepExecutionContextMap.put("BusinessDate", "BusinessDate anotherValue"); + expectedStepExecutionContextMap.put("IS_CATCH_UP", "IS_CATCH_UP anotherValue"); + + final JobExecution jobExecution = new JobExecution(1L); + jobExecution.setExecutionContext(new ExecutionContext(jobExecutionContextMap)); + final StepExecution actualStepExecution = new StepExecution("someStep", jobExecution); + actualStepExecution.setExecutionContext(new ExecutionContext(expectedStepExecutionContextMap)); + + jobExecutionContextCopyListener.beforeStep(actualStepExecution); + + assertEquals(actualStepExecution.getExecutionContext().size(), STEP_EXECUTION_KEYS.size()); + actualStepExecution.getExecutionContext().toMap().forEach((key, value) -> { + assertTrue(STEP_EXECUTION_KEYS.contains(key)); + assertEquals(value, expectedStepExecutionContextMap.get(key)); + }); + } + + /** + * Helper method to create a random Hash Map. + * + * @return the random Hash Map. + */ + private Map createRandomHashmap() { + final Random random = new Random(); + final Map map = new HashMap<>(); + for (int i = 0; i < random.nextInt(50); i++) { + map.put(RandomStringUtils.randomAlphanumeric(5), RandomStringUtils.randomAlphanumeric(5)); + } + + return map; + } +}