From 1c944dd83026319080eeaf2ac822db6eebda2a96 Mon Sep 17 00:00:00 2001 From: Ruchi Dhamankar Date: Thu, 27 Oct 2022 19:03:48 +0530 Subject: [PATCH] FINERACT-1694-Validating-event-configuration --- ...alEventConfigurationNotFoundException.java | 4 + ...alEventConfigurationValidationService.java | 98 ++++++++++ .../external/service/JdbcTemplateFactory.java | 38 ++++ ...entConfigurationValidationServiceTest.java | 182 ++++++++++++++++++ 4 files changed, 322 insertions(+) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/JdbcTemplateFactory.java create mode 100644 fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/exception/ExternalEventConfigurationNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/exception/ExternalEventConfigurationNotFoundException.java index ce56b9e1e..2d30a01fc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/exception/ExternalEventConfigurationNotFoundException.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/exception/ExternalEventConfigurationNotFoundException.java @@ -20,6 +20,10 @@ package org.apache.fineract.infrastructure.event.external.exception; public class ExternalEventConfigurationNotFoundException extends RuntimeException { + public ExternalEventConfigurationNotFoundException() { + super("All external events are not configured"); + } + public ExternalEventConfigurationNotFoundException(final String externalEventType) { super("Configuration not found for external event " + externalEventType); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationService.java new file mode 100644 index 000000000..68dcd6cc9 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationService.java @@ -0,0 +1,98 @@ +/** + * 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.event.external.service; + +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfoList; +import io.github.classgraph.ScanResult; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.event.business.domain.BulkBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent; +import org.apache.fineract.infrastructure.event.external.exception.ExternalEventConfigurationNotFoundException; +import org.apache.fineract.infrastructure.security.service.TenantDetailsService; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class ExternalEventConfigurationValidationService implements InitializingBean { + + private static final String EXTERNAL_EVENT_CLASSES_BASE_PACKAGE = "org.apache.fineract.infrastructure.event.business.domain"; + private static final String EXTERNAL_EVENT_BUSINESS_INTERFACE = BusinessEvent.class.getName(); + private static final String BULK_BUSINESS_EVENT = BulkBusinessEvent.class.getName(); + private final TenantDetailsService tenantDetailsService; + private final JdbcTemplateFactory jdbcTemplateFactory; + + @Override + public void afterPropertiesSet() throws Exception { + validateEventConfigurationForAllTenants(); + } + + private void validateEventConfigurationForAllTenants() throws ExternalEventConfigurationNotFoundException { + List eventClasses = getAllEventClasses(); + List tenants = tenantDetailsService.findAllTenants(); + + if (isNotEmpty(tenants)) { + for (FineractPlatformTenant tenant : tenants) { + validateEventConfigurationForIndividualTenant(tenant, eventClasses); + } + } + } + + private void validateEventConfigurationForIndividualTenant(FineractPlatformTenant tenant, List eventClasses) + throws ExternalEventConfigurationNotFoundException { + log.info("Validating external event configuration for {}", tenant.getTenantIdentifier()); + List eventConfigurations = getExternalEventConfigurationsForTenant(tenant); + + if (eventClasses.size() != eventConfigurations.size()) { + throw new ExternalEventConfigurationNotFoundException(); + } + + for (String eventTypeClass : eventClasses) { + if (!eventConfigurations.contains(eventTypeClass)) { + throw new ExternalEventConfigurationNotFoundException(eventTypeClass); + } + } + } + + private List getExternalEventConfigurationsForTenant(FineractPlatformTenant tenant) { + final JdbcTemplate jdbcTemplate = jdbcTemplateFactory.create(tenant); + final StringBuilder eventConfigurations = new StringBuilder(); + eventConfigurations.append("select me.type as type from m_external_event_configuration me"); + List configuredEventTypes = jdbcTemplate.queryForList(eventConfigurations.toString(), String.class); + return configuredEventTypes; + } + + private List getAllEventClasses() { + try (ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages(EXTERNAL_EVENT_CLASSES_BASE_PACKAGE).scan()) { + ClassInfoList businessEventClasses = scanResult.getClassesImplementing(EXTERNAL_EVENT_BUSINESS_INTERFACE) + .filter(classInfo -> (!classInfo.isInterface() && !classInfo.isAbstract() + && !classInfo.getName().equalsIgnoreCase(BULK_BUSINESS_EVENT))); + return businessEventClasses.stream().map(classInfo -> classInfo.getSimpleName()).collect(Collectors.toList()); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/JdbcTemplateFactory.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/JdbcTemplateFactory.java new file mode 100644 index 000000000..4929e050e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/JdbcTemplateFactory.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.event.external.service; + +import javax.sql.DataSource; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.service.migration.TenantDataSourceFactory; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class JdbcTemplateFactory { + + private final TenantDataSourceFactory tenantDataSourceFactory; + + public JdbcTemplate create(FineractPlatformTenant tenant) { + DataSource tenantDataSource = tenantDataSourceFactory.create(tenant); + return new JdbcTemplate(tenantDataSource); + } +} diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java new file mode 100644 index 000000000..f9bb17f03 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java @@ -0,0 +1,182 @@ +/** + * 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.event.external.service; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.event.external.exception.ExternalEventConfigurationNotFoundException; +import org.apache.fineract.infrastructure.security.service.TenantDetailsService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.jdbc.core.JdbcTemplate; + +@ExtendWith(MockitoExtension.class) +public class ExternalEventConfigurationValidationServiceTest { + + @Mock + private JdbcTemplateFactory jdbcTemplateFactory; + + @Mock + private TenantDetailsService tenantDetailsService; + + private ExternalEventConfigurationValidationService underTest; + + @BeforeEach + public void setUp() { + underTest = new ExternalEventConfigurationValidationService(tenantDetailsService, jdbcTemplateFactory); + } + + @Test + public void givenAllConfigurationWhenValidatedThenValidationSuccessful() throws Exception { + + // given + List configurations = Arrays.asList("CentersCreateBusinessEvent", "ClientActivateBusinessEvent", + "ClientCreateBusinessEvent", "ClientRejectBusinessEvent", "FixedDepositAccountCreateBusinessEvent", + "GroupsCreateBusinessEvent", "LoanAcceptTransferBusinessEvent", "LoanAddChargeBusinessEvent", + "LoanAdjustTransactionBusinessEvent", "LoanApplyOverdueChargeBusinessEvent", "LoanApprovedBusinessEvent", + "LoanBalanceChangedBusinessEvent", "LoanChargebackTransactionBusinessEvent", "LoanChargePaymentPostBusinessEvent", + "LoanChargePaymentPreBusinessEvent", "LoanChargeRefundBusinessEvent", "LoanCloseAsRescheduleBusinessEvent", + "LoanCloseBusinessEvent", "LoanCreatedBusinessEvent", "LoanCreditBalanceRefundPostBusinessEvent", + "LoanCreditBalanceRefundPreBusinessEvent", "LoanDeleteChargeBusinessEvent", "LoanDisbursalBusinessEvent", + "LoanDisbursalTransactionBusinessEvent", "LoanForeClosurePostBusinessEvent", "LoanForeClosurePreBusinessEvent", + "LoanInitiateTransferBusinessEvent", "LoanInterestRecalculationBusinessEvent", "LoanProductCreateBusinessEvent", + "LoanReassignOfficerBusinessEvent", "LoanRefundPostBusinessEvent", "LoanRefundPreBusinessEvent", + "LoanRejectedBusinessEvent", "LoanRejectTransferBusinessEvent", "LoanRemoveOfficerBusinessEvent", + "LoanRescheduledDueCalendarChangeBusinessEvent", "LoanRescheduledDueHolidayBusinessEvent", + "LoanScheduleVariationsAddedBusinessEvent", "LoanScheduleVariationsDeletedBusinessEvent", "LoanStatusChangedBusinessEvent", + "LoanTransactionGoodwillCreditPostBusinessEvent", "LoanTransactionGoodwillCreditPreBusinessEvent", + "LoanTransactionMakeRepaymentPostBusinessEvent", "LoanTransactionMakeRepaymentPreBusinessEvent", + "LoanTransactionMerchantIssuedRefundPostBusinessEvent", "LoanTransactionMerchantIssuedRefundPreBusinessEvent", + "LoanTransactionPayoutRefundPostBusinessEvent", "LoanTransactionPayoutRefundPreBusinessEvent", + "LoanTransactionRecoveryPaymentPostBusinessEvent", "LoanTransactionRecoveryPaymentPreBusinessEvent", + "LoanUndoApprovalBusinessEvent", "LoanUndoDisbursalBusinessEvent", "LoanUndoLastDisbursalBusinessEvent", + "LoanUndoWrittenOffBusinessEvent", "LoanUpdateChargeBusinessEvent", "LoanUpdateDisbursementDataBusinessEvent", + "LoanWaiveChargeBusinessEvent", "LoanWaiveChargeUndoBusinessEvent", "LoanWaiveInterestBusinessEvent", + "LoanWithdrawTransferBusinessEvent", "LoanWrittenOffPostBusinessEvent", "LoanWrittenOffPreBusinessEvent", + "RecurringDepositAccountCreateBusinessEvent", "SavingsActivateBusinessEvent", "SavingsApproveBusinessEvent", + "SavingsCloseBusinessEvent", "SavingsCreateBusinessEvent", "SavingsDepositBusinessEvent", + "SavingsPostInterestBusinessEvent", "SavingsRejectBusinessEvent", "SavingsWithdrawalBusinessEvent", + "ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent"); + + List tenants = Arrays + .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null)); + + JdbcTemplate jdbcTemplate = Mockito.mock(JdbcTemplate.class); + when(tenantDetailsService.findAllTenants()).thenReturn(tenants); + when(jdbcTemplateFactory.create(any())).thenReturn(jdbcTemplate); + when(jdbcTemplate.queryForList(anyString(), eq(String.class))).thenReturn(configurations); + + // when + underTest.afterPropertiesSet(); + + // then + verify(tenantDetailsService).findAllTenants(); + verify(jdbcTemplateFactory, times(1)).create(any()); + } + + @Test + public void givenNoEventConfigurationWhenValidatedThenThrowException() throws Exception { + // given + List tenants = Arrays + .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null)); + + JdbcTemplate jdbcTemplate = Mockito.mock(JdbcTemplate.class); + when(tenantDetailsService.findAllTenants()).thenReturn(tenants); + when(jdbcTemplateFactory.create(any())).thenReturn(jdbcTemplate); + when(jdbcTemplate.queryForList(anyString(), eq(String.class))).thenReturn(new ArrayList<>()); + + // when + ExternalEventConfigurationNotFoundException exceptionThrown = assertThrows(ExternalEventConfigurationNotFoundException.class, + () -> underTest.afterPropertiesSet()); + + // then + String expectedMessage = "All external events are not configured"; + String actualMessage = exceptionThrown.getMessage(); + + assertTrue(actualMessage.contains(expectedMessage)); + + } + + @Test + public void givenMissingEventConfigurationWhenValidatedThenThrowException() throws Exception { + + // given + List configurationWithMissingCentersCreateBusinessEvent = Arrays.asList("MockBusinessEvent", "ClientActivateBusinessEvent", + "ClientCreateBusinessEvent", "ClientRejectBusinessEvent", "FixedDepositAccountCreateBusinessEvent", + "GroupsCreateBusinessEvent", "LoanAcceptTransferBusinessEvent", "LoanAddChargeBusinessEvent", + "LoanAdjustTransactionBusinessEvent", "LoanApplyOverdueChargeBusinessEvent", "LoanApprovedBusinessEvent", + "LoanBalanceChangedBusinessEvent", "LoanChargebackTransactionBusinessEvent", "LoanChargePaymentPostBusinessEvent", + "LoanChargePaymentPreBusinessEvent", "LoanChargeRefundBusinessEvent", "LoanCloseAsRescheduleBusinessEvent", + "LoanCloseBusinessEvent", "LoanCreatedBusinessEvent", "LoanCreditBalanceRefundPostBusinessEvent", + "LoanCreditBalanceRefundPreBusinessEvent", "LoanDeleteChargeBusinessEvent", "LoanDisbursalBusinessEvent", + "LoanDisbursalTransactionBusinessEvent", "LoanForeClosurePostBusinessEvent", "LoanForeClosurePreBusinessEvent", + "LoanInitiateTransferBusinessEvent", "LoanInterestRecalculationBusinessEvent", "LoanProductCreateBusinessEvent", + "LoanReassignOfficerBusinessEvent", "LoanRefundPostBusinessEvent", "LoanRefundPreBusinessEvent", + "LoanRejectedBusinessEvent", "LoanRejectTransferBusinessEvent", "LoanRemoveOfficerBusinessEvent", + "LoanRescheduledDueCalendarChangeBusinessEvent", "LoanRescheduledDueHolidayBusinessEvent", + "LoanScheduleVariationsAddedBusinessEvent", "LoanScheduleVariationsDeletedBusinessEvent", "LoanStatusChangedBusinessEvent", + "LoanTransactionGoodwillCreditPostBusinessEvent", "LoanTransactionGoodwillCreditPreBusinessEvent", + "LoanTransactionMakeRepaymentPostBusinessEvent", "LoanTransactionMakeRepaymentPreBusinessEvent", + "LoanTransactionMerchantIssuedRefundPostBusinessEvent", "LoanTransactionMerchantIssuedRefundPreBusinessEvent", + "LoanTransactionPayoutRefundPostBusinessEvent", "LoanTransactionPayoutRefundPreBusinessEvent", + "LoanTransactionRecoveryPaymentPostBusinessEvent", "LoanTransactionRecoveryPaymentPreBusinessEvent", + "LoanUndoApprovalBusinessEvent", "LoanUndoDisbursalBusinessEvent", "LoanUndoLastDisbursalBusinessEvent", + "LoanUndoWrittenOffBusinessEvent", "LoanUpdateChargeBusinessEvent", "LoanUpdateDisbursementDataBusinessEvent", + "LoanWaiveChargeBusinessEvent", "LoanWaiveChargeUndoBusinessEvent", "LoanWaiveInterestBusinessEvent", + "LoanWithdrawTransferBusinessEvent", "LoanWrittenOffPostBusinessEvent", "LoanWrittenOffPreBusinessEvent", + "RecurringDepositAccountCreateBusinessEvent", "SavingsActivateBusinessEvent", "SavingsApproveBusinessEvent", + "SavingsCloseBusinessEvent", "SavingsCreateBusinessEvent", "SavingsDepositBusinessEvent", + "SavingsPostInterestBusinessEvent", "SavingsRejectBusinessEvent", "SavingsWithdrawalBusinessEvent", + "ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent"); + List tenants = Arrays + .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null)); + + JdbcTemplate jdbcTemplate = Mockito.mock(JdbcTemplate.class); + when(tenantDetailsService.findAllTenants()).thenReturn(tenants); + when(jdbcTemplateFactory.create(any())).thenReturn(jdbcTemplate); + when(jdbcTemplate.queryForList(anyString(), eq(String.class))).thenReturn(configurationWithMissingCentersCreateBusinessEvent); + + // when + ExternalEventConfigurationNotFoundException exceptionThrown = assertThrows(ExternalEventConfigurationNotFoundException.class, + () -> underTest.afterPropertiesSet()); + + // then + String expectedMessage = "Configuration not found for external event CentersCreateBusinessEvent"; + String actualMessage = exceptionThrown.getMessage(); + + assertTrue(actualMessage.contains(expectedMessage)); + + } + +}