From 45bed0a05adf73c7bcad60b45080527961dd2a9e Mon Sep 17 00:00:00 2001 From: Arnold Galovics Date: Wed, 9 Feb 2022 21:33:01 +0100 Subject: [PATCH] FINERACT-1498: Switching from Flyway to Liqubase migrations --- README.md | 4 +- build.gradle | 9 +- fineract-provider/build.gradle | 13 + fineract-provider/dependencies.gradle | 2 +- .../AbstractApplicationConfiguration.java | 9 +- .../core/service/RoutingDataSource.java | 2 +- .../service/TenantDatabaseUpgradeService.java | 169 - .../DatabaseIndependentQueryService.java | 63 + .../database/DatabaseQueryService.java | 28 + .../core/service/database/DatabaseType.java | 23 + .../database/DatabaseTypeResolver.java | 70 + .../service/database/MySQLQueryService.java | 47 + ...DatabaseAwareMigrationContextProvider.java | 48 + .../migration/ExtendedSpringLiquibase.java | 36 + .../ExtendedSpringLiquibaseBuilder.java | 125 + .../ExtendedSpringLiquibaseFactory.java | 50 + .../SchemaUpgradeNeededException.java | 26 + .../migration/TenantDataSourceFactory.java | 65 + .../TenantDatabaseStateVerifier.java | 83 + .../TenantDatabaseUpgradeService.java | 146 + ...WritePlatformServiceJpaRepositoryImpl.java | 6 + .../src/main/resources/application.properties | 14 + .../db/changelog/db.changelog-master.xml | 31 + .../tenant-store/changelog-tenant-store.xml | 27 + .../initial-switch-changelog-tenant-store.xml | 29 + .../parts/0001_initial_schema.xml | 122 + .../tenant-store/parts/0002_initial_data.xml | 2538 +++ .../db/changelog/tenant/changelog-tenant.xml | 27 + .../initial-switch-changelog-tenant.xml | 29 + .../tenant/parts/0001_initial_schema.xml | 9447 +++++++++ .../tenant/parts/0002_initial_data.xml | 16791 ++++++++++++++++ .../src/main/resources/logback-spring.xml | 3 +- ...WithoutDatabaseAndNoJobsConfiguration.java | 25 +- .../TenantDatabaseUpgradeServiceTest.java | 219 + 34 files changed, 30137 insertions(+), 189 deletions(-) delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseIndependentQueryService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseQueryService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseType.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseTypeResolver.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/MySQLQueryService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/DatabaseAwareMigrationContextProvider.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/ExtendedSpringLiquibase.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/ExtendedSpringLiquibaseBuilder.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/ExtendedSpringLiquibaseFactory.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/SchemaUpgradeNeededException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDataSourceFactory.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseStateVerifier.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeService.java create mode 100644 fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant-store/initial-switch-changelog-tenant-store.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0001_initial_schema.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0002_initial_data.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/initial-switch-changelog-tenant.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0001_initial_schema.xml create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0002_initial_data.xml create mode 100644 fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeServiceTest.java diff --git a/README.md b/README.md index 767629fa2..153bf258c 100644 --- a/README.md +++ b/README.md @@ -309,11 +309,11 @@ License This project is licensed under Apache License Version 2.0. See for reference. The Connector/J JDBC Driver client library from MariaDB.org, which is licensed under the LGPL, -is used in development when running integration tests that use the Flyway library. That JDBC +is used in development when running integration tests that use the Liquibase library. That JDBC driver is however not included in and distributed with the Fineract product and is not required to use the product. If you are developer and object to using the LGPL licensed Connector/J JDBC driver, -simply do not run the integration tests that use the Flyway library and/or use another JDBC driver. +simply do not run the integration tests that use the Liquibase library and/or use another JDBC driver. As discussed in [LEGAL-462](https://issues.apache.org/jira/browse/LEGAL-462), this project therefore complies with the [Apache Software Foundation third-party license policy](https://www.apache.org/legal/resolved.html). diff --git a/build.gradle b/build.gradle index 72488eaf8..0ded477e4 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,7 @@ buildscript { dependencies { classpath 'com.bmuschko:gradle-cargo-plugin:2.8.0' classpath 'org.apache.openjpa:openjpa:3.2.0' // when upgrading, also change OpenJPA version repeated in fineract-provider/build.gradle! + classpath('com.google.cloud.tools:jib-layer-filter-extension-gradle:0.1.0') } } @@ -182,12 +183,8 @@ allprojects { dependency "com.google.code.findbugs:jsr305:3.0.2" dependency "commons-codec:commons-codec:1.15" - dependency ('org.flywaydb:flyway-core:8.1.0') { - // https://issues.apache.org/jira/browse/FINERACT-1172 - // https://github.com/apache/fineract/pull/1355 - // https://github.com/flyway/flyway/issues/2957 - // exclude conflicting non- dependencies from https://github.com/flyway/flyway/blob/master/flyway-core/pom.xml - exclude 'software.amazon.awssdk:s3' + dependency ('org.liquibase:liquibase-core:4.5.0') { + exclude 'javax.xml.bind:jaxb-api' } dependency ('org.dom4j:dom4j:2.1.3') { diff --git a/fineract-provider/build.gradle b/fineract-provider/build.gradle index 680875914..56bf75b0b 100644 --- a/fineract-provider/build.gradle +++ b/fineract-provider/build.gradle @@ -326,6 +326,19 @@ jib { } } } + + pluginExtensions { + pluginExtension { + implementation = 'com.google.cloud.tools.jib.gradle.extension.layerfilter.JibLayerFilterExtension' + configuration { + filters { + filter { + glob = '/app/resources/**' + } + } + } + } + } } tasks.jibDockerBuild.dependsOn = ["bootJar"] diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle index 393562ffb..8151f018d 100644 --- a/fineract-provider/dependencies.gradle +++ b/fineract-provider/dependencies.gradle @@ -54,7 +54,7 @@ dependencies { 'org.apache.poi:poi-ooxml-schemas', 'org.apache.tika:tika-core', - 'org.flywaydb:flyway-core', + 'org.liquibase:liquibase-core', 'com.github.librepdf:openpdf', 'com.github.spullara.mustache.java:compiler', diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java index 26e032ae4..0c0b6c4be 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/boot/AbstractApplicationConfiguration.java @@ -20,11 +20,12 @@ package org.apache.fineract.infrastructure.core.boot; import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.ComponentScan; @@ -38,11 +39,11 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, - DataSourceTransactionManagerAutoConfiguration.class, FlywayAutoConfiguration.class, GsonAutoConfiguration.class, - JdbcTemplateAutoConfiguration.class }) + DataSourceTransactionManagerAutoConfiguration.class, GsonAutoConfiguration.class, JdbcTemplateAutoConfiguration.class, + LiquibaseAutoConfiguration.class }) @EnableTransactionManagement @EnableWebSecurity -@EnableConfigurationProperties({ FineractProperties.class }) +@EnableConfigurationProperties({ FineractProperties.class, LiquibaseProperties.class }) @ComponentScan(basePackages = "org.apache.fineract.**") public abstract class AbstractApplicationConfiguration { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/RoutingDataSource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/RoutingDataSource.java index 0a63c7752..6058cc56d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/RoutingDataSource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/RoutingDataSource.java @@ -46,7 +46,7 @@ public class RoutingDataSource extends AbstractDataSource { return determineTargetDataSource().getConnection(); } - private DataSource determineTargetDataSource() { + public DataSource determineTargetDataSource() { return this.dataSourceServiceFactory.determineDataSourceService().retrieveDataSource(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java deleted file mode 100644 index 264f9e1ad..000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java +++ /dev/null @@ -1,169 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.fineract.infrastructure.core.service; - -import static org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection.toJdbcUrl; -import static org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection.toProtocol; - -import com.zaxxer.hikari.HikariConfig; -import java.util.List; -import java.util.Map; -import javax.annotation.PostConstruct; -import javax.sql.DataSource; -import org.apache.fineract.infrastructure.core.config.FineractProperties; -import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; -import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection; -import org.apache.fineract.infrastructure.security.service.TenantDetailsService; -import org.flywaydb.core.Flyway; -import org.flywaydb.core.api.FlywayException; -import org.flywaydb.core.internal.jdbc.DriverDataSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.support.rowset.SqlRowSet; -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 -public class TenantDatabaseUpgradeService { - - private static final Logger LOG = LoggerFactory.getLogger(TenantDatabaseUpgradeService.class); - - private final HikariConfig hikariConfig; - private final TenantDetailsService tenantDetailsService; - protected final DataSource tenantDataSource; - protected final FineractProperties fineractProperties; - - @Autowired - public TenantDatabaseUpgradeService(final HikariConfig hikariConfig, final TenantDetailsService detailsService, - @Qualifier("hikariTenantDataSource") final DataSource dataSource, final FineractProperties fineractProperties) { - this.hikariConfig = hikariConfig; - this.tenantDetailsService = detailsService; - this.tenantDataSource = dataSource; - this.fineractProperties = fineractProperties; - } - - @PostConstruct - public void upgradeAllTenants() { - upgradeTenantDB(); - final List tenants = this.tenantDetailsService.findAllTenants(); - for (final FineractPlatformTenant tenant : tenants) { - final FineractPlatformTenantConnection connection = tenant.getConnection(); - if (connection.isAutoUpdateEnabled()) { - - String protocol = toProtocol(this.tenantDataSource); - String jdbcUrl = toJdbcUrl(protocol, connection.getSchemaServer(), connection.getSchemaServerPort(), - connection.getSchemaName(), connection.getSchemaConnectionParameters()); - DriverDataSource source = new DriverDataSource(Thread.currentThread().getContextClassLoader(), - hikariConfig.getDriverClassName(), jdbcUrl, connection.getSchemaUsername(), connection.getSchemaPassword()); - - final Flyway flyway = Flyway.configure().dataSource(source).locations("sql/migrations/core_db").outOfOrder(true) - .placeholderReplacement(false).configuration(Map.of("flyway.table", "schema_version")) // FINERACT-979 - .load(); - - // Should be removed later when all instances are stabilized - // :FINERACT-1008 - repairFlywayVersionSkip(flyway.getConfiguration().getDataSource()); - - try { - flyway.repair(); - flyway.migrate(); - } catch (FlywayException e) { - String betterMessage = e.getMessage() + "; for Tenant DB URL: " + jdbcUrl + ", username: " - + connection.getSchemaUsername(); - throw new FlywayException(betterMessage, e); - } - } - } - } - - /** - * Initializes, and if required upgrades (using Flyway) the Tenant DB itself. - */ - private void upgradeTenantDB() { - LOG.info("Upgrade tenant DB: {}:{}", fineractProperties.getTenant().getHost(), fineractProperties.getTenant().getPort()); - 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()); - - final Flyway flyway = Flyway.configure().dataSource(tenantDataSource).locations("sql/migrations/list_db").outOfOrder(true) - // FINERACT-773 - .placeholders(Map.of("fineract.tenant.host", fineractProperties.getTenant().getHost(), "fineract.tenant.port", - fineractProperties.getTenant().getPort().toString(), "fineract.tenant.username", - fineractProperties.getTenant().getUsername(), "fineract.tenant.password", - fineractProperties.getTenant().getPassword(), "fineract.tenant.parameters", - fineractProperties.getTenant().getParameters(), "fineract.tenant.timezone", - fineractProperties.getTenant().getTimezone(), "fineract.tenant.description", - fineractProperties.getTenant().getDescription(), "fineract.tenant.identifier", - fineractProperties.getTenant().getIdentifier(), "fineract.tenant.name", fineractProperties.getTenant().getName())) - .configuration(Map.of("flyway.table", "schema_version")) // FINERACT-979 - .load(); - - // Should be removed later when all instances are stabilized - // :FINERACT-1008 - repairFlywayVersionSkip(flyway.getConfiguration().getDataSource()); - - flyway.repair(); - flyway.migrate(); - } - - private void repairFlywayVersionSkip(DataSource source) { - JdbcTemplate template = new JdbcTemplate(source); - LOG.info("repairFlywayVersionSkip: Check whether the version table is in old format "); - SqlRowSet ts = template.queryForRowSet("SHOW TABLES LIKE 'schema_version';"); - if (ts.next()) { - SqlRowSet rs = template.queryForRowSet("SHOW COLUMNS FROM `schema_version` LIKE 'version_rank';"); - if (rs.next()) { - LOG.info("repairFlywayVersionSkip: The schema_version table is in old format, executing repair "); - template.execute("CREATE TABLE `schema_version_history` ( `version_rank` int(11) NOT NULL, " - + " `installed_rank` int(11) NOT NULL, `version` varchar(50) NOT NULL, `description` varchar(200) NOT NULL, " - + "`type` varchar(20) NOT NULL, `script` varchar(1000) NOT NULL, `checksum` int(11) DEFAULT NULL, " - + " `installed_by` varchar(100) NOT NULL, `installed_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, " - + " `execution_time` int(11) NOT NULL, `success` tinyint(1) NOT NULL, PRIMARY KEY (`version`), " - + " KEY `schema_version_vr_idx` (`version_rank`), KEY `schema_version_ir_idx` (`installed_rank`), " - + " KEY `schema_version_s_idx` (`success`));"); - template.execute("INSERT INTO schema_version_history select * from schema_version;"); - template.execute("DROP TABLE schema_version;"); - template.execute("CREATE TABLE `schema_version` ( `installed_rank` int(11) NOT NULL, " - + " `version` varchar(50) DEFAULT NULL, `description` varchar(200) NOT NULL, `type` varchar(20) NOT NULL, " - + " `script` varchar(1000) NOT NULL, `checksum` int(11) DEFAULT NULL, `installed_by` varchar(100) NOT NULL, " - + " `installed_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `execution_time` int(11) NOT NULL, " - + " `success` tinyint(1) NOT NULL, PRIMARY KEY (`installed_rank`), KEY `flyway_schema_history_s_idx` (`success`) );"); - template.execute("INSERT INTO schema_version (installed_rank, version, description, type, script, checksum, " - + "installed_by, installed_on, execution_time, success) SELECT installed_rank, version, description, type, " - + "script, checksum, installed_by, installed_on, execution_time, success FROM schema_version_history;"); - template.execute("DROP TABLE schema_version_history;"); - - LOG.info("repairFlywayVersionSkip: The schema_version repair completed."); - } else { - LOG.info("repairFlywayVersionSkip: The schema_version table format is new, aborting repair"); - } - } else { - LOG.info("repairFlywayVersionSkip: The schema_version table does not exist, aborting repair"); - } - } -} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseIndependentQueryService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseIndependentQueryService.java new file mode 100644 index 000000000..d472521c7 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseIndependentQueryService.java @@ -0,0 +1,63 @@ +/** + * 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.service.database; + +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; + +import java.sql.SQLException; +import java.util.Collection; +import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class DatabaseIndependentQueryService implements DatabaseQueryService { + + private final Collection queryServices; + + @Autowired + public DatabaseIndependentQueryService(Collection queryServices) { + this.queryServices = queryServices; + } + + private DatabaseQueryService choose(DataSource dataSource) { + try { + DatabaseQueryService result = null; + if (isNotEmpty(queryServices)) { + result = queryServices.stream().filter(qs -> qs.isSupported(dataSource)).findAny().orElse(null); + } + if (result == null) { + throw new IllegalStateException("DataSource not supported: " + dataSource.getConnection().getMetaData().getURL()); + } + return result; + } catch (SQLException e) { + throw new RuntimeException("Error while trying to choose the proper query service", e); + } + } + + @Override + public boolean isSupported(DataSource dataSource) { + return true; + } + + @Override + public boolean isTablePresent(DataSource dataSource, String tableName) { + return choose(dataSource).isTablePresent(dataSource, tableName); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseQueryService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseQueryService.java new file mode 100644 index 000000000..38fc65f85 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseQueryService.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.core.service.database; + +import javax.sql.DataSource; + +public interface DatabaseQueryService { + + boolean isSupported(DataSource dataSource); + + boolean isTablePresent(DataSource dataSource, String tableName); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseType.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseType.java new file mode 100644 index 000000000..feaea7d28 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseType.java @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.core.service.database; + +public enum DatabaseType { + MYSQL +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseTypeResolver.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseTypeResolver.java new file mode 100644 index 000000000..bcfab30b5 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/DatabaseTypeResolver.java @@ -0,0 +1,70 @@ +/** + * 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.service.database; + +import static org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection.toProtocol; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import javax.sql.DataSource; +import org.apache.fineract.infrastructure.core.service.RoutingDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class DatabaseTypeResolver { + + private final Map protocolMapping = Map.of("jdbc:mariadb", DatabaseType.MYSQL, "jdbc:mysql", DatabaseType.MYSQL); + + private final Map dataSourceCache = new ConcurrentHashMap<>(); + + private final RoutingDataSource routingDataSource; + + @Autowired + public DatabaseTypeResolver(RoutingDataSource routingDataSource) { + this.routingDataSource = routingDataSource; + } + + public boolean isMySQL() { + return isMySQL(routingDataSource); + } + + public boolean isMySQL(DataSource dataSource) { + return DatabaseType.MYSQL.equals(databaseType(dataSource)); + } + + public DatabaseType databaseType() { + return databaseType(routingDataSource); + } + + public DatabaseType databaseType(DataSource dataSource) { + DataSource unwrappedDataSource = dataSource; + if (dataSource instanceof RoutingDataSource) { + unwrappedDataSource = ((RoutingDataSource) dataSource).determineTargetDataSource(); + } + return dataSourceCache.computeIfAbsent(unwrappedDataSource, (ds) -> { + String protocol = toProtocol(ds); + DatabaseType databaseType = protocolMapping.get(protocol); + if (databaseType == null) { + throw new IllegalArgumentException("The DataSource is not supported"); + } + return databaseType; + }); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/MySQLQueryService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/MySQLQueryService.java new file mode 100644 index 000000000..4896a5b5c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/database/MySQLQueryService.java @@ -0,0 +1,47 @@ +/** + * 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.service.database; + +import static java.lang.String.format; +import static org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection.toProtocol; + +import java.util.Set; +import javax.sql.DataSource; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.stereotype.Component; + +@Component +public class MySQLQueryService implements DatabaseQueryService { + + private final Set supportedProtocols = Set.of("jdbc:mariadb", "jdbc:mysql"); + + @Override + public boolean isSupported(DataSource dataSource) { + String protocol = toProtocol(dataSource); + return supportedProtocols.contains(protocol.toLowerCase()); + } + + @Override + public boolean isTablePresent(DataSource dataSource, String tableName) { + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + SqlRowSet rs = jdbcTemplate.queryForRowSet(format("SHOW TABLES LIKE '%s'", tableName)); + return rs.next(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/DatabaseAwareMigrationContextProvider.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/DatabaseAwareMigrationContextProvider.java new file mode 100644 index 000000000..2f27b0820 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/DatabaseAwareMigrationContextProvider.java @@ -0,0 +1,48 @@ +/** + * 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.service.migration; + +import java.util.Map; +import javax.sql.DataSource; +import org.apache.fineract.infrastructure.core.service.database.DatabaseType; +import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class DatabaseAwareMigrationContextProvider { + + private final Map contextMapping = Map.of(DatabaseType.MYSQL, "mysql"); + + private final DatabaseTypeResolver databaseTypeResolver; + + @Autowired + public DatabaseAwareMigrationContextProvider(DatabaseTypeResolver databaseTypeResolver) { + this.databaseTypeResolver = databaseTypeResolver; + } + + public String provide(DataSource dataSource) { + DatabaseType databaseType = databaseTypeResolver.databaseType(dataSource); + String context = contextMapping.get(databaseType); + if (context == null) { + throw new IllegalStateException("Database is not supported"); + } + return context; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/ExtendedSpringLiquibase.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/ExtendedSpringLiquibase.java new file mode 100644 index 000000000..c92302964 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/ExtendedSpringLiquibase.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.core.service.migration; + +import java.sql.SQLException; +import liquibase.Liquibase; +import liquibase.exception.DatabaseException; +import liquibase.exception.LiquibaseException; +import liquibase.integration.spring.SpringLiquibase; + +public class ExtendedSpringLiquibase extends SpringLiquibase { + + public void changeLogSync() throws LiquibaseException { + try (Liquibase liquibase = createLiquibase(getDataSource().getConnection())) { + liquibase.changeLogSync(getContexts()); + } catch (SQLException e) { + throw new DatabaseException(e); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/ExtendedSpringLiquibaseBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/ExtendedSpringLiquibaseBuilder.java new file mode 100644 index 000000000..484fe9476 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/ExtendedSpringLiquibaseBuilder.java @@ -0,0 +1,125 @@ +/** + * 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.service.migration; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +import com.google.common.base.Joiner; +import java.io.File; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.sql.DataSource; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.core.io.ResourceLoader; + +public class ExtendedSpringLiquibaseBuilder { + + private final Set contexts = new HashSet<>(); + private final Map changeLogParameters = new HashMap<>(); + private final boolean clearCheckSums; + private final String liquibaseSchema; + private final String liquibaseTableSpace; + private final String databaseChangeLogTable; + private final String databaseChangeLogLockTable; + private final boolean shouldRun; + private final String labels; + private final File rollbackFile; + private final boolean testRollbackOnUpdate; + private final String tag; + private String changeLog; + private ResourceLoader resourceLoader; + private String defaultSchema; + private boolean dropFirst; + private DataSource dataSource; + + public ExtendedSpringLiquibaseBuilder(LiquibaseProperties liquibaseProperties) { + this.defaultSchema = liquibaseProperties.getDefaultSchema(); + this.dropFirst = liquibaseProperties.isDropFirst(); + if (liquibaseProperties.getParameters() != null) { + this.changeLogParameters.putAll(liquibaseProperties.getParameters()); + } + this.changeLog = liquibaseProperties.getChangeLog(); + if (isNotBlank(liquibaseProperties.getContexts())) { + this.contexts.add(liquibaseProperties.getContexts()); + } + this.clearCheckSums = liquibaseProperties.isClearChecksums(); + this.liquibaseSchema = liquibaseProperties.getLiquibaseSchema(); + this.liquibaseTableSpace = liquibaseProperties.getLiquibaseTablespace(); + this.databaseChangeLogTable = liquibaseProperties.getDatabaseChangeLogTable(); + this.databaseChangeLogLockTable = liquibaseProperties.getDatabaseChangeLogLockTable(); + this.shouldRun = liquibaseProperties.isEnabled(); + this.labels = liquibaseProperties.getLabels(); + this.rollbackFile = liquibaseProperties.getRollbackFile(); + this.testRollbackOnUpdate = liquibaseProperties.isTestRollbackOnUpdate(); + this.tag = liquibaseProperties.getTag(); + } + + public ExtendedSpringLiquibaseBuilder withDataSource(DataSource dataSource) { + this.dataSource = dataSource; + return this; + } + + public ExtendedSpringLiquibaseBuilder withContext(String context) { + if (isNotBlank(context)) { + this.contexts.add(context); + } + return this; + } + + public ExtendedSpringLiquibaseBuilder withContexts(String... contexts) { + return withContexts(Arrays.asList(contexts)); + } + + public ExtendedSpringLiquibaseBuilder withContexts(Collection contexts) { + this.contexts.addAll(contexts); + return this; + } + + public ExtendedSpringLiquibaseBuilder withResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + return this; + } + + public ExtendedSpringLiquibase build() { + ExtendedSpringLiquibase result = new ExtendedSpringLiquibase(); + String liquibaseContexts = Joiner.on(",").join(contexts); + result.setContexts(liquibaseContexts); + result.setDataSource(dataSource); + result.setChangeLog(changeLog); + result.setResourceLoader(resourceLoader); + result.setDefaultSchema(defaultSchema); + result.setDropFirst(dropFirst); + result.setChangeLogParameters(changeLogParameters); + result.setClearCheckSums(clearCheckSums); + result.setLiquibaseSchema(liquibaseSchema); + result.setLiquibaseTablespace(liquibaseTableSpace); + result.setDatabaseChangeLogTable(databaseChangeLogTable); + result.setDatabaseChangeLogLockTable(databaseChangeLogLockTable); + result.setShouldRun(shouldRun); + result.setLabels(labels); + result.setRollbackFile(rollbackFile); + result.setTestRollbackOnUpdate(testRollbackOnUpdate); + result.setTag(tag); + return result; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/ExtendedSpringLiquibaseFactory.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/ExtendedSpringLiquibaseFactory.java new file mode 100644 index 000000000..4c40e35d7 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/ExtendedSpringLiquibaseFactory.java @@ -0,0 +1,50 @@ +/** + * 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.service.migration; + +import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.stereotype.Component; + +@Component +public class ExtendedSpringLiquibaseFactory { + + private final LiquibaseProperties liquibaseProperties; + private final ResourceLoader resourceLoader; + private final Environment environment; + private final DatabaseAwareMigrationContextProvider databaseAwareMigrationContextProvider; + + @Autowired + public ExtendedSpringLiquibaseFactory(LiquibaseProperties liquibaseProperties, ResourceLoader resourceLoader, Environment environment, + DatabaseAwareMigrationContextProvider databaseAwareMigrationContextProvider) { + this.liquibaseProperties = liquibaseProperties; + this.resourceLoader = resourceLoader; + this.environment = environment; + this.databaseAwareMigrationContextProvider = databaseAwareMigrationContextProvider; + } + + public ExtendedSpringLiquibase create(DataSource dataSource, String... contexts) { + String databaseContext = databaseAwareMigrationContextProvider.provide(dataSource); + return new ExtendedSpringLiquibaseBuilder(liquibaseProperties).withDataSource(dataSource).withResourceLoader(resourceLoader) + .withContexts(contexts).withContexts(environment.getActiveProfiles()).withContext(databaseContext).build(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/SchemaUpgradeNeededException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/SchemaUpgradeNeededException.java new file mode 100644 index 000000000..df1708f08 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/SchemaUpgradeNeededException.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.core.service.migration; + +public class SchemaUpgradeNeededException extends RuntimeException { + + public SchemaUpgradeNeededException(String message) { + super(message); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDataSourceFactory.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDataSourceFactory.java new file mode 100644 index 000000000..11c3f6aec --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDataSourceFactory.java @@ -0,0 +1,65 @@ +/** + * 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.service.migration; + +import static org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection.toJdbcUrl; +import static org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection.toProtocol; + +import com.zaxxer.hikari.HikariDataSource; +import javax.sql.DataSource; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenantConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +@Component +public class TenantDataSourceFactory { + + private static final Logger LOG = LoggerFactory.getLogger(TenantDataSourceFactory.class); + + private final HikariDataSource tenantDataSource; + + @Autowired + public TenantDataSourceFactory(@Qualifier("hikariTenantDataSource") HikariDataSource tenantDataSource) { + this.tenantDataSource = tenantDataSource; + } + + public DataSource create(FineractPlatformTenant tenant) { + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setDriverClassName(tenantDataSource.getDriverClassName()); + dataSource.setDataSourceProperties(tenantDataSource.getDataSourceProperties()); + dataSource.setMinimumIdle(tenantDataSource.getMinimumIdle()); + dataSource.setMaximumPoolSize(tenantDataSource.getMaximumPoolSize()); + dataSource.setIdleTimeout(tenantDataSource.getIdleTimeout()); + dataSource.setConnectionTestQuery(tenantDataSource.getConnectionTestQuery()); + + FineractPlatformTenantConnection tenantConnection = tenant.getConnection(); + dataSource.setUsername(tenantConnection.getSchemaUsername()); + dataSource.setPassword(tenantConnection.getSchemaPassword()); + String protocol = toProtocol(tenantDataSource); + String tenantJdbcUrl = toJdbcUrl(protocol, tenantConnection.getSchemaServer(), tenantConnection.getSchemaServerPort(), + tenantConnection.getSchemaName(), tenantConnection.getSchemaConnectionParameters()); + LOG.debug("JDBC URL for tenant {} is {}", tenant.getTenantIdentifier(), tenantJdbcUrl); + dataSource.setJdbcUrl(tenantJdbcUrl); + return dataSource; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseStateVerifier.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseStateVerifier.java new file mode 100644 index 000000000..63e7e766b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseStateVerifier.java @@ -0,0 +1,83 @@ +/** + * 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.service.migration; + +import java.util.Map; +import java.util.Objects; +import javax.sql.DataSource; +import org.apache.fineract.infrastructure.core.service.database.DatabaseIndependentQueryService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Component; + +@Component +public class TenantDatabaseStateVerifier { + + private static final int TENANT_STORE_LATEST_FLYWAY_VERSION = 6; + private static final String TENANT_STORE_LATEST_FLYWAY_SCRIPT_NAME = "V6__add_unique_tenant_identifier.sql"; + private static final int TENANT_STORE_LATEST_FLYWAY_SCRIPT_CHECKSUM = -43094919; + private static final int TENANT_LATEST_FLYWAY_VERSION = 392; + private static final String TENANT_LATEST_FLYWAY_SCRIPT_NAME = "V392__interest_recovery_conf_for_rescedule.sql"; + private static final int TENANT_LATEST_FLYWAY_SCRIPT_CHECKSUM = 1102395052; + + private final LiquibaseProperties liquibaseProperties; + private final DatabaseIndependentQueryService dbQueryService; + + @Autowired + public TenantDatabaseStateVerifier(LiquibaseProperties liquibaseProperties, DatabaseIndependentQueryService dbQueryService) { + this.liquibaseProperties = liquibaseProperties; + this.dbQueryService = dbQueryService; + } + + public boolean isFirstLiquibaseMigration(DataSource dataSource) { + boolean databaseChangelogTableExists = dbQueryService.isTablePresent(dataSource, "DATABASECHANGELOG"); + return !databaseChangelogTableExists; + } + + public boolean isFlywayPresent(DataSource dataSource) { + return dbQueryService.isTablePresent(dataSource, "schema_version"); + } + + public boolean isLiquibaseDisabled() { + return !liquibaseProperties.isEnabled(); + } + + public boolean isTenantStoreOnLatestUpgradableVersion(DataSource dataSource) { + return isOnLatestFlywayVersion(TENANT_STORE_LATEST_FLYWAY_VERSION, TENANT_STORE_LATEST_FLYWAY_SCRIPT_NAME, + TENANT_STORE_LATEST_FLYWAY_SCRIPT_CHECKSUM, dataSource); + } + + public boolean isTenantOnLatestUpgradableVersion(DataSource dataSource) { + return isOnLatestFlywayVersion(TENANT_LATEST_FLYWAY_VERSION, TENANT_LATEST_FLYWAY_SCRIPT_NAME, TENANT_LATEST_FLYWAY_SCRIPT_CHECKSUM, + dataSource); + } + + private boolean isOnLatestFlywayVersion(int version, String scriptName, int checksum, DataSource dataSource) { + Map paramMap = Map.of("latestFlywayVersion", version, "latestFlywayScriptName", scriptName, + "latestFlywayScriptChecksum", checksum); + + NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + Integer result = jdbcTemplate.queryForObject( + "SELECT COUNT(script) FROM schema_version " + "WHERE version = :latestFlywayVersion " + + "AND script = :latestFlywayScriptName " + "AND checksum = :latestFlywayScriptChecksum " + "AND success = 1", + paramMap, Integer.class); + return Objects.equals(result, 1); + } +} 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 new file mode 100644 index 000000000..7086259dd --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeService.java @@ -0,0 +1,146 @@ +/** + * 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.service.migration; + +import static org.apache.commons.collections4.CollectionUtils.isNotEmpty; + +import com.zaxxer.hikari.HikariDataSource; +import java.util.List; +import java.util.function.Function; +import javax.sql.DataSource; +import liquibase.exception.LiquibaseException; +import liquibase.integration.spring.SpringLiquibase; +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; +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 +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 final TenantDetailsService tenantDetailsService; + private final HikariDataSource tenantDataSource; + private final FineractProperties fineractProperties; + private final TenantDatabaseStateVerifier databaseStateVerifier; + private final ExtendedSpringLiquibaseFactory liquibaseFactory; + private final TenantDataSourceFactory tenantDataSourceFactory; + + @Autowired + public TenantDatabaseUpgradeService(final TenantDetailsService detailsService, + @Qualifier("hikariTenantDataSource") final HikariDataSource tenantDataSource, final FineractProperties fineractProperties, + TenantDatabaseStateVerifier databaseStateVerifier, ExtendedSpringLiquibaseFactory liquibaseFactory, + TenantDataSourceFactory tenantDataSourceFactory) { + this.tenantDetailsService = detailsService; + this.tenantDataSource = tenantDataSource; + this.fineractProperties = fineractProperties; + this.databaseStateVerifier = databaseStateVerifier; + this.liquibaseFactory = liquibaseFactory; + this.tenantDataSourceFactory = tenantDataSourceFactory; + } + + @Override + public void afterPropertiesSet() throws Exception { + if (databaseStateVerifier.isLiquibaseDisabled()) { + LOG.warn("Liquibase is disabled. Not upgrading any database."); + return; + } + try { + upgradeTenantStore(); + upgradeIndividualTenants(); + } catch (LiquibaseException e) { + throw new RuntimeException("Error while migrating the schema", e); + } + } + + private void upgradeTenantStore() throws LiquibaseException { + 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); + applyInitialLiquibase(tenantDataSource, liquibase, "tenant store", + (ds) -> !databaseStateVerifier.isTenantStoreOnLatestUpgradableVersion(ds)); + } + SpringLiquibase liquibase = liquibaseFactory.create(tenantDataSource, TENANT_STORE_DB_CONTEXT); + liquibase.afterPropertiesSet(); + 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()); + } + + private void upgradeIndividualTenants() throws LiquibaseException { + LOG.warn("Upgrading all tenants"); + List tenants = tenantDetailsService.findAllTenants(); + if (isNotEmpty(tenants)) { + for (FineractPlatformTenant tenant : tenants) { + upgradeIndividualTenant(tenant); + } + } + LOG.warn("Tenant upgrades have finished"); + } + + private void upgradeIndividualTenant(FineractPlatformTenant tenant) throws LiquibaseException { + 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); + applyInitialLiquibase(tenantDataSource, liquibase, tenant.getTenantIdentifier(), + (ds) -> !databaseStateVerifier.isTenantOnLatestUpgradableVersion(ds)); + } + SpringLiquibase tenantLiquibase = liquibaseFactory.create(tenantDataSource, TENANT_DB_CONTEXT); + tenantLiquibase.afterPropertiesSet(); + LOG.info("Upgrade for tenant {} has finished", tenant.getTenantIdentifier()); + } + + private void applyInitialLiquibase(DataSource dataSource, ExtendedSpringLiquibase liquibase, String id, + Function 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"); + 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); + liquibase.changeLogSync(); + LOG.warn("Liquibase changelog sync is complete"); + } else { + liquibase.afterPropertiesSet(); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java index 253d601a0..48267959a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/useradministration/service/AppUserWritePlatformServiceJpaRepositoryImpl.java @@ -314,6 +314,9 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit */ private PlatformDataIntegrityException handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dve) { + // TODO: this needs to be fixed. The error condition should be independent from the underlying message and + // naming + // of the constraint if (realCause.getMessage().contains("'username_org'")) { final String username = command.stringValueOfParameterNamed("username"); final StringBuilder defaultMessageBuilder = new StringBuilder("User with username ").append(username) @@ -322,6 +325,9 @@ public class AppUserWritePlatformServiceJpaRepositoryImpl implements AppUserWrit username); } + // TODO: this needs to be fixed. The error condition should be independent from the underlying message and + // naming + // of the constraint if (realCause.getMessage().contains("'unique_self_client'")) { return new PlatformDataIntegrityException("error.msg.user.self.service.user.already.exist", "Self Service User Id is already created. Go to Admin->Users to edit or delete the self-service user."); diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index 92b6f4eb0..7940a7952 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -74,3 +74,17 @@ spring.datasource.hikari.dataSourceProperties['maintainTimeStats']=${FINERACT_HI # TODO FINERACT-890: com.mysql.cj.log.Slf4JLogger spring.datasource.hikari.dataSourceProperties['logSlowQueries']=${FINERACT_HIKARI_DS_PROPERTIES_LOG_SLOW_QUERIES:true} spring.datasource.hikari.dataSourceProperties['dumpQueriesOnException']=${FINERACT_HIKARI_DS_PROPERTIES_DUMP_QUERIES_IN_EXCEPTION:true} + +# Liquibase configuration +spring.liquibase.enabled=true +spring.liquibase.changeLog=classpath:/db/changelog/db.changelog-master.xml + +spring.liquibase.parameters.fineract.tenant.identifier=${fineract.tenant.identifier} +spring.liquibase.parameters.fineract.tenant.description=${fineract.tenant.description} +spring.liquibase.parameters.fineract.tenant.timezone=${fineract.tenant.timezone} +spring.liquibase.parameters.fineract.tenant.schema-name=${fineract.tenant.name} +spring.liquibase.parameters.fineract.tenant.host=${fineract.tenant.host} +spring.liquibase.parameters.fineract.tenant.port=${fineract.tenant.port} +spring.liquibase.parameters.fineract.tenant.username=${fineract.tenant.username} +spring.liquibase.parameters.fineract.tenant.password=${fineract.tenant.password} +spring.liquibase.parameters.fineract.tenant.parameters=${fineract.tenant.parameters} 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 new file mode 100644 index 000000000..7e5cd0f97 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml new file mode 100644 index 000000000..05449fa33 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant-store/initial-switch-changelog-tenant-store.xml b/fineract-provider/src/main/resources/db/changelog/tenant-store/initial-switch-changelog-tenant-store.xml new file mode 100644 index 000000000..130f98090 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant-store/initial-switch-changelog-tenant-store.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0001_initial_schema.xml b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0001_initial_schema.xml new file mode 100644 index 000000000..fb512c03a --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0001_initial_schema.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0002_initial_data.xml b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0002_initial_data.xml new file mode 100644 index 000000000..7586eb566 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0002_initial_data.xml @@ -0,0 +1,2538 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml new file mode 100644 index 000000000..05449fa33 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/initial-switch-changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/initial-switch-changelog-tenant.xml new file mode 100644 index 000000000..130f98090 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/initial-switch-changelog-tenant.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0001_initial_schema.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0001_initial_schema.xml new file mode 100644 index 000000000..97c96088b --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0001_initial_schema.xml @@ -0,0 +1,9447 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0002_initial_data.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0002_initial_data.xml new file mode 100644 index 000000000..d30f543c6 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0002_initial_data.xml @@ -0,0 +1,16791 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/logback-spring.xml b/fineract-provider/src/main/resources/logback-spring.xml index 99e6e8ce2..98ae105f0 100644 --- a/fineract-provider/src/main/resources/logback-spring.xml +++ b/fineract-provider/src/main/resources/logback-spring.xml @@ -36,7 +36,8 @@ - + + diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/configuration/spring/TestsWithoutDatabaseAndNoJobsConfiguration.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/configuration/spring/TestsWithoutDatabaseAndNoJobsConfiguration.java index 760b54b75..de35c63d2 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/configuration/spring/TestsWithoutDatabaseAndNoJobsConfiguration.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/configuration/spring/TestsWithoutDatabaseAndNoJobsConfiguration.java @@ -18,10 +18,14 @@ */ package org.apache.fineract.infrastructure.configuration.spring; +import static org.mockito.Mockito.mock; + import javax.sql.DataSource; import org.apache.fineract.infrastructure.core.boot.AbstractApplicationConfiguration; import org.apache.fineract.infrastructure.core.config.FineractProperties; -import org.apache.fineract.infrastructure.core.service.TenantDatabaseUpgradeService; +import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; +import org.apache.fineract.infrastructure.core.service.migration.TenantDataSourceFactory; +import org.apache.fineract.infrastructure.core.service.migration.TenantDatabaseUpgradeService; import org.apache.fineract.infrastructure.jobs.service.JobRegisterService; import org.mockito.Mockito; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -38,16 +42,27 @@ import org.springframework.security.crypto.password.PasswordEncoder; @EnableConfigurationProperties({ FineractProperties.class }) public class TestsWithoutDatabaseAndNoJobsConfiguration extends AbstractApplicationConfiguration { + @Bean + public TenantDataSourceFactory tenantDataSourceFactory() { + return new TenantDataSourceFactory(null) { + + @Override + public DataSource create(FineractPlatformTenant tenant) { + return mock(DataSource.class); + } + }; + } + /** * Override TenantDatabaseUpgradeService binding, because the real one has a @PostConstruct upgradeAllTenants() * which accesses the database on start-up. */ @Bean public TenantDatabaseUpgradeService tenantDatabaseUpgradeService() { - return new TenantDatabaseUpgradeService(null, null, null, null) { + return new TenantDatabaseUpgradeService(null, null, null, null, null, null) { @Override - public void upgradeAllTenants() { + public void afterPropertiesSet() { // NOOP } }; @@ -59,7 +74,7 @@ public class TestsWithoutDatabaseAndNoJobsConfiguration extends AbstractApplicat */ @Bean public JobRegisterService jobRegisterServiceImpl() { - JobRegisterService mockJobRegisterService = Mockito.mock(JobRegisterService.class); + JobRegisterService mockJobRegisterService = mock(JobRegisterService.class); return mockJobRegisterService; } @@ -68,7 +83,7 @@ public class TestsWithoutDatabaseAndNoJobsConfiguration extends AbstractApplicat */ @Bean public DataSource hikariTenantDataSource() { - DataSource mockDataSource = Mockito.mock(DataSource.class, Mockito.RETURNS_MOCKS); + DataSource mockDataSource = mock(DataSource.class, Mockito.RETURNS_MOCKS); return mockDataSource; } diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeServiceTest.java new file mode 100644 index 000000000..5e334139c --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/core/service/migration/TenantDatabaseUpgradeServiceTest.java @@ -0,0 +1,219 @@ +/** + * 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.service.migration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import com.zaxxer.hikari.HikariDataSource; +import java.util.List; +import javax.sql.DataSource; +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.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class TenantDatabaseUpgradeServiceTest { + + @Mock + private TenantDetailsService tenantDetailsService; + @Mock + private HikariDataSource tenantDataSource; + @Mock + private FineractProperties fineractProperties; + @Mock + private TenantDatabaseStateVerifier databaseStateVerifier; + @Mock + private ExtendedSpringLiquibaseFactory liquibaseFactory; + @Mock + private TenantDataSourceFactory tenantDataSourceFactory; + + private TenantDatabaseUpgradeService underTest; + + @BeforeEach + public void setUp() { + underTest = new TenantDatabaseUpgradeService(tenantDetailsService, tenantDataSource, fineractProperties, databaseStateVerifier, + liquibaseFactory, tenantDataSourceFactory); + } + + @Test + public void testAfterPropertiesSetDoesNotDoAnythingWhenLiquibaseIsDisabled() throws Exception { + // given + given(databaseStateVerifier.isLiquibaseDisabled()).willReturn(true); + // when + underTest.afterPropertiesSet(); + // then + verify(databaseStateVerifier).isLiquibaseDisabled(); + verifyNoMoreInteractions(databaseStateVerifier); + verifyNoInteractions(tenantDetailsService, tenantDataSource, fineractProperties, liquibaseFactory, tenantDataSourceFactory); + } + + @Test + public void testAfterPropertiesSetGetsLiquibaseAppliedWhenStartingFromScratch() throws Exception { + // given + ExtendedSpringLiquibase initialTenantStoreLiquibase = mock(ExtendedSpringLiquibase.class); + ExtendedSpringLiquibase tenantStoreLiquibase = mock(ExtendedSpringLiquibase.class); + given(databaseStateVerifier.isLiquibaseDisabled()).willReturn(false); + given(databaseStateVerifier.isFirstLiquibaseMigration(tenantDataSource)).willReturn(true); + given(databaseStateVerifier.isFlywayPresent(tenantDataSource)).willReturn(false); + given(fineractProperties.getTenant()).willReturn(new FineractProperties.FineractTenantProperties()); + given(liquibaseFactory.create(tenantDataSource, "tenant_store_db", "initial_switch")).willReturn(initialTenantStoreLiquibase); + given(liquibaseFactory.create(tenantDataSource, "tenant_store_db")).willReturn(tenantStoreLiquibase); + + ExtendedSpringLiquibase initialTenantLiquibase = mock(ExtendedSpringLiquibase.class); + ExtendedSpringLiquibase tenantLiquibase = mock(ExtendedSpringLiquibase.class); + FineractPlatformTenant defaultTenant = mock(FineractPlatformTenant.class); + List allTenants = List.of(defaultTenant); + DataSource defaultTenantDataSource = mock(DataSource.class); + given(databaseStateVerifier.isFirstLiquibaseMigration(defaultTenantDataSource)).willReturn(true); + given(databaseStateVerifier.isFlywayPresent(defaultTenantDataSource)).willReturn(false); + given(tenantDetailsService.findAllTenants()).willReturn(allTenants); + given(tenantDataSourceFactory.create(defaultTenant)).willReturn(defaultTenantDataSource); + given(liquibaseFactory.create(defaultTenantDataSource, "tenant_db", "initial_switch")).willReturn(initialTenantLiquibase); + given(liquibaseFactory.create(defaultTenantDataSource, "tenant_db")).willReturn(tenantLiquibase); + // when + underTest.afterPropertiesSet(); + // then + verify(initialTenantStoreLiquibase).afterPropertiesSet(); + verify(tenantStoreLiquibase).afterPropertiesSet(); + verify(initialTenantLiquibase).afterPropertiesSet(); + verify(tenantLiquibase).afterPropertiesSet(); + } + + @Test + public void testAfterPropertiesSetSyncsTheInitialChangeLogAndAppliesRemainingChangesWhenFlywayIsOnLatestVersion() throws Exception { + // given + ExtendedSpringLiquibase initialTenantStoreLiquibase = mock(ExtendedSpringLiquibase.class); + ExtendedSpringLiquibase tenantStoreLiquibase = mock(ExtendedSpringLiquibase.class); + given(databaseStateVerifier.isLiquibaseDisabled()).willReturn(false); + given(databaseStateVerifier.isFirstLiquibaseMigration(tenantDataSource)).willReturn(true); + given(databaseStateVerifier.isFlywayPresent(tenantDataSource)).willReturn(true); + given(databaseStateVerifier.isTenantStoreOnLatestUpgradableVersion(tenantDataSource)).willReturn(true); + given(fineractProperties.getTenant()).willReturn(new FineractProperties.FineractTenantProperties()); + given(liquibaseFactory.create(tenantDataSource, "tenant_store_db", "initial_switch")).willReturn(initialTenantStoreLiquibase); + given(liquibaseFactory.create(tenantDataSource, "tenant_store_db")).willReturn(tenantStoreLiquibase); + + ExtendedSpringLiquibase initialTenantLiquibase = mock(ExtendedSpringLiquibase.class); + ExtendedSpringLiquibase tenantLiquibase = mock(ExtendedSpringLiquibase.class); + FineractPlatformTenant defaultTenant = mock(FineractPlatformTenant.class); + List allTenants = List.of(defaultTenant); + DataSource defaultTenantDataSource = mock(DataSource.class); + given(databaseStateVerifier.isFirstLiquibaseMigration(defaultTenantDataSource)).willReturn(true); + given(databaseStateVerifier.isFlywayPresent(defaultTenantDataSource)).willReturn(true); + given(databaseStateVerifier.isTenantOnLatestUpgradableVersion(defaultTenantDataSource)).willReturn(true); + given(tenantDetailsService.findAllTenants()).willReturn(allTenants); + given(tenantDataSourceFactory.create(defaultTenant)).willReturn(defaultTenantDataSource); + given(liquibaseFactory.create(defaultTenantDataSource, "tenant_db", "initial_switch")).willReturn(initialTenantLiquibase); + given(liquibaseFactory.create(defaultTenantDataSource, "tenant_db")).willReturn(tenantLiquibase); + // when + underTest.afterPropertiesSet(); + // then + verify(initialTenantStoreLiquibase).changeLogSync(); + verify(tenantStoreLiquibase).afterPropertiesSet(); + verify(initialTenantLiquibase).changeLogSync(); + verify(tenantLiquibase).afterPropertiesSet(); + } + + @Test + public void testAfterPropertiesSetFailsWhenTenantStoreFlywayIsNotOnLatestVersion() throws Exception { + // given + ExtendedSpringLiquibase initialTenantStoreLiquibase = mock(ExtendedSpringLiquibase.class); + ExtendedSpringLiquibase tenantStoreLiquibase = mock(ExtendedSpringLiquibase.class); + given(databaseStateVerifier.isLiquibaseDisabled()).willReturn(false); + given(databaseStateVerifier.isFirstLiquibaseMigration(tenantDataSource)).willReturn(true); + given(databaseStateVerifier.isFlywayPresent(tenantDataSource)).willReturn(true); + given(databaseStateVerifier.isTenantStoreOnLatestUpgradableVersion(tenantDataSource)).willReturn(false); + given(fineractProperties.getTenant()).willReturn(new FineractProperties.FineractTenantProperties()); + given(liquibaseFactory.create(tenantDataSource, "tenant_store_db", "initial_switch")).willReturn(initialTenantStoreLiquibase); + given(liquibaseFactory.create(tenantDataSource, "tenant_store_db")).willReturn(tenantStoreLiquibase); + + ExtendedSpringLiquibase initialTenantLiquibase = mock(ExtendedSpringLiquibase.class); + ExtendedSpringLiquibase tenantLiquibase = mock(ExtendedSpringLiquibase.class); + FineractPlatformTenant defaultTenant = mock(FineractPlatformTenant.class); + List allTenants = List.of(defaultTenant); + DataSource defaultTenantDataSource = mock(DataSource.class); + given(databaseStateVerifier.isFirstLiquibaseMigration(defaultTenantDataSource)).willReturn(true); + given(databaseStateVerifier.isFlywayPresent(defaultTenantDataSource)).willReturn(true); + given(databaseStateVerifier.isTenantOnLatestUpgradableVersion(defaultTenantDataSource)).willReturn(true); + given(tenantDetailsService.findAllTenants()).willReturn(allTenants); + given(tenantDataSourceFactory.create(defaultTenant)).willReturn(defaultTenantDataSource); + given(liquibaseFactory.create(defaultTenantDataSource, "tenant_db", "initial_switch")).willReturn(initialTenantLiquibase); + given(liquibaseFactory.create(defaultTenantDataSource, "tenant_db")).willReturn(tenantLiquibase); + // when + SchemaUpgradeNeededException result = assertThrows(SchemaUpgradeNeededException.class, () -> underTest.afterPropertiesSet()); + // then + assertThat(result).isNotNull(); + verify(liquibaseFactory).create(eq(tenantDataSource), any()); + verifyNoMoreInteractions(liquibaseFactory); + verifyNoInteractions(initialTenantStoreLiquibase, tenantStoreLiquibase, initialTenantLiquibase, tenantLiquibase); + } + + @Test + public void testAfterPropertiesSetFailsWhenTenantFlywayIsNotOnLatestVersion() throws Exception { + // given + ExtendedSpringLiquibase initialTenantStoreLiquibase = mock(ExtendedSpringLiquibase.class); + ExtendedSpringLiquibase tenantStoreLiquibase = mock(ExtendedSpringLiquibase.class); + given(databaseStateVerifier.isLiquibaseDisabled()).willReturn(false); + given(databaseStateVerifier.isFirstLiquibaseMigration(tenantDataSource)).willReturn(true); + given(databaseStateVerifier.isFlywayPresent(tenantDataSource)).willReturn(true); + given(databaseStateVerifier.isTenantStoreOnLatestUpgradableVersion(tenantDataSource)).willReturn(true); + given(fineractProperties.getTenant()).willReturn(new FineractProperties.FineractTenantProperties()); + given(liquibaseFactory.create(tenantDataSource, "tenant_store_db", "initial_switch")).willReturn(initialTenantStoreLiquibase); + given(liquibaseFactory.create(tenantDataSource, "tenant_store_db")).willReturn(tenantStoreLiquibase); + + ExtendedSpringLiquibase initialTenantLiquibase = mock(ExtendedSpringLiquibase.class); + ExtendedSpringLiquibase tenantLiquibase = mock(ExtendedSpringLiquibase.class); + FineractPlatformTenant defaultTenant = mock(FineractPlatformTenant.class); + List allTenants = List.of(defaultTenant); + DataSource defaultTenantDataSource = mock(DataSource.class); + given(databaseStateVerifier.isFirstLiquibaseMigration(defaultTenantDataSource)).willReturn(true); + given(databaseStateVerifier.isFlywayPresent(defaultTenantDataSource)).willReturn(true); + given(databaseStateVerifier.isTenantOnLatestUpgradableVersion(defaultTenantDataSource)).willReturn(false); + given(tenantDetailsService.findAllTenants()).willReturn(allTenants); + given(tenantDataSourceFactory.create(defaultTenant)).willReturn(defaultTenantDataSource); + given(liquibaseFactory.create(defaultTenantDataSource, "tenant_db", "initial_switch")).willReturn(initialTenantLiquibase); + given(liquibaseFactory.create(defaultTenantDataSource, "tenant_db")).willReturn(tenantLiquibase); + // when + SchemaUpgradeNeededException result = assertThrows(SchemaUpgradeNeededException.class, () -> underTest.afterPropertiesSet()); + // then + assertThat(result).isNotNull(); + verify(liquibaseFactory, times(2)).create(eq(tenantDataSource), any()); + verify(liquibaseFactory).create(eq(defaultTenantDataSource), any()); + verifyNoMoreInteractions(liquibaseFactory); + verify(initialTenantStoreLiquibase).changeLogSync(); + verify(tenantStoreLiquibase).afterPropertiesSet(); + verifyNoInteractions(initialTenantLiquibase, tenantLiquibase); + } +}