You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2022/12/23 11:16:42 UTC

[fineract] branch develop updated: FINERACT-1734-Create-Custom-Business-Event

This is an automated email from the ASF dual-hosted git repository.

arnold pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new d3dcc760e  FINERACT-1734-Create-Custom-Business-Event
d3dcc760e is described below

commit d3dcc760e27999a8bc57da413402d05d12edddfd
Author: Ruchi Dhamankar <ru...@gmail.com>
AuthorDate: Fri Dec 23 15:23:03 2022 +0530

     FINERACT-1734-Create-Custom-Business-Event
---
 custom/acme/event/externalevent/build.gradle       | 25 ++++++++++++
 .../acme/event/externalevent/dependencies.gradle   | 23 +++++++++++
 .../AcmeExternalEventSourceProviderConfig.java     | 35 ++++++++++++++++
 .../event/externalevent/AcmeLoanExternalEvent.java | 36 +++++++++++++++++
 .../0001_acme_external_event_configuration.xml     | 16 +++-----
 custom/acme/event/starter/build.gradle             | 25 ++++++++++++
 custom/acme/event/starter/dependencies.gradle      | 23 +++++++++++
 .../event/starter/AcmeEventAutoConfiguration.java  | 27 +++++++++++++
 .../src/main/resources/META-INF/spring.factories   |  2 +
 .../migration/TenantDatabaseUpgradeService.java    | 46 +++++++++++-----------
 ...xternalEventConfigurationValidationService.java | 16 +++++++-
 .../validation/ExternalEventSourceData.java        | 29 ++++++++++++++
 .../validation/ExternalEventSourceProvider.java    | 26 ++++++++++++
 .../ExternalEventSourceProviderConfig.java         | 36 +++++++++++++++++
 .../validation/ExternalEventSourceService.java     | 39 ++++++++++++++++++
 .../SimpleExternalEventSourceProvider.java         | 36 +++++++++++++++++
 .../resources/db/changelog/db.changelog-master.xml |  2 +-
 .../core/LiquibaseStepDefinitions.java             |  3 ++
 ...nalEventConfigurationValidationServiceTest.java | 15 +++++--
 19 files changed, 421 insertions(+), 39 deletions(-)

