From ebe87433e764094014085da1a759dcc5588fd43c Mon Sep 17 00:00:00 2001 From: taskain7 Date: Sat, 15 Oct 2022 05:16:06 +0200 Subject: [PATCH] [FINERACT-1678] Inline COB API --- .../fineract/cob/data/LoanIdsResponseDTO.java | 28 +++ ...AccountLockCannotBeOverruledException.java | 26 +++ ...ner.java => AbstractLoanItemListener.java} | 8 +- .../ChunkProcessingLoanItemListener.java | 35 ++++ .../listener/InlineCOBLoanItemListener.java | 35 ++++ .../cob/loan/AbstractLoanItemProcessor.java | 56 ++++++ .../cob/loan/AbstractLoanItemReader.java | 71 +++++++ .../cob/loan/AbstractLoanItemWriter.java | 46 +++++ .../cob/loan/InlineCOBLoanItemProcessor.java | 35 ++++ .../cob/loan/InlineCOBLoanItemReader.java | 42 +++++ .../cob/loan/InlineCOBLoanItemWriter.java | 34 ++++ ...neLoanCOBBuildExecutionContextTasklet.java | 73 ++++++++ .../fineract/cob/loan/LoanCOBConstant.java | 1 + .../cob/loan/LoanCOBWorkerConfiguration.java | 6 +- .../cob/loan/LoanInlineCOBConfig.java | 119 ++++++++++++ .../fineract/cob/loan/LoanItemProcessor.java | 36 +--- .../fineract/cob/loan/LoanItemReader.java | 49 +---- .../fineract/cob/loan/LoanItemWriter.java | 25 +-- .../BusinessStepConfigUpdateHandler.java | 9 +- .../InlineLoanCOBExecutionDataParser.java | 53 ++++++ .../InlineLoanCOBExecutorServiceImpl.java | 177 ++++++++++++++++++ .../commands/domain/CommandSource.java | 7 + .../commands/domain/CommandWrapper.java | 10 +- .../service/CommandWrapperBuilder.java | 11 +- ...CommandSourceWritePlatformServiceImpl.java | 4 +- .../center/CenterImportHandler.java | 2 +- .../group/GroupImportHandler.java | 2 +- .../infrastructure/core/api/JsonCommand.java | 31 ++- ...tLockCannotBeOverruledExceptionMapper.java | 45 +++++ .../LoanNotFoundExceptionMapper.java | 45 +++++ .../GoogleGsonSerializerHelper.java | 4 + ...auIntegrationWritePlatformServiceImpl.java | 2 +- .../jobs/api/InlineJobResource.java | 69 +++++++ .../jobs/api/InlineJobResourceSwagger.java | 44 +++++ .../jobs/domain/CustomJobParameter.java | 38 ++++ .../domain/CustomJobParameterRepository.java | 23 +++ .../jobs/exception/JobNotFoundException.java | 5 + .../jobs/service/InlineExecutorService.java | 27 +++ .../jobs/service/InlineJobExecuteHandler.java | 42 +++++ .../jobs/service/InlineJobType.java | 43 +++++ .../springbatch/SpringBatchJobConstants.java | 24 +++ .../api/InteropWrapperBuilder.java | 2 +- ...WritePlatformServiceJpaRepositoryImpl.java | 2 +- .../ShareAccountCommandsServiceImpl.java | 2 +- .../ShareProductCommandsServiceImpl.java | 2 +- .../db/changelog/tenant/changelog-tenant.xml | 3 + ...0058_add_execute_inline_cob_permission.xml | 32 ++++ ...59_add_spring_batch_loan_id_list_table.xml | 35 ++++ .../0060_add_job_name_to_command_source.xml | 29 +++ .../LoanItemListenerStepDefinitions.java | 3 +- .../BusinessStepConfigUpdateHandlerTest.java | 80 -------- .../InlineLoanCOBExecutorServiceImplTest.java | 61 ++++++ ...CommandHandlerProviderStepDefinitions.java | 2 +- 53 files changed, 1489 insertions(+), 206 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanIdsResponseDTO.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanAccountLockCannotBeOverruledException.java rename fineract-provider/src/main/java/org/apache/fineract/cob/listener/{LoanItemListener.java => AbstractLoanItemListener.java} (96%) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/listener/ChunkProcessingLoanItemListener.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/listener/InlineCOBLoanItemListener.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemProcessor.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemWriter.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemProcessor.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemWriter.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutionDataParser.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/LoanAccountLockCannotBeOverruledExceptionMapper.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/LoanNotFoundExceptionMapper.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobResource.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobResourceSwagger.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/CustomJobParameter.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/CustomJobParameterRepository.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineExecutorService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineJobExecuteHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineJobType.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/springbatch/SpringBatchJobConstants.java create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0058_add_execute_inline_cob_permission.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0059_add_spring_batch_loan_id_list_table.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0060_add_job_name_to_command_source.xml delete mode 100644 fineract-provider/src/test/java/org/apache/fineract/cob/service/BusinessStepConfigUpdateHandlerTest.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanIdsResponseDTO.java b/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanIdsResponseDTO.java new file mode 100644 index 000000000..76d45c4eb --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/data/LoanIdsResponseDTO.java @@ -0,0 +1,28 @@ +/** + * 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.data; + +import java.util.List; +import lombok.Data; + +@Data +public class LoanIdsResponseDTO { + + private List loanIds; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanAccountLockCannotBeOverruledException.java b/fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanAccountLockCannotBeOverruledException.java new file mode 100644 index 000000000..686ae4c86 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/exceptions/LoanAccountLockCannotBeOverruledException.java @@ -0,0 +1,26 @@ +/** + * 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.exceptions; + +public class LoanAccountLockCannotBeOverruledException extends RuntimeException { + + public LoanAccountLockCannotBeOverruledException(String message) { + super(message); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/LoanItemListener.java b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java similarity index 96% rename from fineract-provider/src/main/java/org/apache/fineract/cob/listener/LoanItemListener.java rename to fineract-provider/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java index 12a55ae4b..e8246ccd8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/LoanItemListener.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/AbstractLoanItemListener.java @@ -44,7 +44,7 @@ import org.springframework.transaction.support.TransactionTemplate; @Slf4j @RequiredArgsConstructor -public class LoanItemListener { +public abstract class AbstractLoanItemListener { private final LoanAccountLockRepository accountLockRepository; @@ -57,8 +57,7 @@ public class LoanItemListener { @Override protected void doInTransactionWithoutResult(@NotNull TransactionStatus status) { for (Long loanId : loanIds) { - Optional loanAccountLock = accountLockRepository.findByLoanIdAndLockOwner(loanId, - LockOwner.LOAN_COB_CHUNK_PROCESSING); + Optional loanAccountLock = accountLockRepository.findByLoanIdAndLockOwner(loanId, getLockOwner()); loanAccountLock.ifPresent( accountLock -> accountLock.setError(String.format(msg, loanId), ThrowableSerialization.serialize(e))); } @@ -101,4 +100,7 @@ public class LoanItemListener { public void onSkipInWrite(@NotNull Loan item, @NotNull Exception e) { log.warn("Skipping was triggered during writing of Loan (id={})", item.getId()); } + + protected abstract LockOwner getLockOwner(); + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/ChunkProcessingLoanItemListener.java b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/ChunkProcessingLoanItemListener.java new file mode 100644 index 000000000..378083684 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/ChunkProcessingLoanItemListener.java @@ -0,0 +1,35 @@ +/** + * 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 org.apache.fineract.cob.domain.LoanAccountLockRepository; +import org.apache.fineract.cob.domain.LockOwner; +import org.springframework.transaction.support.TransactionTemplate; + +public class ChunkProcessingLoanItemListener extends AbstractLoanItemListener { + + public ChunkProcessingLoanItemListener(LoanAccountLockRepository accountLockRepository, TransactionTemplate transactionTemplate) { + super(accountLockRepository, transactionTemplate); + } + + @Override + protected LockOwner getLockOwner() { + return LockOwner.LOAN_COB_CHUNK_PROCESSING; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/listener/InlineCOBLoanItemListener.java b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/InlineCOBLoanItemListener.java new file mode 100644 index 000000000..cedeab776 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/listener/InlineCOBLoanItemListener.java @@ -0,0 +1,35 @@ +/** + * 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 org.apache.fineract.cob.domain.LoanAccountLockRepository; +import org.apache.fineract.cob.domain.LockOwner; +import org.springframework.transaction.support.TransactionTemplate; + +public class InlineCOBLoanItemListener extends AbstractLoanItemListener { + + public InlineCOBLoanItemListener(LoanAccountLockRepository accountLockRepository, TransactionTemplate transactionTemplate) { + super(accountLockRepository, transactionTemplate); + } + + @Override + protected LockOwner getLockOwner() { + return LockOwner.LOAN_INLINE_COB_PROCESSING; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemProcessor.java new file mode 100644 index 000000000..9c936f991 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemProcessor.java @@ -0,0 +1,56 @@ +/** + * 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.loan; + +import java.util.TreeMap; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.jetbrains.annotations.NotNull; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.annotation.AfterStep; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemProcessor; + +@RequiredArgsConstructor +@Slf4j +public abstract class AbstractLoanItemProcessor implements ItemProcessor { + + private final COBBusinessStepService cobBusinessStepService; + + @Setter(AccessLevel.PROTECTED) + private ExecutionContext executionContext; + + @Override + public Loan process(@NotNull Loan item) throws Exception { + TreeMap businessStepMap = (TreeMap) executionContext.get(LoanCOBConstant.BUSINESS_STEP_MAP); + + return cobBusinessStepService.run(businessStepMap, item); + } + + @AfterStep + public ExitStatus afterStep(@NotNull StepExecution stepExecution) { + return ExitStatus.COMPLETED; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java new file mode 100644 index 000000000..3a4499185 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemReader.java @@ -0,0 +1,71 @@ +/** + * 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.loan; + +import java.util.List; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.exceptions.LoanAccountWasAlreadyLocked; +import org.apache.fineract.cob.exceptions.LoanReadException; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; +import org.jetbrains.annotations.NotNull; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.annotation.AfterStep; +import org.springframework.batch.item.ItemReader; + +@Slf4j +@RequiredArgsConstructor +public abstract class AbstractLoanItemReader implements ItemReader { + + private final LoanRepository loanRepository; + + @Setter(AccessLevel.PROTECTED) + private List alreadyLockedAccounts; + @Setter(AccessLevel.PROTECTED) + private List remainingData; + private Long loanId; + + @Override + public Loan read() throws Exception { + try { + if (remainingData.size() > 0) { + loanId = remainingData.remove(0); + if (alreadyLockedAccounts != null && alreadyLockedAccounts.remove(loanId)) { + throw new LoanAccountWasAlreadyLocked(loanId); + } + return loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId)); + } + } catch (Exception e) { + throw new LoanReadException(loanId, e); + } + return null; + + } + + @AfterStep + public ExitStatus afterStep(@NotNull StepExecution stepExecution) { + return ExitStatus.COMPLETED; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemWriter.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemWriter.java new file mode 100644 index 000000000..95dc8953b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AbstractLoanItemWriter.java @@ -0,0 +1,46 @@ +/** + * 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.loan; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.domain.LoanAccountLockRepository; +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.jetbrains.annotations.NotNull; +import org.springframework.batch.item.data.RepositoryItemWriter; + +@Slf4j +@RequiredArgsConstructor +public abstract class AbstractLoanItemWriter extends RepositoryItemWriter { + + private final LoanAccountLockRepository accountLockRepository; + + @Override + public void write(@NotNull List items) throws Exception { + super.write(items); + List loanIds = items.stream().map(AbstractPersistableCustom::getId).toList(); + accountLockRepository.deleteByLoanIdInAndLockOwner(loanIds, getLockOwner()); + } + + protected abstract LockOwner getLockOwner(); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemProcessor.java new file mode 100644 index 000000000..88e365373 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemProcessor.java @@ -0,0 +1,35 @@ +/** + * 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.loan; + +import org.apache.fineract.cob.COBBusinessStepService; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.annotation.BeforeStep; + +public class InlineCOBLoanItemProcessor extends AbstractLoanItemProcessor { + + public InlineCOBLoanItemProcessor(COBBusinessStepService cobBusinessStepService) { + super(cobBusinessStepService); + } + + @BeforeStep + public void beforeStep(StepExecution stepExecution) { + setExecutionContext(stepExecution.getJobExecution().getExecutionContext()); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java new file mode 100644 index 000000000..4da7b1398 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemReader.java @@ -0,0 +1,42 @@ +/** + * 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.loan; + +import java.util.ArrayList; +import java.util.List; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.jetbrains.annotations.NotNull; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.annotation.BeforeStep; +import org.springframework.batch.item.ExecutionContext; + +public class InlineCOBLoanItemReader extends AbstractLoanItemReader { + + public InlineCOBLoanItemReader(LoanRepository loanRepository) { + super(loanRepository); + } + + @BeforeStep + @SuppressWarnings({ "unchecked" }) + public void beforeStep(@NotNull StepExecution stepExecution) { + ExecutionContext executionContext = stepExecution.getJobExecution().getExecutionContext(); + List loanIds = (List) executionContext.get(LoanCOBConstant.LOAN_IDS); + setRemainingData(new ArrayList<>(loanIds)); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemWriter.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemWriter.java new file mode 100644 index 000000000..9092cede0 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineCOBLoanItemWriter.java @@ -0,0 +1,34 @@ +/** + * 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.loan; + +import org.apache.fineract.cob.domain.LoanAccountLockRepository; +import org.apache.fineract.cob.domain.LockOwner; + +public class InlineCOBLoanItemWriter extends AbstractLoanItemWriter { + + public InlineCOBLoanItemWriter(LoanAccountLockRepository accountLockRepository) { + super(accountLockRepository); + } + + @Override + protected LockOwner getLockOwner() { + return LockOwner.LOAN_INLINE_COB_PROCESSING; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java new file mode 100644 index 000000000..37bf40e51 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/InlineLoanCOBBuildExecutionContextTasklet.java @@ -0,0 +1,73 @@ +/** + * 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.loan; + +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; +import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameter; +import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository; +import org.apache.fineract.infrastructure.springbatch.SpringBatchJobConstants; +import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; +import org.springframework.batch.core.StepContribution; +import org.springframework.batch.core.scope.context.ChunkContext; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.repeat.RepeatStatus; +import org.springframework.beans.factory.InitializingBean; + +@Slf4j +@RequiredArgsConstructor +public class InlineLoanCOBBuildExecutionContextTasklet implements Tasklet, InitializingBean { + + private final GoogleGsonSerializerHelper gsonFactory; + private final COBBusinessStepService cobBusinessStepService; + private final CustomJobParameterRepository customJobParameterRepository; + + private Gson gson; + + @Override + public void afterPropertiesSet() throws Exception { + this.gson = gsonFactory.createSimpleGson(); + } + + @Override + public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { + TreeMap cobBusinessStepMap = cobBusinessStepService.getCOBBusinessStepMap(LoanCOBBusinessStep.class, + LoanCOBConstant.LOAN_COB_JOB_NAME); + contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.LOAN_IDS, getLoanIdsFromJobParameters(chunkContext)); + contribution.getStepExecution().getExecutionContext().put(LoanCOBConstant.BUSINESS_STEP_MAP, cobBusinessStepMap); + + return RepeatStatus.FINISHED; + } + + private List getLoanIdsFromJobParameters(ChunkContext chunkContext) { + Long customJobParameterId = (Long) chunkContext.getStepContext().getJobParameters() + .get(SpringBatchJobConstants.CUSTOM_JOB_PARAMETER_ID_KEY); + CustomJobParameter customJobParameter = customJobParameterRepository.findById(customJobParameterId) + .orElseThrow(() -> new LoanNotFoundException(customJobParameterId)); + String parameterJson = customJobParameter.getParameterJson(); + return gson.fromJson(parameterJson, new TypeToken>() {}.getType()); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java index 067eaf05f..ccb889b12 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBConstant.java @@ -27,6 +27,7 @@ public final class LoanCOBConstant { public static final String LOAN_COB_WORKER_STEP = "loanCOBWorkerStep"; public static final String ALREADY_LOCKED_LOAN_IDS = "alreadyLockedLoanIds"; + public static final String INLINE_LOAN_COB_JOB_NAME = "INLINE_LOAN_COB"; private LoanCOBConstant() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java index c74006872..0a3bd5a33 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanCOBWorkerConfiguration.java @@ -21,7 +21,7 @@ package org.apache.fineract.cob.loan; import org.apache.fineract.cob.COBBusinessStepService; import org.apache.fineract.cob.common.InitialisationTasklet; import org.apache.fineract.cob.domain.LoanAccountLockRepository; -import org.apache.fineract.cob.listener.LoanItemListener; +import org.apache.fineract.cob.listener.ChunkProcessingLoanItemListener; import org.apache.fineract.infrastructure.jobs.service.JobName; import org.apache.fineract.infrastructure.springbatch.PropertyService; import org.apache.fineract.portfolio.loanaccount.domain.Loan; @@ -105,8 +105,8 @@ public class LoanCOBWorkerConfiguration { } @Bean - public LoanItemListener loanItemListener() { - return new LoanItemListener(accountLockRepository, transactionTemplate); + public ChunkProcessingLoanItemListener loanItemListener() { + return new ChunkProcessingLoanItemListener(accountLockRepository, transactionTemplate); } @Bean diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java new file mode 100644 index 000000000..cbaa5bc0d --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanInlineCOBConfig.java @@ -0,0 +1,119 @@ +/** + * 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.loan; + +import org.apache.fineract.cob.COBBusinessStepService; +import org.apache.fineract.cob.domain.LoanAccountLockRepository; +import org.apache.fineract.cob.listener.InlineCOBLoanItemListener; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; +import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository; +import org.apache.fineract.infrastructure.jobs.service.JobName; +import org.apache.fineract.infrastructure.springbatch.PropertyService; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; +import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; +import org.springframework.batch.core.launch.support.RunIdIncrementer; +import org.springframework.batch.core.listener.ExecutionContextPromotionListener; +import org.springframework.batch.integration.config.annotation.EnableBatchIntegration; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.support.TransactionTemplate; + +@Configuration +@EnableBatchIntegration +public class LoanInlineCOBConfig { + + @Autowired + private JobBuilderFactory jobBuilderFactory; + @Autowired + private StepBuilderFactory stepBuilderFactory; + @Autowired + private PropertyService propertyService; + @Autowired + private LoanRepository loanRepository; + @Autowired + private COBBusinessStepService cobBusinessStepService; + @Autowired + private LoanAccountLockRepository accountLockRepository; + @Autowired + private TransactionTemplate transactionTemplate; + @Autowired + private CustomJobParameterRepository loanIdListRepository; + @Autowired + private GoogleGsonSerializerHelper googleGsonSerializerHelper; + + @Bean + public InlineLoanCOBBuildExecutionContextTasklet inlineLoanCOBBuildExecutionContextTasklet() { + return new InlineLoanCOBBuildExecutionContextTasklet(googleGsonSerializerHelper, cobBusinessStepService, loanIdListRepository); + } + + @Bean + protected Step inlineCOBBuildExecutionContextStep() { + return stepBuilderFactory.get("Inline COB build execution context step").tasklet(inlineLoanCOBBuildExecutionContextTasklet()) + .listener(inlineCobPromotionListener()).build(); + } + + @Bean + public Step inlineLoanCOBStep() { + return stepBuilderFactory.get("Inline Loan COB Step").chunk(propertyService.getChunkSize(JobName.LOAN_COB.name())) + .reader(inlineCobWorkerItemReader()).processor(inlineCobWorkerItemProcessor()).writer(inlineCobWorkerItemWriter()) + .listener(inlineCobLoanItemListener()).build(); + } + + @Bean(name = "loanInlineCOBJob") + public Job loanInlineCOBJob() { + return jobBuilderFactory.get(LoanCOBConstant.INLINE_LOAN_COB_JOB_NAME) // + .start(inlineCOBBuildExecutionContextStep()).next(inlineLoanCOBStep()) // + .incrementer(new RunIdIncrementer()) // + .build(); + } + + @Bean + public InlineCOBLoanItemReader inlineCobWorkerItemReader() { + return new InlineCOBLoanItemReader(loanRepository); + } + + @Bean + public InlineCOBLoanItemProcessor inlineCobWorkerItemProcessor() { + return new InlineCOBLoanItemProcessor(cobBusinessStepService); + } + + @Bean + public InlineCOBLoanItemWriter inlineCobWorkerItemWriter() { + InlineCOBLoanItemWriter repositoryItemWriter = new InlineCOBLoanItemWriter(accountLockRepository); + repositoryItemWriter.setRepository(loanRepository); + return repositoryItemWriter; + } + + @Bean + public InlineCOBLoanItemListener inlineCobLoanItemListener() { + return new InlineCOBLoanItemListener(accountLockRepository, transactionTemplate); + } + + @Bean + public ExecutionContextPromotionListener inlineCobPromotionListener() { + ExecutionContextPromotionListener listener = new ExecutionContextPromotionListener(); + listener.setKeys(new String[] { LoanCOBConstant.LOAN_IDS, LoanCOBConstant.BUSINESS_STEP_MAP }); + return listener; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemProcessor.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemProcessor.java index bd88f5b15..daea5f970 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemProcessor.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemProcessor.java @@ -18,42 +18,18 @@ */ package org.apache.fineract.cob.loan; -import java.util.TreeMap; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.apache.fineract.cob.COBBusinessStepService; -import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.jetbrains.annotations.NotNull; -import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.annotation.AfterStep; import org.springframework.batch.core.annotation.BeforeStep; -import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.item.ItemProcessor; -@RequiredArgsConstructor -@Slf4j -public class LoanItemProcessor implements ItemProcessor { +public class LoanItemProcessor extends AbstractLoanItemProcessor { - private final COBBusinessStepService cobBusinessStepService; - private StepExecution stepExecution; + public LoanItemProcessor(COBBusinessStepService cobBusinessStepService) { + super(cobBusinessStepService); + } @BeforeStep - public void beforeStep(@NotNull StepExecution stepExecution) { - this.stepExecution = stepExecution; + public void beforeStep(StepExecution stepExecution) { + setExecutionContext(stepExecution.getExecutionContext()); } - - @Override - public Loan process(@NotNull Loan item) throws Exception { - ExecutionContext executionContext = stepExecution.getExecutionContext(); - TreeMap businessStepMap = (TreeMap) executionContext.get(LoanCOBConstant.BUSINESS_STEP_MAP); - - return cobBusinessStepService.run(businessStepMap, item); - } - - @AfterStep - public ExitStatus afterStep(@NotNull StepExecution stepExecution) { - return ExitStatus.COMPLETED; - } - } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java index e9336f00c..8237ba928 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemReader.java @@ -20,59 +20,26 @@ package org.apache.fineract.cob.loan; import java.util.ArrayList; import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.fineract.cob.exceptions.LoanAccountWasAlreadyLocked; -import org.apache.fineract.cob.exceptions.LoanReadException; -import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; -import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; import org.jetbrains.annotations.NotNull; -import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.annotation.AfterStep; import org.springframework.batch.core.annotation.BeforeStep; import org.springframework.batch.item.ExecutionContext; -import org.springframework.batch.item.ItemReader; -@Slf4j -@RequiredArgsConstructor -public class LoanItemReader implements ItemReader { +public class LoanItemReader extends AbstractLoanItemReader { - private final LoanRepository loanRepository; - private List alreadyLockedAccounts; - private List remainingData; - private Long loanId; + public LoanItemReader(LoanRepository loanRepository) { + super(loanRepository); + } @BeforeStep + @SuppressWarnings({ "unchecked" }) public void beforeStep(@NotNull StepExecution stepExecution) { + ExecutionContext executionContext = stepExecution.getExecutionContext(); ExecutionContext jobExecutionContext = stepExecution.getJobExecution().getExecutionContext(); List loanIds = (List) executionContext.get(LoanCOBConstant.LOAN_IDS); - alreadyLockedAccounts = (List) jobExecutionContext.get(LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS); - remainingData = new ArrayList<>(loanIds); - } - - @Override - public Loan read() throws Exception { - try { - if (remainingData.size() > 0) { - loanId = remainingData.remove(0); - if (alreadyLockedAccounts != null && alreadyLockedAccounts.remove(loanId)) { - throw new LoanAccountWasAlreadyLocked(loanId); - } - - return loanRepository.findById(loanId).orElseThrow(() -> new LoanNotFoundException(loanId)); - } - } catch (Exception e) { - throw new LoanReadException(loanId, e); - } - return null; - - } - - @AfterStep - public ExitStatus afterStep(@NotNull StepExecution stepExecution) { - return ExitStatus.COMPLETED; + setAlreadyLockedAccounts((List) jobExecutionContext.get(LoanCOBConstant.ALREADY_LOCKED_LOAN_IDS)); + setRemainingData(new ArrayList<>(loanIds)); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemWriter.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemWriter.java index bb5d2e6e9..709e2bdd8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemWriter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/LoanItemWriter.java @@ -18,28 +18,17 @@ */ package org.apache.fineract.cob.loan; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.apache.fineract.cob.domain.LoanAccountLockRepository; import org.apache.fineract.cob.domain.LockOwner; -import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; -import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.jetbrains.annotations.NotNull; -import org.springframework.batch.item.data.RepositoryItemWriter; -@Slf4j -@RequiredArgsConstructor -public class LoanItemWriter extends RepositoryItemWriter { +public class LoanItemWriter extends AbstractLoanItemWriter { - private final LoanAccountLockRepository accountLockRepository; - - @Override - public void write(@NotNull List items) throws Exception { - super.write(items); - List loanIds = items.stream().map(AbstractPersistableCustom::getId).toList(); - - accountLockRepository.deleteByLoanIdInAndLockOwner(loanIds, LockOwner.LOAN_COB_CHUNK_PROCESSING); + public LoanItemWriter(LoanAccountLockRepository accountLockRepository) { + super(accountLockRepository); } + @Override + protected LockOwner getLockOwner() { + return LockOwner.LOAN_COB_CHUNK_PROCESSING; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/BusinessStepConfigUpdateHandler.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/BusinessStepConfigUpdateHandler.java index c552a3fea..b2922369c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/BusinessStepConfigUpdateHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/BusinessStepConfigUpdateHandler.java @@ -18,8 +18,6 @@ */ package org.apache.fineract.cob.service; -import com.google.common.base.Splitter; -import java.util.List; import lombok.RequiredArgsConstructor; import org.apache.fineract.cob.exceptions.BusinessStepException; import org.apache.fineract.cob.exceptions.BusinessStepNotBelongsToJobException; @@ -40,11 +38,6 @@ public class BusinessStepConfigUpdateHandler implements NewCommandSourceHandler @Override @Transactional public CommandProcessingResult processCommand(JsonCommand command) throws BusinessStepNotBelongsToJobException, BusinessStepException { - List split = Splitter.on("/").splitToList(command.getUrl()); - String jobName = split.get(split.size() - 2); - if ("null".equals(jobName)) { - jobName = null; - } - return configJobParameterService.updateStepConfigByJobName(command, jobName); + return configJobParameterService.updateStepConfigByJobName(command, command.getJobName()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutionDataParser.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutionDataParser.java new file mode 100644 index 000000000..17c4afd32 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutionDataParser.java @@ -0,0 +1,53 @@ +/** + * 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.service; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.util.Arrays; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class InlineLoanCOBExecutionDataParser { + + private final FromJsonHelper jsonHelper; + + public List parseExecution(JsonCommand command) { + JsonObject element = extractJsonObject(command); + String[] loanArray = jsonHelper.extractArrayNamed("loanIds", element); + return Arrays.stream(loanArray).map(Long::parseLong).toList(); + } + + private JsonObject extractJsonObject(JsonCommand command) { + String json = command.json(); + if (StringUtils.isBlank(json)) { + throw new InvalidJsonException(); + } + + final JsonElement element = jsonHelper.parse(json); + return element.getAsJsonObject(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java new file mode 100644 index 000000000..b8e10bfaa --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java @@ -0,0 +1,177 @@ +/** + * 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.service; + +import static org.springframework.transaction.TransactionDefinition.PROPAGATION_REQUIRES_NEW; + +import com.google.gson.Gson; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.cob.domain.LoanAccountLock; +import org.apache.fineract.cob.domain.LoanAccountLockRepository; +import org.apache.fineract.cob.domain.LockOwner; +import org.apache.fineract.cob.exceptions.LoanAccountLockCannotBeOverruledException; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException; +import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; +import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameter; +import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameterRepository; +import org.apache.fineract.infrastructure.jobs.exception.JobNotFoundException; +import org.apache.fineract.infrastructure.jobs.service.InlineExecutorService; +import org.apache.fineract.infrastructure.springbatch.SpringBatchJobConstants; +import org.jetbrains.annotations.NotNull; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.Job; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobParameter; +import org.springframework.batch.core.JobParameters; +import org.springframework.batch.core.JobParametersBuilder; +import org.springframework.batch.core.configuration.JobLocator; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Service; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +@Service +@Slf4j +@RequiredArgsConstructor +public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService, InitializingBean { + + private static final String JOB_EXECUTION_FAILED_MESSAGE = "Job execution failed for job with name: "; + + private final GoogleGsonSerializerHelper gsonFactory; + private final LoanAccountLockRepository loanAccountLockRepository; + private final InlineLoanCOBExecutionDataParser dataParser; + private final JobLauncher jobLauncher; + private final JobLocator jobLocator; + private final JobExplorer jobExplorer; + private final TransactionTemplate transactionTemplate; + private final CustomJobParameterRepository customJobParameterRepository; + + private Gson gson; + + @Override + public void afterPropertiesSet() throws Exception { + this.gson = gsonFactory.createSimpleGson(); + } + + @Override + @Transactional(propagation = Propagation.NOT_SUPPORTED) + public CommandProcessingResult executeInlineJob(JsonCommand command, String jobName) throws LoanAccountLockCannotBeOverruledException { + List loanIds = dataParser.parseExecution(command); + lockLoanAccounts(loanIds); + execute(loanIds, jobName); + return new CommandProcessingResultBuilder().withCommandId(command.commandId()).build(); + } + + private List getLoanAccountLocks(List loanIds) { + List loanAccountLocks = new ArrayList<>(); + List alreadyLockedLoanIds = new ArrayList<>(); + loanIds.forEach(loanId -> { + Optional loanLockOptional = loanAccountLockRepository.findById(loanId); + if (loanLockOptional.isPresent()) { + LoanAccountLock loanAccountLock = loanLockOptional.get(); + if (isLockOverrulable(loanAccountLock)) { + loanAccountLocks.add(loanAccountLock); + } else { + alreadyLockedLoanIds.add(loanId); + } + } else { + loanAccountLocks.add(new LoanAccountLock(loanId, LockOwner.LOAN_INLINE_COB_PROCESSING)); + } + }); + if (!alreadyLockedLoanIds.isEmpty()) { + String message = "There is a hard lock on the loan account without any error, so it can't be overruled."; + String loanIdsMessage = " Locked loan IDs: " + alreadyLockedLoanIds; + throw new LoanAccountLockCannotBeOverruledException(message + loanIdsMessage); + } + + return loanAccountLocks; + } + + private void execute(List loanIds, String jobName) { + Job inlineLoanCOBJob; + try { + inlineLoanCOBJob = jobLocator.getJob(jobName); + } catch (NoSuchJobException e) { + throw new JobNotFoundException(jobName, e); + } + JobParameters jobParameters = new JobParametersBuilder(jobExplorer).getNextJobParameters(inlineLoanCOBJob) + .addJobParameters(new JobParameters(getJobParametersMap(loanIds))).toJobParameters(); + JobExecution jobExecution; + try { + jobExecution = jobLauncher.run(inlineLoanCOBJob, jobParameters); + } catch (Exception e) { + log.error("{}{}", JOB_EXECUTION_FAILED_MESSAGE, jobName, e); + throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", JOB_EXECUTION_FAILED_MESSAGE, jobName, e); + } + if (!BatchStatus.COMPLETED.equals(jobExecution.getStatus())) { + log.error("{}{}", JOB_EXECUTION_FAILED_MESSAGE, jobName); + throw new PlatformInternalServerException("error.msg.sheduler.job.execution.failed", JOB_EXECUTION_FAILED_MESSAGE, jobName); + } + } + + private Map getJobParametersMap(List loanIds) { + // TODO: refactor for a more generic solution + String parameterJson = gson.toJson(loanIds); + CustomJobParameter customJobParameter = new CustomJobParameter(); + customJobParameter.setParameterJson(parameterJson); + Long customJobParameterId = customJobParameterRepository.saveAndFlush(customJobParameter).getId(); + Map jobParameterMap = new HashMap<>(); + jobParameterMap.put(SpringBatchJobConstants.CUSTOM_JOB_PARAMETER_ID_KEY, new JobParameter(customJobParameterId)); + return jobParameterMap; + } + + private void lockLoanAccounts(List loanIds) { + transactionTemplate.setPropagationBehavior(PROPAGATION_REQUIRES_NEW); + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + + @Override + protected void doInTransactionWithoutResult(@NotNull TransactionStatus status) { + List loanAccountLocks = getLoanAccountLocks(loanIds); + loanAccountLocks.forEach(loanAccountLock -> { + loanAccountLock.setNewLockOwner(LockOwner.LOAN_INLINE_COB_PROCESSING); + loanAccountLockRepository.save(loanAccountLock); + }); + } + }); + } + + private boolean isLockOverrulable(LoanAccountLock loanAccountLock) { + if (LockOwner.LOAN_COB_PARTITIONING.equals(loanAccountLock.getLockOwner())) { + return true; + } else { + return StringUtils.isNotBlank(loanAccountLock.getError()); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java index bfd66e18a..60027871f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandSource.java @@ -104,6 +104,9 @@ public class CommandSource extends AbstractPersistableCustom { @Column(name = "organisation_creditbureau_id") private Long organisationCreditBureauId; + @Column(name = "job_name") + private String jobName; + public static CommandSource fullEntryFrom(final CommandWrapper wrapper, final JsonCommand command, final AppUser maker) { return new CommandSource(wrapper.actionName(), wrapper.entityName(), wrapper.getHref(), command.entityId(), command.subentityId(), command.json(), maker); @@ -138,6 +141,10 @@ public class CommandSource extends AbstractPersistableCustom { return this.organisationCreditBureauId; } + public String getJobName() { + return this.jobName; + } + public void setOrganisationCreditBureauId(Long organisationCreditBureauId) { this.organisationCreditBureauId = organisationCreditBureauId; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java index 7dac6ee67..cb09f24f6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/domain/CommandWrapper.java @@ -40,6 +40,7 @@ public class CommandWrapper { private final Long productId; private final Long creditBureauId; private final Long organisationCreditBureauId; + private final String jobName; @SuppressWarnings("unused") private Long templateId; @@ -80,12 +81,13 @@ public class CommandWrapper { this.productId = productId; this.creditBureauId = null; this.organisationCreditBureauId = null; + this.jobName = null; } public CommandWrapper(final Long officeId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String actionName, final String entityName, final Long entityId, final Long subentityId, final String href, final String json, final String transactionId, final Long productId, final Long templateId, final Long creditBureauId, - final Long organisationCreditBureauId) { + final Long organisationCreditBureauId, final String jobName) { this.commandId = null; this.officeId = officeId; @@ -105,6 +107,7 @@ public class CommandWrapper { this.templateId = templateId; this.creditBureauId = creditBureauId; this.organisationCreditBureauId = organisationCreditBureauId; + this.jobName = jobName; } private CommandWrapper(final Long commandId, final String actionName, final String entityName, final Long resourceId, @@ -129,6 +132,7 @@ public class CommandWrapper { this.productId = productId; this.creditBureauId = creditBureauId; this.organisationCreditBureauId = organisationCreditBureauId; + this.jobName = null; } public Long getCreditBureauId() { @@ -338,4 +342,8 @@ public class CommandWrapper { return this.actionName.equalsIgnoreCase("UPDATE") && this.entityName.equalsIgnoreCase("DISBURSEMENTDETAIL") && this.entityId == null; } + + public String getJobName() { + return jobName; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 28d523242..56892ae07 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -44,11 +44,12 @@ public class CommandWrapperBuilder { private Long templateId; private Long creditBureauId; private Long organisationCreditBureauId; + private String jobName; public CommandWrapper build() { return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName, this.entityName, this.entityId, this.subentityId, this.href, this.json, this.transactionId, this.productId, this.templateId, - this.creditBureauId, this.organisationCreditBureauId); + this.creditBureauId, this.organisationCreditBureauId, this.jobName); } public CommandWrapperBuilder updateCreditBureau() { @@ -3562,7 +3563,15 @@ public class CommandWrapperBuilder { this.actionName = "UPDATE"; this.entityName = "BATCH_BUSINESS_STEP"; this.href = "/jobs/" + jobName + "/steps"; + this.jobName = jobName; return this; } + public CommandWrapperBuilder executeInlineJob(String jobName) { + this.actionName = "EXECUTE"; + this.entityName = "INLINE_JOB"; + this.href = "/jobs/" + jobName + "/inline"; + this.jobName = jobName; + return this; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java index 7c8ceb4e9..0552172e7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/PortfolioCommandSourceWritePlatformServiceImpl.java @@ -85,7 +85,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio command = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(), wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(), wrapper.getSavingsId(), wrapper.getTransactionId(), wrapper.getHref(), wrapper.getProductId(), wrapper.getCreditBureauId(), - wrapper.getOrganisationCreditBureauId()); + wrapper.getOrganisationCreditBureauId(), wrapper.getJobName()); while (numberOfRetries <= maxNumberOfRetries) { try { result = this.processAndLogCommandService.executeCommand(wrapper, command, isApprovedByChecker); @@ -137,7 +137,7 @@ public class PortfolioCommandSourceWritePlatformServiceImpl implements Portfolio commandSourceInput.subresourceId(), commandSourceInput.getGroupId(), commandSourceInput.getClientId(), commandSourceInput.getLoanId(), commandSourceInput.getSavingsId(), commandSourceInput.getTransactionId(), commandSourceInput.getResourceGetUrl(), commandSourceInput.getProductId(), commandSourceInput.getCreditBureauId(), - commandSourceInput.getOrganisationCreditBureauId()); + commandSourceInput.getOrganisationCreditBureauId(), commandSourceInput.getJobName()); final boolean makerCheckerApproval = true; return this.processAndLogCommandService.executeCommand(wrapper, command, makerCheckerApproval); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java index c2f0a6a43..d38440250 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/center/CenterImportHandler.java @@ -262,7 +262,7 @@ public class CenterImportHandler implements ImportHandler { String payload = gsonBuilder.create().toJson(calendarData); CommandWrapper commandWrapper = new CommandWrapper(result.getOfficeId(), result.getGroupId(), result.getClientId(), result.getLoanId(), result.getSavingsId(), null, null, null, null, null, payload, result.getTransactionId(), - result.getProductId(), null, null, null); + result.getProductId(), null, null, null, null); final CommandWrapper commandRequest = new CommandWrapperBuilder() // .createCalendar(commandWrapper, TemplatePopulateImportConstants.CENTER_ENTITY_TYPE, result.getGroupId()) // .withJson(payload) // diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java index 3d166ee81..b8835a9a3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/group/GroupImportHandler.java @@ -241,7 +241,7 @@ public class GroupImportHandler implements ImportHandler { String payload = gsonBuilder.create().toJson(calendarData); CommandWrapper commandWrapper = new CommandWrapper(result.getOfficeId(), result.getGroupId(), result.getClientId(), result.getLoanId(), result.getSavingsId(), null, null, null, null, null, payload, result.getTransactionId(), - result.getProductId(), null, null, null); + result.getProductId(), null, null, null, null); final CommandWrapper commandRequest = new CommandWrapperBuilder() // .createCalendar(commandWrapper, TemplatePopulateImportConstants.CENTER_ENTITY_TYPE, result.getGroupId()) // .withJson(payload) // diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java index 31e333935..49ebc937d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/api/JsonCommand.java @@ -64,29 +64,31 @@ public final class JsonCommand { private final Long productId; private final Long creditBureauId; private final Long organisationCreditBureauId; + private final String jobName; public static JsonCommand from(final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String transactionId, final String url, final Long productId, - final Long creditBureauId, final Long organisationCreditBureauId) { + final Long creditBureauId, final Long organisationCreditBureauId, final String jobName) { return new JsonCommand(null, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, groupId, - clientId, loanId, savingsId, transactionId, url, productId, creditBureauId, organisationCreditBureauId); + clientId, loanId, savingsId, transactionId, url, productId, creditBureauId, organisationCreditBureauId, jobName); } public static JsonCommand fromExistingCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId, - final String url, final Long productId, final Long creditBureauId, final Long organisationCreditBureauId) { + final String url, final Long productId, final Long creditBureauId, final Long organisationCreditBureauId, + final String jobName) { return new JsonCommand(commandId, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, null, null, - null, null, null, url, productId, creditBureauId, organisationCreditBureauId); + null, null, null, url, productId, creditBureauId, organisationCreditBureauId, jobName); } public static JsonCommand fromExistingCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String transactionId, final String url, - final Long productId, Long creditBureauId, final Long organisationCreditBureauId) { + final Long productId, Long creditBureauId, final Long organisationCreditBureauId, final String jobName) { return new JsonCommand(commandId, jsonCommand, parsedCommand, fromApiJsonHelper, entityName, resourceId, subresourceId, groupId, - clientId, loanId, savingsId, transactionId, url, productId, creditBureauId, organisationCreditBureauId); + clientId, loanId, savingsId, transactionId, url, productId, creditBureauId, organisationCreditBureauId, jobName); } @@ -94,20 +96,22 @@ public final class JsonCommand { final String jsonCommand = command.fromApiJsonHelper.toJson(parsedCommand); return new JsonCommand(command.commandId, jsonCommand, parsedCommand, command.fromApiJsonHelper, command.entityName, command.resourceId, command.subresourceId, command.groupId, command.clientId, command.loanId, command.savingsId, - command.transactionId, command.url, command.productId, command.creditBureauId, command.organisationCreditBureauId); + command.transactionId, command.url, command.productId, command.creditBureauId, command.organisationCreditBureauId, + command.jobName); } public static JsonCommand fromExistingCommand(JsonCommand command, final JsonElement parsedCommand, final Long clientId) { final String jsonCommand = command.fromApiJsonHelper.toJson(parsedCommand); return new JsonCommand(command.commandId, jsonCommand, parsedCommand, command.fromApiJsonHelper, command.entityName, command.resourceId, command.subresourceId, command.groupId, clientId, command.loanId, command.savingsId, - command.transactionId, command.url, command.productId, command.creditBureauId, command.organisationCreditBureauId); + command.transactionId, command.url, command.productId, command.creditBureauId, command.organisationCreditBureauId, + command.jobName); } public JsonCommand(final Long commandId, final String jsonCommand, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper, final String entityName, final Long resourceId, final Long subresourceId, final Long groupId, final Long clientId, final Long loanId, final Long savingsId, final String transactionId, final String url, - final Long productId, final Long creditBureauId, final Long organisationCreditBureauId) { + final Long productId, final Long creditBureauId, final Long organisationCreditBureauId, final String jobName) { this.commandId = commandId; this.jsonCommand = jsonCommand; @@ -125,6 +129,7 @@ public final class JsonCommand { this.productId = productId; this.creditBureauId = creditBureauId; this.organisationCreditBureauId = organisationCreditBureauId; + this.jobName = jobName; } public static JsonCommand fromJsonElement(final Long resourceId, final JsonElement parsedCommand) { @@ -153,6 +158,7 @@ public final class JsonCommand { this.productId = null; this.creditBureauId = null; this.organisationCreditBureauId = null; + this.jobName = null; } public JsonCommand(final Long resourceId, final JsonElement parsedCommand, final FromJsonHelper fromApiJsonHelper) { @@ -172,10 +178,11 @@ public final class JsonCommand { this.productId = null; this.creditBureauId = null; this.organisationCreditBureauId = null; + this.jobName = null; } public static JsonCommand from(final String jsonCommand) { - return new JsonCommand(null, jsonCommand, null, null, null, null, null, null, null, null, null, null, null, null, null, null); + return new JsonCommand(null, jsonCommand, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); } @@ -255,6 +262,10 @@ public final class JsonCommand { return this.productId; } + public String getJobName() { + return this.jobName; + } + private boolean differenceExists(final TemporalAccessor baseValue, final TemporalAccessor workingCopyValue) { boolean differenceExists = false; diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/LoanAccountLockCannotBeOverruledExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/LoanAccountLockCannotBeOverruledExceptionMapper.java new file mode 100644 index 000000000..483f13c50 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/LoanAccountLockCannotBeOverruledExceptionMapper.java @@ -0,0 +1,45 @@ +/** + * 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.infrastructure.core.exceptionmapper; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.cob.exceptions.LoanAccountLockCannotBeOverruledException; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.springframework.stereotype.Component; + +@Provider +@Component +@Slf4j +public class LoanAccountLockCannotBeOverruledExceptionMapper implements ExceptionMapper { + + @Override + public Response toResponse(LoanAccountLockCannotBeOverruledException exception) { + final String globalisationMessageCode = "error.msg.invalid.request.body"; + final String defaultUserMessage = exception.getMessage(); + log.warn("Exception: {}, Message: {}", exception.getClass().getName(), defaultUserMessage); + + final ApiParameterError error = ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage); + + return Response.status(Response.Status.CONFLICT).entity(error).type(MediaType.APPLICATION_JSON).build(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/LoanNotFoundExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/LoanNotFoundExceptionMapper.java new file mode 100644 index 000000000..45f0dcb42 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/LoanNotFoundExceptionMapper.java @@ -0,0 +1,45 @@ +/** + * 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.infrastructure.core.exceptionmapper; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; +import org.springframework.stereotype.Component; + +@Provider +@Component +@Slf4j +public class LoanNotFoundExceptionMapper implements ExceptionMapper { + + @Override + public Response toResponse(LoanNotFoundException exception) { + final String globalisationMessageCode = "error.msg.invalid.request.body"; + final String defaultUserMessage = exception.getMessage(); + log.warn("Exception: {}, Message: {}", exception.getClass().getName(), defaultUserMessage); + + final ApiParameterError error = ApiParameterError.generalError(globalisationMessageCode, defaultUserMessage); + + return Response.status(Response.Status.NOT_FOUND).entity(error).type(MediaType.APPLICATION_JSON).build(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java index b1af11537..1d561e873 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/serialization/GoogleGsonSerializerHelper.java @@ -96,6 +96,10 @@ public final class GoogleGsonSerializerHelper { return serializer.toJson(singleDataObject); } + public Gson createSimpleGson() { + return createGsonBuilder().create(); + } + public static GsonBuilder createGsonBuilder() { return createGsonBuilder(false); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java index 9de336bbb..f4ff81dcc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/creditbureau/service/ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl.java @@ -450,7 +450,7 @@ public class ThitsaWorksCreditBureauIntegrationWritePlatformServiceImpl implemen apicommand = JsonCommand.from(json, parsedCommand, this.fromApiJsonHelper, wrapper.getEntityName(), wrapper.getEntityId(), wrapper.getSubentityId(), wrapper.getGroupId(), wrapper.getClientId(), wrapper.getLoanId(), wrapper.getSavingsId(), wrapper.getTransactionId(), wrapper.getHref(), wrapper.getProductId(), wrapper.getCreditBureauId(), - wrapper.getOrganisationCreditBureauId()); + wrapper.getOrganisationCreditBureauId(), wrapper.getJobName()); this.fromApiJsonDeserializer.validateForCreate(apicommand.json()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobResource.java new file mode 100644 index 000000000..a931b4390 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobResource.java @@ -0,0 +1,69 @@ +/** + * 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.infrastructure.jobs.api; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.cob.data.LoanIdsResponseDTO; +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.commands.service.CommandWrapperBuilder; +import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.springframework.stereotype.Component; + +@Path("/jobs") +@Component +@Tag(name = "Inline Job", description = "") +@RequiredArgsConstructor +public class InlineJobResource { + + private final PortfolioCommandSourceWritePlatformService commandWritePlatformService; + private final DefaultToApiJsonSerializer serializer; + + @POST + @Path("{jobName}/inline") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Starts an inline Job", description = "Starts an inline Job") + @RequestBody(content = @Content(schema = @Schema(implementation = InlineJobResourceSwagger.InlineJobRequest.class))) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = InlineJobResourceSwagger.InlineJobResponse.class)))) }) + public String executeInlineJob(@PathParam("jobName") @Parameter(description = "jobName") final String jobName, + @Parameter(hidden = true) final String jsonRequestBody) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder().executeInlineJob(jobName).withJson(jsonRequestBody).build(); + CommandProcessingResult result = commandWritePlatformService.logCommandSource(commandRequest); + return serializer.serialize(result); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobResourceSwagger.java new file mode 100644 index 000000000..5eccec61e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobResourceSwagger.java @@ -0,0 +1,44 @@ +/** + * 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.infrastructure.jobs.api; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + +final class InlineJobResourceSwagger { + + private InlineJobResourceSwagger() {} + + @Schema(description = "InlineJobRequest") + public static final class InlineJobRequest { + + private InlineJobRequest() {} + + public List loanIds; + } + + @Schema(description = "InlineJobResponse") + public static final class InlineJobResponse { + + private InlineJobResponse() {} + + public List loanIds; + + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/CustomJobParameter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/CustomJobParameter.java new file mode 100644 index 000000000..11a8fd9dd --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/CustomJobParameter.java @@ -0,0 +1,38 @@ +/** + * 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.infrastructure.jobs.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; + +@Entity +@Table(name = "batch_custom_job_parameters") +@NoArgsConstructor +@Getter +@Setter +public class CustomJobParameter extends AbstractPersistableCustom { + + @Column(name = "parameter_json", nullable = false) + private String parameterJson; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/CustomJobParameterRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/CustomJobParameterRepository.java new file mode 100644 index 000000000..2cb820e0e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/domain/CustomJobParameterRepository.java @@ -0,0 +1,23 @@ +/** + * 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.infrastructure.jobs.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CustomJobParameterRepository extends JpaRepository {} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobNotFoundException.java index b4bd7e589..becff1983 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobNotFoundException.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/exception/JobNotFoundException.java @@ -19,6 +19,7 @@ package org.apache.fineract.infrastructure.jobs.exception; import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; +import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.dao.EmptyResultDataAccessException; /** @@ -30,6 +31,10 @@ public class JobNotFoundException extends AbstractPlatformResourceNotFoundExcept super("error.msg.sheduler.job.id.invalid", "Job with identifier " + identifier + " does not exist", identifier); } + public JobNotFoundException(final String identifier, NoSuchJobException e) { + super("error.msg.sheduler.job.id.invalid", "Job with identifier " + identifier + " does not exist", identifier, e); + } + public JobNotFoundException(String identifier, EmptyResultDataAccessException e) { super("error.msg.sheduler.job.id.invalid", "Job with identifier " + identifier + " does not exist", identifier, e); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineExecutorService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineExecutorService.java new file mode 100644 index 000000000..2b96a1b3a --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineExecutorService.java @@ -0,0 +1,27 @@ +/** + * 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.infrastructure.jobs.service; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; + +public interface InlineExecutorService { + + CommandProcessingResult executeInlineJob(JsonCommand command, String jobName); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineJobExecuteHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineJobExecuteHandler.java new file mode 100644 index 000000000..aef62e1c4 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineJobExecuteHandler.java @@ -0,0 +1,42 @@ +/** + * 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.infrastructure.jobs.service; + +import lombok.RequiredArgsConstructor; +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +@CommandType(entity = "INLINE_JOB", action = "EXECUTE") +public class InlineJobExecuteHandler implements NewCommandSourceHandler { + + private final ApplicationContext applicationContext; + + @Override + public CommandProcessingResult processCommand(JsonCommand command) { + InlineJobType inlineJobType = InlineJobType.getInlineJobType(command.getJobName()); + InlineExecutorService inlineJobExecutorService = applicationContext.getBean(inlineJobType.getExecutorServiceClass()); + return inlineJobExecutorService.executeInlineJob(command, inlineJobType.getInlineJobName()); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineJobType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineJobType.java new file mode 100644 index 000000000..e8b2ce18c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/InlineJobType.java @@ -0,0 +1,43 @@ +/** + * 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.infrastructure.jobs.service; + +import java.util.Arrays; +import java.util.Optional; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.cob.service.InlineLoanCOBExecutorServiceImpl; + +@RequiredArgsConstructor +public enum InlineJobType { + + LOAN_COB("LOAN_COB", "INLINE_LOAN_COB", InlineLoanCOBExecutorServiceImpl.class); + + private final String jobName; + @Getter + private final String inlineJobName; + @Getter + private final Class executorServiceClass; + + public static InlineJobType getInlineJobType(String jobName) { + Optional optionalInlineJobType = Arrays.stream(InlineJobType.values()) + .filter(inlineCOBType -> jobName.equals(inlineCOBType.jobName)).findAny(); + return optionalInlineJobType.orElseThrow(() -> new IllegalArgumentException("Inline Job is not found by job name: " + jobName)); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/springbatch/SpringBatchJobConstants.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/springbatch/SpringBatchJobConstants.java new file mode 100644 index 000000000..016e9ba15 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/springbatch/SpringBatchJobConstants.java @@ -0,0 +1,24 @@ +/** + * 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.infrastructure.springbatch; + +public abstract class SpringBatchJobConstants { + + public static final String CUSTOM_JOB_PARAMETER_ID_KEY = "CUSTOM_JOB_PARAMETER_ID"; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java index fd92896fc..0f847dabf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/interoperation/api/InteropWrapperBuilder.java @@ -37,7 +37,7 @@ public class InteropWrapperBuilder { public CommandWrapper build() { return new CommandWrapper(null, null, null, null, null, actionName, entityName, null, null, href, json, null, null, null, null, - null); + null, null); } public InteropWrapperBuilder withJson(final String json) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index ee6ba0cd8..07f22ce99 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -2857,7 +2857,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final JsonElement parsedCommand = this.fromApiJsonHelper.parse(overdueInstallment.toString()); final JsonCommand command = JsonCommand.from(overdueInstallment.toString(), parsedCommand, this.fromApiJsonHelper, null, null, - null, null, null, loanId, null, null, null, null, null, null); + null, null, null, loanId, null, null, null, null, null, null, null); LoanOverdueDTO overdueDTO = applyChargeToOverdueLoanInstallment(loan, overdueInstallment.getChargeId(), overdueInstallment.getPeriodNumber(), command); loan = overdueDTO.getLoan(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountCommandsServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountCommandsServiceImpl.java index f56785424..04fdadb3f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountCommandsServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountCommandsServiceImpl.java @@ -46,7 +46,7 @@ public class ShareAccountCommandsServiceImpl implements AccountsCommandsService public Object handleCommand(Long accountId, String command, String jsonBody) { final JsonElement parsedCommand = this.fromApiJsonHelper.parse(jsonBody); final JsonCommand jsonCommand = JsonCommand.from(jsonBody, parsedCommand, this.fromApiJsonHelper, null, null, null, null, null, - null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null); if (ShareAccountApiConstants.APPROVE_COMMAND.equals(command)) { return approveShareAccount(accountId, jsonCommand); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductCommandsServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductCommandsServiceImpl.java index d0b765381..52c6bf3e8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductCommandsServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareproducts/service/ShareProductCommandsServiceImpl.java @@ -45,7 +45,7 @@ public class ShareProductCommandsServiceImpl implements ProductCommandsService { public Object handleCommand(Long productId, String command, String jsonBody) { final JsonElement parsedCommand = this.fromApiJsonHelper.parse(jsonBody); final JsonCommand jsonCommand = JsonCommand.from(jsonBody, parsedCommand, this.fromApiJsonHelper, null, null, null, null, null, - null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null); if (ShareProductApiConstants.PREIEW_DIVIDENDS_COMMAND_STRING.equals(command)) { return null; } else if (ShareProductApiConstants.POST_DIVIDENdS_COMMAND_STRING.equals(command)) { diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index d4d072406..31941ab88 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -77,4 +77,7 @@ + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0058_add_execute_inline_cob_permission.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0058_add_execute_inline_cob_permission.xml new file mode 100644 index 000000000..69ee92526 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0058_add_execute_inline_cob_permission.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0059_add_spring_batch_loan_id_list_table.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0059_add_spring_batch_loan_id_list_table.xml new file mode 100644 index 000000000..494891018 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0059_add_spring_batch_loan_id_list_table.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0060_add_job_name_to_command_source.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0060_add_job_name_to_command_source.xml new file mode 100644 index 000000000..245a2daa9 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0060_add_job_name_to_command_source.xml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/listener/LoanItemListenerStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/cob/listener/LoanItemListenerStepDefinitions.java index 757669ff2..b855d7060 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/listener/LoanItemListenerStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/listener/LoanItemListenerStepDefinitions.java @@ -46,7 +46,8 @@ public class LoanItemListenerStepDefinitions implements En { private LoanAccountLockRepository accountLockRepository = mock(LoanAccountLockRepository.class); private TransactionTemplate transactionTemplate = spy(TransactionTemplate.class); - private LoanItemListener loanItemListener = new LoanItemListener(accountLockRepository, transactionTemplate); + private ChunkProcessingLoanItemListener loanItemListener = new ChunkProcessingLoanItemListener(accountLockRepository, + transactionTemplate); private Exception exception; diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/service/BusinessStepConfigUpdateHandlerTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/service/BusinessStepConfigUpdateHandlerTest.java deleted file mode 100644 index c20ab8948..000000000 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/service/BusinessStepConfigUpdateHandlerTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/** - * 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.service; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; - -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -class BusinessStepConfigUpdateHandlerTest { - - @InjectMocks - private BusinessStepConfigUpdateHandler testObj; - - @Mock - private ConfigJobParameterService configJobParameterService; - - @Captor - private ArgumentCaptor argumentCaptor; - - @Test - void shouldProcessCommandGetsJobName() { - JsonCommand command = mock(JsonCommand.class); - given(command.getUrl()).willReturn("/jobs/jobName/steps"); - testObj.processCommand(command); - - verify(configJobParameterService).updateStepConfigByJobName(eq(command), argumentCaptor.capture()); - assertEquals("jobName", argumentCaptor.getValue()); - } - - @Test - void shouldProcessCommandGetsJobNameEmptyString() { - JsonCommand command = mock(JsonCommand.class); - given(command.getUrl()).willReturn("/jobs//steps"); - testObj.processCommand(command); - - verify(configJobParameterService).updateStepConfigByJobName(eq(command), argumentCaptor.capture()); - assertEquals("", argumentCaptor.getValue()); - } - - @Test - void shouldProcessCommandGetsJobNameNull() { - JsonCommand command = mock(JsonCommand.class); - given(command.getUrl()).willReturn("/jobs/" + null + "/steps"); - testObj.processCommand(command); - - verify(configJobParameterService).updateStepConfigByJobName(eq(command), argumentCaptor.capture()); - assertEquals(null, argumentCaptor.getValue()); - } -} diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java new file mode 100644 index 000000000..00ee9da1f --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java @@ -0,0 +1,61 @@ +/** + * 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.service; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; +import org.apache.fineract.cob.exceptions.LoanAccountLockCannotBeOverruledException; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.transaction.support.TransactionTemplate; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class InlineLoanCOBExecutorServiceImplTest { + + @InjectMocks + private InlineLoanCOBExecutorServiceImpl testObj; + @Mock + private TransactionTemplate transactionTemplate; + + @Mock + private InlineLoanCOBExecutionDataParser dataParser; + + @Test + void shouldExceptionThrownIfLoanIsAlreadyLocked() { + JsonCommand command = mock(JsonCommand.class); + ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null)); + + when(dataParser.parseExecution(any())).thenReturn(List.of(3L)); + when(transactionTemplate.execute(any())).thenThrow(new LoanAccountLockCannotBeOverruledException("")); + assertThrows(LoanAccountLockCannotBeOverruledException.class, () -> testObj.executeInlineJob(command, "INLINE_LOAN_COB")); + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerProviderStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerProviderStepDefinitions.java index dc865c20a..a422ef191 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerProviderStepDefinitions.java +++ b/fineract-provider/src/test/java/org/apache/fineract/commands/provider/CommandHandlerProviderStepDefinitions.java @@ -42,7 +42,7 @@ public class CommandHandlerProviderStepDefinitions implements En { When("The user processes the command with ID {long}", (Long id) -> { this.result = commandHandler - .processCommand(JsonCommand.fromExistingCommand(id, null, null, null, null, null, null, null, null, null, null)); + .processCommand(JsonCommand.fromExistingCommand(id, null, null, null, null, null, null, null, null, null, null, null)); }); Then("The command ID matches {long}", (Long id) -> {