diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle index 18a5f5264..459a89306 100644 --- a/fineract-provider/dependencies.gradle +++ b/fineract-provider/dependencies.gradle @@ -166,5 +166,6 @@ dependencies { exclude group: 'javax.activation' exclude group: 'org.skyscreamer' } + testImplementation ('org.mockito:mockito-inline:4.6.1') } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/ExplicitConfigurationPropertiesFactory.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/ExplicitConfigurationPropertiesFactory.java new file mode 100644 index 000000000..98e673890 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/ExplicitConfigurationPropertiesFactory.java @@ -0,0 +1,31 @@ +/** + * 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.config; + +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.context.annotation.ConditionContext; + +public final class ExplicitConfigurationPropertiesFactory { + + private ExplicitConfigurationPropertiesFactory() {} + + public static T getProperty(ConditionContext context, String propertyName, Class type) { + return Binder.get(context.getEnvironment()).bind(propertyName, type).orElse(null); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractModeValidationCondition.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractModeValidationCondition.java index aafca6218..aff4865a9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractModeValidationCondition.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractModeValidationCondition.java @@ -19,10 +19,12 @@ package org.apache.fineract.infrastructure.core.config; import java.util.Optional; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.type.AnnotatedTypeMetadata; +@Slf4j public class FineractModeValidationCondition implements Condition { @Override @@ -33,6 +35,11 @@ public class FineractModeValidationCondition implements Condition { .orElse(true); boolean isBatchModeEnabled = Optional.ofNullable(context.getEnvironment().getProperty("fineract.mode.batch-enabled", Boolean.class)) .orElse(true); - return !isReadModeEnabled && !isWriteModeEnabled && !isBatchModeEnabled; + boolean isValidationFails = !isReadModeEnabled && !isWriteModeEnabled && !isBatchModeEnabled; + if (isValidationFails) { + log.error( + "The Fineract instance type is not configured properly. At least one of these environment variables should be true: FINERACT_MODE_READ_ENABLED, FINERACT_MODE_WRITE_ENABLED, FINERACT_MODE_BATCH_ENABLED"); + } + return isValidationFails; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractPartitionJobConfigValidationCondition.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractPartitionJobConfigValidationCondition.java new file mode 100644 index 000000000..3cb345e3b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractPartitionJobConfigValidationCondition.java @@ -0,0 +1,59 @@ +/** + * 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.config; + +import java.util.List; +import java.util.function.Predicate; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +@Slf4j +public class FineractPartitionJobConfigValidationCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + FineractProperties.FineractPartitionedJob partitionedJobProperties = ExplicitConfigurationPropertiesFactory.getProperty(context, + "fineract.partitioned-job", FineractProperties.FineractPartitionedJob.class); + if (partitionedJobProperties != null) { + List invalidConfigs = partitionedJobProperties.getPartitionedJobProperties().stream() + .filter(isAnyConfigBelowOne().or(FineractPartitionJobConfigValidationCondition::isPartitionRatioInvalid)).toList(); + if (!invalidConfigs.isEmpty()) { + for (FineractProperties.PartitionedJobProperty invalidConfig : invalidConfigs) { + log.error( + "{} partitioned job is not configured properly. The partition size, chunk size and thread count must be more than 0, and partition size must be less then chunk size * thread count", + invalidConfig.getJobName()); + } + } + return !invalidConfigs.isEmpty(); + } else { + return false; + } + } + + private static Predicate isAnyConfigBelowOne() { + return partitionedJobProperty -> !(partitionedJobProperty.getPartitionSize() > 0 && partitionedJobProperty.getChunkSize() > 0 + && partitionedJobProperty.getThreadCount() > 0); + } + + private static boolean isPartitionRatioInvalid(FineractProperties.PartitionedJobProperty partitionedJobProperty) { + return partitionedJobProperty.getPartitionSize() < partitionedJobProperty.getThreadCount() * partitionedJobProperty.getChunkSize(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java index fb888cb89..e9ff3258e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java @@ -19,6 +19,7 @@ package org.apache.fineract.infrastructure.core.config; +import java.util.List; import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -36,6 +37,8 @@ public class FineractProperties { private FineractCorrelationProperties correlation; + private FineractPartitionedJob partitionedJob; + @Getter @Setter public static class FineractTenantProperties { @@ -72,4 +75,22 @@ public class FineractProperties { private boolean enabled; private String headerName; } + + @Getter + @Setter + public static class FineractPartitionedJob { + + // TODO should be used without wrapper class + private List partitionedJobProperties; + } + + @Getter + @Setter + public static class PartitionedJobProperty { + + private String jobName; + private Integer chunkSize; + private Integer partitionSize; + private Integer threadCount; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractModeValidationConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractStartupValidationConfig.java similarity index 78% rename from fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractModeValidationConfig.java rename to fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractStartupValidationConfig.java index 7fa3bb917..825cf8ff1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractModeValidationConfig.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractStartupValidationConfig.java @@ -32,8 +32,8 @@ import org.springframework.stereotype.Component; @Component @Order(Ordered.HIGHEST_PRECEDENCE) @RequiredArgsConstructor -@Conditional(FineractModeValidationCondition.class) -public class FineractModeValidationConfig implements InitializingBean { +@Conditional(FineractValidationCondition.class) +public class FineractStartupValidationConfig implements InitializingBean { private final ApplicationContext applicationContext; @@ -43,8 +43,7 @@ public class FineractModeValidationConfig implements InitializingBean { } private void terminateApplication() { - log.error( - "The Fineract instance type is not configured properly. At least one of these environment variables should be true: FINERACT_MODE_READ_ENABLED, FINERACT_MODE_WRITE_ENABLED, FINERACT_MODE_BATCH_ENABLED"); - ((ConfigurableApplicationContext) this.applicationContext).close(); + log.error("The application startup fails on validations. Please check the log above for the details"); + ((ConfigurableApplicationContext) applicationContext).close(); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractValidationCondition.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractValidationCondition.java new file mode 100644 index 000000000..f980ec092 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractValidationCondition.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.infrastructure.core.config; + +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; +import org.springframework.context.annotation.Conditional; + +public class FineractValidationCondition extends AnyNestedCondition { + + public FineractValidationCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @Conditional(FineractModeValidationCondition.class) + static class FineractModeValidation {} + + @Conditional(FineractPartitionJobConfigValidationCondition.class) + static class FineractPartitionedJobValidation {} +} diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index ffdff8dcf..89bf99423 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -43,6 +43,11 @@ fineract.mode.batch-manager-enabled=${FINERACT_MODE_BATCH_MANAGER_ENABLED:true} fineract.correlation.enabled=${FINERACT_LOGGING_HTTP_CORRELATION_ID_ENABLED:false} fineract.correlation.header-name=${FINERACT_LOGGING_HTTP_CORRELATION_ID_HEADER_NAME:X-Correlation-ID} +fineract.partitioned-job.partitioned-job-properties[0].job-name=LOAN_COB +fineract.partitioned-job.partitioned-job-properties[0].chunk-size=${LOAN_COB_CHUNK_SIZE:100} +fineract.partitioned-job.partitioned-job-properties[0].partition-size=${LOAN_COB_PARTITION_SIZE:100} +fineract.partitioned-job.partitioned-job-properties[0].thread-count=${LOAN_COB_THREAD_COUNT:1} + # Logging pattern for the console logging.pattern.console=${CONSOLE_LOG_PATTERN:%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(%replace([%X{correlationId}]){'\\[\\]', ''}) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}} diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/config/FineractPartitionJobConfigValidationConditionTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/config/FineractPartitionJobConfigValidationConditionTest.java new file mode 100644 index 000000000..4e2d4b214 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/config/FineractPartitionJobConfigValidationConditionTest.java @@ -0,0 +1,126 @@ +/** + * 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.config; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class FineractPartitionJobConfigValidationConditionTest { + + @InjectMocks + private FineractPartitionJobConfigValidationCondition testObj; + + @Mock + private AnnotatedTypeMetadata metadata; + + private ConditionContext context; + + @BeforeEach + void setUp() { + context = mock(ConditionContext.class); + } + + @Test + public void testApplicationStartup_ShouldApplicationStartupFails_WhenConfigurationNumbersAreBelowOne() { + try (MockedStatic propertyFactory = Mockito + .mockStatic(ExplicitConfigurationPropertiesFactory.class)) { + propertyFactory.when(() -> ExplicitConfigurationPropertiesFactory.getProperty(context, "fineract.partitioned-job", + FineractProperties.FineractPartitionedJob.class)).thenReturn(getBelowOneConfig()); + assertTrue(testObj.matches(context, metadata)); + } + } + + @Test + public void testApplicationStartup_ShouldApplicationStartupFails_WhenPartitionSizeInvalid() { + try (MockedStatic propertyFactory = Mockito + .mockStatic(ExplicitConfigurationPropertiesFactory.class)) { + propertyFactory.when(() -> ExplicitConfigurationPropertiesFactory.getProperty(context, "fineract.partitioned-job", + FineractProperties.FineractPartitionedJob.class)).thenReturn(getInvalidPartitionSize()); + assertTrue(testObj.matches(context, metadata)); + } + } + + @Test + public void testApplicationStartup_ShouldApplicationStart_WhenConfigValid() { + try (MockedStatic propertyFactory = Mockito + .mockStatic(ExplicitConfigurationPropertiesFactory.class)) { + propertyFactory.when(() -> ExplicitConfigurationPropertiesFactory.getProperty(context, "fineract.partitioned-job", + FineractProperties.FineractPartitionedJob.class)).thenReturn(getValidConfig()); + assertFalse(testObj.matches(context, metadata)); + } + } + + private FineractProperties.FineractPartitionedJob getValidConfig() { + FineractProperties.FineractPartitionedJob partitionedJob = new FineractProperties.FineractPartitionedJob(); + List jobProperties = new ArrayList<>(); + FineractProperties.PartitionedJobProperty partitionedJobProperty = new FineractProperties.PartitionedJobProperty(); + partitionedJobProperty.setJobName("LOAN_COB"); + partitionedJobProperty.setPartitionSize(100); + partitionedJobProperty.setChunkSize(10); + partitionedJobProperty.setThreadCount(10); + jobProperties.add(partitionedJobProperty); + partitionedJob.setPartitionedJobProperties(jobProperties); + return partitionedJob; + } + + private FineractProperties.FineractPartitionedJob getInvalidPartitionSize() { + FineractProperties.FineractPartitionedJob partitionedJob = new FineractProperties.FineractPartitionedJob(); + List jobProperties = new ArrayList<>(); + FineractProperties.PartitionedJobProperty partitionedJobProperty = new FineractProperties.PartitionedJobProperty(); + partitionedJobProperty.setJobName("LOAN_COB"); + partitionedJobProperty.setPartitionSize(99); + partitionedJobProperty.setChunkSize(10); + partitionedJobProperty.setThreadCount(10); + jobProperties.add(partitionedJobProperty); + partitionedJob.setPartitionedJobProperties(jobProperties); + return partitionedJob; + } + + private FineractProperties.FineractPartitionedJob getBelowOneConfig() { + FineractProperties.FineractPartitionedJob partitionedJob = new FineractProperties.FineractPartitionedJob(); + List jobProperties = new ArrayList<>(); + FineractProperties.PartitionedJobProperty partitionedJobProperty = new FineractProperties.PartitionedJobProperty(); + partitionedJobProperty.setJobName("LOAN_COB"); + partitionedJobProperty.setPartitionSize(0); + partitionedJobProperty.setChunkSize(1); + partitionedJobProperty.setThreadCount(1); + jobProperties.add(partitionedJobProperty); + partitionedJob.setPartitionedJobProperties(jobProperties); + return partitionedJob; + } + +} diff --git a/fineract-provider/src/test/resources/application-test.properties b/fineract-provider/src/test/resources/application-test.properties index 39b476324..f4fc3d53d 100644 --- a/fineract-provider/src/test/resources/application-test.properties +++ b/fineract-provider/src/test/resources/application-test.properties @@ -37,6 +37,11 @@ fineract.mode.read-enabled=true fineract.mode.write-enabled=true fineract.mode.batch-enabled=true +fineract.partitioned-job.partitioned-job-properties[0].job-name=LOAN_COB +fineract.partitioned-job.partitioned-job-properties[0].chunk-size=100 +fineract.partitioned-job.partitioned-job-properties[0].partition-size=100 +fineract.partitioned-job.partitioned-job-properties[0].thread-count=1 + management.health.jms.enabled=false # FINERACT 1296