FINERACT-1734-Create-Custom-Business-Event

This commit is contained in:
Ruchi Dhamankar
2022-12-23 12:16:33 +01:00
committed by Arnold Gálovics
parent 0f41eb2c42
commit d3dcc760e2
19 changed files with 446 additions and 29 deletions
@@ -25,11 +25,10 @@ import java.util.function.Function;
import javax.sql.DataSource;
import liquibase.exception.LiquibaseException;
import liquibase.integration.spring.SpringLiquibase;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.config.FineractProperties;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.security.service.TenantDetailsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -39,12 +38,13 @@ import org.springframework.stereotype.Service;
* A service that picks up on tenants that are configured to auto-update their specific schema on application startup.
*/
@Service
@Slf4j
public class TenantDatabaseUpgradeService implements InitializingBean {
private static final Logger LOG = LoggerFactory.getLogger(TenantDatabaseUpgradeService.class);
private static final String TENANT_STORE_DB_CONTEXT = "tenant_store_db";
private static final String INITIAL_SWITCH_CONTEXT = "initial_switch";
private static final String TENANT_DB_CONTEXT = "tenant_db";
private static final String CUSTOM_CHANGELOG_CONTEXT = "custom_changelog";
private final TenantDetailsService tenantDetailsService;
private final DataSource tenantDataSource;
@@ -69,9 +69,9 @@ public class TenantDatabaseUpgradeService implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
if (databaseStateVerifier.isLiquibaseDisabled() || !fineractProperties.getMode().isWriteEnabled()) {
LOG.warn("Liquibase is disabled. Not upgrading any database.");
log.warn("Liquibase is disabled. Not upgrading any database.");
if (!fineractProperties.getMode().isWriteEnabled()) {
LOG.warn("Liquibase is disabled because the current instance is configured as a non-write Fineract instance");
log.warn("Liquibase is disabled because the current instance is configured as a non-write Fineract instance");
}
return;
}
@@ -84,7 +84,7 @@ public class TenantDatabaseUpgradeService implements InitializingBean {
}
private void upgradeTenantStore() throws LiquibaseException {
LOG.warn("Upgrading tenant store DB at {}:{}", fineractProperties.getTenant().getHost(), fineractProperties.getTenant().getPort());
log.warn("Upgrading tenant store DB at {}:{}", fineractProperties.getTenant().getHost(), fineractProperties.getTenant().getPort());
logTenantStoreDetails();
if (databaseStateVerifier.isFirstLiquibaseMigration(tenantDataSource)) {
ExtendedSpringLiquibase liquibase = liquibaseFactory.create(tenantDataSource, TENANT_STORE_DB_CONTEXT, INITIAL_SWITCH_CONTEXT);
@@ -93,32 +93,32 @@ public class TenantDatabaseUpgradeService implements InitializingBean {
}
SpringLiquibase liquibase = liquibaseFactory.create(tenantDataSource, TENANT_STORE_DB_CONTEXT);
liquibase.afterPropertiesSet();
LOG.warn("Tenant store upgrade finished");
log.warn("Tenant store upgrade finished");
}
private void logTenantStoreDetails() {
LOG.info("- fineract.tenant.username: {}", fineractProperties.getTenant().getUsername());
LOG.info("- fineract.tenant.password: ****");
LOG.info("- fineract.tenant.parameters: {}", fineractProperties.getTenant().getParameters());
LOG.info("- fineract.tenant.timezone: {}", fineractProperties.getTenant().getTimezone());
LOG.info("- fineract.tenant.description: {}", fineractProperties.getTenant().getDescription());
LOG.info("- fineract.tenant.identifier: {}", fineractProperties.getTenant().getIdentifier());
LOG.info("- fineract.tenant.name: {}", fineractProperties.getTenant().getName());
log.info("- fineract.tenant.username: {}", fineractProperties.getTenant().getUsername());
log.info("- fineract.tenant.password: ****");
log.info("- fineract.tenant.parameters: {}", fineractProperties.getTenant().getParameters());
log.info("- fineract.tenant.timezone: {}", fineractProperties.getTenant().getTimezone());
log.info("- fineract.tenant.description: {}", fineractProperties.getTenant().getDescription());
log.info("- fineract.tenant.identifier: {}", fineractProperties.getTenant().getIdentifier());
log.info("- fineract.tenant.name: {}", fineractProperties.getTenant().getName());
}
private void upgradeIndividualTenants() throws LiquibaseException {
LOG.warn("Upgrading all tenants");
log.warn("Upgrading all tenants");
List<FineractPlatformTenant> tenants = tenantDetailsService.findAllTenants();
if (isNotEmpty(tenants)) {
for (FineractPlatformTenant tenant : tenants) {
upgradeIndividualTenant(tenant);
}
}
LOG.warn("Tenant upgrades have finished");
log.warn("Tenant upgrades have finished");
}
private void upgradeIndividualTenant(FineractPlatformTenant tenant) throws LiquibaseException {
LOG.info("Upgrade for tenant {} has started", tenant.getTenantIdentifier());
log.info("Upgrade for tenant {} has started", tenant.getTenantIdentifier());
DataSource tenantDataSource = tenantDataSourceFactory.create(tenant);
if (databaseStateVerifier.isFirstLiquibaseMigration(tenantDataSource)) {
ExtendedSpringLiquibase liquibase = liquibaseFactory.create(tenantDataSource, TENANT_DB_CONTEXT, INITIAL_SWITCH_CONTEXT);
@@ -127,20 +127,22 @@ public class TenantDatabaseUpgradeService implements InitializingBean {
}
SpringLiquibase tenantLiquibase = liquibaseFactory.create(tenantDataSource, TENANT_DB_CONTEXT);
tenantLiquibase.afterPropertiesSet();
LOG.info("Upgrade for tenant {} has finished", tenant.getTenantIdentifier());
SpringLiquibase customChangelogLiquibase = liquibaseFactory.create(tenantDataSource, TENANT_DB_CONTEXT, CUSTOM_CHANGELOG_CONTEXT);
customChangelogLiquibase.afterPropertiesSet();
log.info("Upgrade for tenant {} has finished", tenant.getTenantIdentifier());
}
private void applyInitialLiquibase(DataSource dataSource, ExtendedSpringLiquibase liquibase, String id,
Function<DataSource, Boolean> isUpgradableFn) throws LiquibaseException {
if (databaseStateVerifier.isFlywayPresent(dataSource)) {
if (isUpgradableFn.apply(dataSource)) {
LOG.warn("Cannot proceed with upgrading database {}", id);
LOG.warn("It seems the database doesn't have the latest schema changes applied until the 1.6 release");
log.warn("Cannot proceed with upgrading database {}", id);
log.warn("It seems the database doesn't have the latest schema changes applied until the 1.6 release");
throw new SchemaUpgradeNeededException("Make sure to upgrade to Fineract 1.6 first and then to a newer version");
}
LOG.warn("This is the first Liquibase migration for {}. We'll sync the changelog for you and then apply everything else", id);
log.warn("This is the first Liquibase migration for {}. We'll sync the changelog for you and then apply everything else", id);
liquibase.changeLogSync();
LOG.warn("Liquibase changelog sync is complete");
log.warn("Liquibase changelog sync is complete");
} else {
liquibase.afterPropertiesSet();
}
@@ -27,10 +27,12 @@ import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
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.event.external.service.validation.ExternalEventSourceService;
import org.apache.fineract.infrastructure.security.service.TenantDetailsService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -41,11 +43,11 @@ import org.springframework.stereotype.Service;
@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;
private final ExternalEventSourceService externalEventSourceService;
@Override
public void afterPropertiesSet() throws Exception {
@@ -67,6 +69,10 @@ public class ExternalEventConfigurationValidationService implements Initializing
throws ExternalEventConfigurationNotFoundException {
log.info("Validating external event configuration for {}", tenant.getTenantIdentifier());
List<String> eventConfigurations = getExternalEventConfigurationsForTenant(tenant);
if (log.isDebugEnabled()) {
log.debug("Missing from eventClasses: {}", CollectionUtils.subtract(eventClasses, eventConfigurations));
log.debug("Missing from eventConfigurations: {}", CollectionUtils.subtract(eventConfigurations, eventClasses));
}
if (eventClasses.size() != eventConfigurations.size()) {
throw new ExternalEventConfigurationNotFoundException();
@@ -88,7 +94,13 @@ public class ExternalEventConfigurationValidationService implements Initializing
}
private List<String> getAllEventClasses() {
try (ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages(EXTERNAL_EVENT_CLASSES_BASE_PACKAGE).scan()) {
List<String> sourcePackages = externalEventSourceService.getSourcePackages();
if (log.isDebugEnabled()) {
log.debug("Packages {}", sourcePackages);
}
String[] sourcePackagesForScan = new String[sourcePackages.size()];
try (ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages(sourcePackages.toArray(sourcePackagesForScan))
.scan()) {
ClassInfoList businessEventClasses = scanResult.getClassesImplementing(EXTERNAL_EVENT_BUSINESS_INTERFACE)
.filter(classInfo -> (!classInfo.isInterface() && !classInfo.isAbstract()
&& !classInfo.getName().equalsIgnoreCase(BULK_BUSINESS_EVENT)));
@@ -0,0 +1,29 @@
/**
* 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.validation;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class ExternalEventSourceData {
private final String sourcePackage;
}
@@ -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.infrastructure.event.external.service.validation;
import java.util.Set;
public interface ExternalEventSourceProvider {
Set<ExternalEventSourceData> provide();
}
@@ -0,0 +1,36 @@
/**
* 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.validation;
import java.util.Arrays;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ExternalEventSourceProviderConfig {
private static final String EXTERNAL_EVENT_CLASSES_BASE_PACKAGE = "org.apache.fineract.infrastructure.event.business.domain";
@Bean
public ExternalEventSourceProvider fineractExternalEventSourceProvider() {
List<ExternalEventSourceData> sourcePackages = Arrays.asList(new ExternalEventSourceData(EXTERNAL_EVENT_CLASSES_BASE_PACKAGE));
return new SimpleExternalEventSourceProvider(sourcePackages);
}
}
@@ -0,0 +1,39 @@
/**
* 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.validation;
import static java.util.stream.Collectors.toList;
import java.util.Collection;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class ExternalEventSourceService {
private final Collection<ExternalEventSourceProvider> providers;
public List<String> getSourcePackages() {
return providers.stream().map(ExternalEventSourceProvider::provide).flatMap(Collection::stream)
.map(source -> source.getSourcePackage()).collect(toList());
}
}
@@ -0,0 +1,36 @@
/**
* 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.validation;
import java.util.Collection;
import java.util.Set;
public class SimpleExternalEventSourceProvider implements ExternalEventSourceProvider {
private final Set<ExternalEventSourceData> sourcePackages;
public SimpleExternalEventSourceProvider(Collection<ExternalEventSourceData> sourcePackages) {
this.sourcePackages = Set.copyOf(sourcePackages);
}
@Override
public Set<ExternalEventSourceData> provide() {
return sourcePackages;
}
}
@@ -31,5 +31,5 @@
<include file="tenant-store/changelog-tenant-store.xml" relativeToChangelogFile="true" context="tenant_store_db AND !initial_switch"/>
<include file="tenant/initial-switch-changelog-tenant.xml" relativeToChangelogFile="true" context="tenant_db AND initial_switch"/>
<include file="tenant/changelog-tenant.xml" relativeToChangelogFile="true" context="tenant_db AND !initial_switch"/>
<includeAll path="db/custom-changelog" errorIfMissingOrEmpty="false" context="tenant_db" />
<includeAll path="db/custom-changelog" errorIfMissingOrEmpty="false" context="tenant_db AND custom_changelog" />
</databaseChangeLog>
@@ -54,6 +54,7 @@ public class LiquibaseStepDefinitions implements En {
private ExtendedSpringLiquibase tenantStoreLiquibase;
private ExtendedSpringLiquibase initialTenantLiquibase;
private ExtendedSpringLiquibase tenantLiquibase;
private ExtendedSpringLiquibase customChangeLogLiquibase;
private FineractPlatformTenant defaultTenant;
private DataSource tenantStoreDataSource;
private TenantDatabaseUpgradeService tenantDatabaseUpgradeService;
@@ -158,6 +159,7 @@ public class LiquibaseStepDefinitions implements En {
initialTenantLiquibase = mock(ExtendedSpringLiquibase.class);
tenantLiquibase = mock(ExtendedSpringLiquibase.class);
customChangeLogLiquibase = mock(ExtendedSpringLiquibase.class);
initialTenantStoreLiquibase = mock(ExtendedSpringLiquibase.class);
tenantStoreLiquibase = mock(ExtendedSpringLiquibase.class);
@@ -171,6 +173,7 @@ public class LiquibaseStepDefinitions implements En {
given(tenantDataSourceFactory.create(defaultTenant)).willReturn(defaultTenantDataSource);
given(liquibaseFactory.create(defaultTenantDataSource, "tenant_db", "initial_switch")).willReturn(initialTenantLiquibase);
given(liquibaseFactory.create(defaultTenantDataSource, "tenant_db")).willReturn(tenantLiquibase);
given(liquibaseFactory.create(defaultTenantDataSource, "tenant_db", "custom_changelog")).willReturn(customChangeLogLiquibase);
tenantDatabaseUpgradeService = new TenantDatabaseUpgradeService(tenantDetailsService, tenantStoreDataSource, fineractProperties,
databaseStateVerifier, liquibaseFactory, tenantDataSourceFactory);
@@ -32,6 +32,7 @@ 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.event.external.service.validation.ExternalEventSourceService;
import org.apache.fineract.infrastructure.security.service.TenantDetailsService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -50,11 +51,14 @@ public class ExternalEventConfigurationValidationServiceTest {
@Mock
private TenantDetailsService tenantDetailsService;
@Mock
private ExternalEventSourceService externalEventSourceService;
private ExternalEventConfigurationValidationService underTest;
@BeforeEach
public void setUp() {
underTest = new ExternalEventConfigurationValidationService(tenantDetailsService, jdbcTemplateFactory);
underTest = new ExternalEventConfigurationValidationService(tenantDetailsService, jdbcTemplateFactory, externalEventSourceService);
}
@Test
@@ -98,7 +102,8 @@ public class ExternalEventConfigurationValidationServiceTest {
when(tenantDetailsService.findAllTenants()).thenReturn(tenants);
when(jdbcTemplateFactory.create(any())).thenReturn(jdbcTemplate);
when(jdbcTemplate.queryForList(anyString(), eq(String.class))).thenReturn(configurations);
List<String> sourcePackage = Arrays.asList("org.apache.fineract.infrastructure.event.business.domain");
when(externalEventSourceService.getSourcePackages()).thenReturn(sourcePackage);
// when
underTest.afterPropertiesSet();
@@ -117,7 +122,8 @@ public class ExternalEventConfigurationValidationServiceTest {
when(tenantDetailsService.findAllTenants()).thenReturn(tenants);
when(jdbcTemplateFactory.create(any())).thenReturn(jdbcTemplate);
when(jdbcTemplate.queryForList(anyString(), eq(String.class))).thenReturn(new ArrayList<>());
List<String> sourcePackage = Arrays.asList("org.apache.fineract.infrastructure.event.business.domain");
when(externalEventSourceService.getSourcePackages()).thenReturn(sourcePackage);
// when
ExternalEventConfigurationNotFoundException exceptionThrown = assertThrows(ExternalEventConfigurationNotFoundException.class,
() -> underTest.afterPropertiesSet());
@@ -171,7 +177,8 @@ public class ExternalEventConfigurationValidationServiceTest {
when(tenantDetailsService.findAllTenants()).thenReturn(tenants);
when(jdbcTemplateFactory.create(any())).thenReturn(jdbcTemplate);
when(jdbcTemplate.queryForList(anyString(), eq(String.class))).thenReturn(configurationWithMissingCentersCreateBusinessEvent);
List<String> sourcePackage = Arrays.asList("org.apache.fineract.infrastructure.event.business.domain");
when(externalEventSourceService.getSourcePackages()).thenReturn(sourcePackage);
// when
ExternalEventConfigurationNotFoundException exceptionThrown = assertThrows(ExternalEventConfigurationNotFoundException.class,
() -> underTest.afterPropertiesSet());