diff --git a/custom/acme/event/externalevent/build.gradle b/custom/acme/event/externalevent/build.gradle
new file mode 100644
index 000000000..bb703fb8a
--- /dev/null
+++ b/custom/acme/event/externalevent/build.gradle
@@ -0,0 +1,25 @@
+/**
+ * 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.
+ */
+description = 'ACME Corp.: Fineract external events'
+
+group = 'com.acme.fineract.event'
+
+archivesBaseName = 'acme-fineract-event-externalevent'
+
+apply from: 'dependencies.gradle'
diff --git a/custom/acme/event/externalevent/dependencies.gradle b/custom/acme/event/externalevent/dependencies.gradle
new file mode 100644
index 000000000..364f7d772
--- /dev/null
+++ b/custom/acme/event/externalevent/dependencies.gradle
@@ -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.
+ */
+
+dependencies {
+    implementation(project(':fineract-provider'))
+    implementation('org.springframework.boot:spring-boot-starter-data-jpa')
+}
diff --git a/custom/acme/event/externalevent/src/main/java/com/acme/fineract/event/externalevent/AcmeExternalEventSourceProviderConfig.java b/custom/acme/event/externalevent/src/main/java/com/acme/fineract/event/externalevent/AcmeExternalEventSourceProviderConfig.java
new file mode 100644
index 000000000..8d98d814b
--- /dev/null
+++ b/custom/acme/event/externalevent/src/main/java/com/acme/fineract/event/externalevent/AcmeExternalEventSourceProviderConfig.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 com.acme.fineract.event.externalevent;
+
+import java.util.List;
+import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventSourceData;
+import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventSourceProvider;
+import org.apache.fineract.infrastructure.event.external.service.validation.SimpleExternalEventSourceProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class AcmeExternalEventSourceProviderConfig {
+
+    @Bean
+    public ExternalEventSourceProvider acmeEventSourceProvider() {
+        return new SimpleExternalEventSourceProvider(List.of(new ExternalEventSourceData("com.acme.fineract.event.externalevent")));
+    }
+}
diff --git a/custom/acme/event/externalevent/src/main/java/com/acme/fineract/event/externalevent/AcmeLoanExternalEvent.java b/custom/acme/event/externalevent/src/main/java/com/acme/fineract/event/externalevent/AcmeLoanExternalEvent.java
new file mode 100644
index 000000000..80d45a9ce
--- /dev/null
+++ b/custom/acme/event/externalevent/src/main/java/com/acme/fineract/event/externalevent/AcmeLoanExternalEvent.java
@@ -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 com.acme.fineract.event.externalevent;
+
+import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusinessEvent;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+
+public class AcmeLoanExternalEvent extends LoanBusinessEvent {
+
+    private static final String TYPE = "AcmeLoanExternalEvent";
+
+    public AcmeLoanExternalEvent(Loan value) {
+        super(value);
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+}
diff --git a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml b/custom/acme/event/externalevent/src/main/resources/db/custom-changelog/0001_acme_external_event_configuration.xml
similarity index 54%
copy from fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
copy to custom/acme/event/externalevent/src/main/resources/db/custom-changelog/0001_acme_external_event_configuration.xml
index 1e3d3e114..05a143eb7 100644
--- a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
+++ b/custom/acme/event/externalevent/src/main/resources/db/custom-changelog/0001_acme_external_event_configuration.xml
@@ -22,14 +22,10 @@
 <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
-    <property name="current_date" value="CURDATE()" context="mysql"/>
-    <property name="current_date" value="CURRENT_DATE" context="postgresql"/>
-    <property name="current_datetime" value="NOW()"/>
-    <property name="uuid" value="uuid()" context="mysql"/>
-    <property name="uuid" value="uuid_generate_v4()" context="postgresql"/>
-    <include file="tenant-store/initial-switch-changelog-tenant-store.xml" relativeToChangelogFile="true" context="tenant_store_db AND initial_switch"/>
-    <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" />
+    <changeSet author="acme" id="1">
+        <insert tableName="m_external_event_configuration">
+            <column name="type" value="AcmeLoanExternalEvent"/>
+            <column name="enabled" valueBoolean="false"/>
+        </insert>
+    </changeSet>
 </databaseChangeLog>
diff --git a/custom/acme/event/starter/build.gradle b/custom/acme/event/starter/build.gradle
new file mode 100644
index 000000000..314a45cf4
--- /dev/null
+++ b/custom/acme/event/starter/build.gradle
@@ -0,0 +1,25 @@
+/**
+ * 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.
+ */
+description = 'ACME Corp.: Fineract Event Starter'
+
+group = 'com.acme.fineract.event'
+
+archivesBaseName = 'acme-fineract-event-starter'
+
+apply from: 'dependencies.gradle'
diff --git a/custom/acme/event/starter/dependencies.gradle b/custom/acme/event/starter/dependencies.gradle
new file mode 100644
index 000000000..30b090b9f
--- /dev/null
+++ b/custom/acme/event/starter/dependencies.gradle
@@ -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.
+ */
+
+dependencies {
+    implementation(project(':fineract-provider'))
+    implementation('org.springframework.boot:spring-boot-starter')
+}
diff --git a/custom/acme/event/starter/src/main/java/com/acme/fineract/event/starter/AcmeEventAutoConfiguration.java b/custom/acme/event/starter/src/main/java/com/acme/fineract/event/starter/AcmeEventAutoConfiguration.java
new file mode 100644
index 000000000..ea8d65d74
--- /dev/null
+++ b/custom/acme/event/starter/src/main/java/com/acme/fineract/event/starter/AcmeEventAutoConfiguration.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 com.acme.fineract.event.starter;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.ComponentScans;
+
+@AutoConfiguration
+@ComponentScans({ @ComponentScan("com.acme.fineract.event.externalevent") })
+public class AcmeEventAutoConfiguration {}
diff --git a/custom/acme/event/starter/src/main/resources/META-INF/spring.factories b/custom/acme/event/starter/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..28d54193a
--- /dev/null
+++ b/custom/acme/event/starter/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.acme.fineract.event.starter.AcmeEventAutoConfiguration
\ No newline at end of file
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeService.java
index dd3a974ce..1faf5b7e2 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeService.java
@@ -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();
         }
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
index 68dcd6cc9..b3c7efc69 100644
--- 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
@@ -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)));
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventSourceData.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventSourceData.java
new file mode 100644
index 000000000..575eb0133
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventSourceData.java
@@ -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;
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventSourceProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventSourceProvider.java
new file mode 100644
index 000000000..7da3e67dc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventSourceProvider.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.infrastructure.event.external.service.validation;
+
+import java.util.Set;
+
+public interface ExternalEventSourceProvider {
+
+    Set<ExternalEventSourceData> provide();
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventSourceProviderConfig.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventSourceProviderConfig.java
new file mode 100644
index 000000000..b76ac648f
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventSourceProviderConfig.java
@@ -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);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventSourceService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventSourceService.java
new file mode 100644
index 000000000..d4f1cb106
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/ExternalEventSourceService.java
@@ -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());
+    }
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/SimpleExternalEventSourceProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/SimpleExternalEventSourceProvider.java
new file mode 100644
index 000000000..f23864188
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/validation/SimpleExternalEventSourceProvider.java
@@ -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;
+    }
+}
diff --git a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
index 1e3d3e114..6d7a0bd16 100644
--- a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
+++ b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
@@ -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>
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/LiquibaseStepDefinitions.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/LiquibaseStepDefinitions.java
index b55fdc32f..be2143c33 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/LiquibaseStepDefinitions.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/LiquibaseStepDefinitions.java
@@ -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);
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
index d17e90d42..abf792af6 100644
--- 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
@@ -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());