diff --git a/src/main/java/org/folio/consortia/service/FolioTenantService.java b/src/main/java/org/folio/consortia/service/FolioTenantService.java
index 0e873e04..4b84c1fc 100644
--- a/src/main/java/org/folio/consortia/service/FolioTenantService.java
+++ b/src/main/java/org/folio/consortia/service/FolioTenantService.java
@@ -1,6 +1,9 @@
package org.folio.consortia.service;
import java.sql.ResultSet;
+import java.util.Map;
+import liquibase.exception.LiquibaseException;
+import liquibase.exception.UnexpectedLiquibaseException;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
@@ -9,6 +12,7 @@
import org.folio.consortia.domain.dto.CustomField;
import org.folio.consortia.domain.dto.CustomFieldType;
import org.folio.spring.FolioExecutionContext;
+import org.folio.spring.exception.TenantUpgradeException;
import org.folio.spring.liquibase.FolioSpringLiquibase;
import org.folio.spring.service.PrepareSystemUserService;
import org.folio.spring.service.SystemUserScopedExecutionService;
@@ -26,7 +30,8 @@
@Primary
public class FolioTenantService extends TenantService {
- private static final String EXIST_SQL = "SELECT EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?)";
+ private static final String EXIST_SQL =
+ "SELECT EXISTS(SELECT 1 FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?)";
private final KafkaService kafkaService;
private final CustomFieldService customFieldService;
@@ -36,6 +41,7 @@ public class FolioTenantService extends TenantService {
private final CustomFieldsRetryProperties customFieldsRetryProperties;
private static final String ORIGINAL_TENANT_ID_NAME = "originalTenantId";
+ private static final String TENANT_NAME_PARAMETER = "tenantname";
private static final CustomField ORIGINAL_TENANT_ID_CUSTOM_FIELD = CustomField.builder()
.name(ORIGINAL_TENANT_ID_NAME)
.entityType("user")
@@ -62,6 +68,31 @@ public FolioTenantService(JdbcTemplate jdbcTemplate,
this.customFieldsRetryProperties = customFieldsRetryProperties;
}
+ /*
+ * Because of the liquibase.Scope implementation for the SpringLiquibase it is not possible to run several
+ * SpringLiquibase executions simultaneously. That is why this method must be synchronized.
+ */
+ @Override
+ public synchronized void createOrUpdateTenant(TenantAttributes tenantAttributes) {
+ if (folioSpringLiquibase != null) {
+ var params = Map.of(TENANT_NAME_PARAMETER, folioExecutionContext.getTenantId());
+ folioSpringLiquibase.setChangeLogParameters(params);
+ log.info("Set ChangeLog parameters: {}", params);
+
+ folioSpringLiquibase.setDefaultSchema(getSchemaName());
+ log.info("About to start liquibase update for tenant [{}]", context.getTenantId());
+
+ try {
+ folioSpringLiquibase.performLiquibaseUpdate();
+ } catch (LiquibaseException | UnexpectedLiquibaseException e) {
+ throw new TenantUpgradeException(e);
+ }
+ log.info("Liquibase update for tenant [{}] executed successfully", context.getTenantId());
+ }
+
+ afterTenantUpdate(tenantAttributes);
+ }
+
@Override
protected void afterTenantUpdate(TenantAttributes tenantAttributes) {
try {
@@ -75,8 +106,8 @@ protected void afterTenantUpdate(TenantAttributes tenantAttributes) {
}
/**
- * Implemented by HSQLDB way
- * Check if the tenant exists (by way of its database schema)
+ * Implemented by HSQLDB way. Check if the tenant exists (by way of its database schema)
+ *
* @return if the tenant's database schema exists
*/
@Override
@@ -92,7 +123,8 @@ protected boolean tenantExists() {
private void createOriginalTenantIdCustomField() {
systemUserScopedExecutionService.executeAsyncSystemUserScoped(folioExecutionContext.getTenantId(), () -> {
if (ObjectUtils.isNotEmpty(customFieldService.getCustomFieldByName(ORIGINAL_TENANT_ID_NAME))) {
- log.info("createOriginalTenantIdCustomField:: custom-field already available in tenant {} with name {}", folioExecutionContext.getTenantId(), ORIGINAL_TENANT_ID_NAME);
+ log.info("createOriginalTenantIdCustomField:: custom-field already available in tenant {} with name {}",
+ folioExecutionContext.getTenantId(), ORIGINAL_TENANT_ID_NAME);
} else {
createCustomFieldsWithRetry();
}
diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml
index 7d98f45f..75c03d2b 100644
--- a/src/main/resources/db/changelog/changelog-master.xml
+++ b/src/main/resources/db/changelog/changelog-master.xml
@@ -12,4 +12,5 @@
+
diff --git a/src/main/resources/db/changelog/changes/migrate-data-from-mod-consortia.xml b/src/main/resources/db/changelog/changes/migrate-data-from-mod-consortia.xml
new file mode 100644
index 00000000..62a9c489
--- /dev/null
+++ b/src/main/resources/db/changelog/changes/migrate-data-from-mod-consortia.xml
@@ -0,0 +1,177 @@
+
+
+
+
+
+ DO
+ '
+ DECLARE
+ BEGIN
+ IF (EXISTS (SELECT 1 FROM information_schema.tables
+ WHERE table_schema = ''${tenantname}_mod_consortia'' AND table_name = ''consortia_configuration''))
+ THEN
+ INSERT INTO ${tenantname}_mod_consortia_keycloak.consortia_configuration (
+ id, central_tenant_id, created_by, created_date, updated_by, updated_date
+ ) SELECT
+ id, central_tenant_id, created_by, created_date, updated_by,
+ (CASE WHEN updated_date IS NULL THEN created_date ELSE updated_date END) as updated_date
+ FROM ${tenantname}_mod_consortia.consortia_configuration;
+ END IF;
+ END;
+ ' LANGUAGE plpgsql;
+
+
+
+
+
+ DO
+ '
+ DECLARE
+ BEGIN
+ IF (EXISTS (SELECT 1 FROM information_schema.tables
+ WHERE table_schema = ''${tenantname}_mod_consortia'' AND table_name = ''consortium''))
+ THEN
+ INSERT INTO ${tenantname}_mod_consortia_keycloak.consortium (
+ id, name, created_by, created_date, updated_by, updated_date
+ ) SELECT
+ id, name, created_by, created_date, updated_by,
+ (CASE WHEN updated_date IS NULL THEN created_date ELSE updated_date END) as updated_date
+ FROM ${tenantname}_mod_consortia.consortium;
+ END IF;
+ END;
+ ' LANGUAGE plpgsql;
+
+
+
+
+
+ DO
+ '
+ DECLARE
+ BEGIN
+ IF (EXISTS (SELECT 1 FROM information_schema.tables
+ WHERE table_schema = ''${tenantname}_mod_consortia'' AND table_name = ''pc_state''))
+ THEN
+ INSERT INTO ${tenantname}_mod_consortia_keycloak.pc_state (
+ id, status, total_records, created_by, created_date, updated_by, updated_date
+ ) SELECT
+ id, status, total_records, created_by, created_date, updated_by,
+ (CASE WHEN updated_date IS NULL THEN created_date ELSE updated_date END) as updated_date
+ FROM ${tenantname}_mod_consortia.pc_state;
+ END IF;
+ END;
+ ' LANGUAGE plpgsql;
+
+
+
+
+
+ DO
+ '
+ DECLARE
+ BEGIN
+ IF (EXISTS (SELECT 1 FROM information_schema.tables
+ WHERE table_schema = ''${tenantname}_mod_consortia'' AND table_name = ''pc_tenant_request''))
+ THEN
+ INSERT INTO ${tenantname}_mod_consortia_keycloak.pc_tenant_request (
+ id, pc_id, tenant_id, status, request_url, request_payload, response, response_status_code,
+ completed_date, created_by, created_date, updated_by, updated_date
+ ) SELECT
+ id, pc_id, tenant_id, status, request_url, request_payload, response, response_status_code,
+ completed_date, created_by, created_date, updated_by,
+ (CASE WHEN updated_date IS NULL THEN created_date ELSE updated_date END) as updated_date
+ FROM ${tenantname}_mod_consortia.pc_tenant_request;
+ END IF;
+ END;
+ ' LANGUAGE plpgsql;
+
+
+
+
+
+ DO
+ '
+ DECLARE
+ BEGIN
+ IF (EXISTS (SELECT 1 FROM information_schema.tables
+ WHERE table_schema = ''${tenantname}_mod_consortia'' AND table_name = ''sharing_instance''))
+ THEN
+ INSERT INTO ${tenantname}_mod_consortia_keycloak.sharing_instance (
+ id, instance_id, source_tenant_id, target_tenant_id, status, error, created_by, created_date, updated_by, updated_date
+ ) SELECT
+ id, instance_id, source_tenant_id, target_tenant_id, status, error, created_by, created_date, updated_by,
+ (CASE WHEN updated_date IS NULL THEN created_date ELSE updated_date END) as updated_date
+ FROM ${tenantname}_mod_consortia.sharing_instance;
+ END IF;
+ END;
+ ' LANGUAGE plpgsql;
+
+
+
+
+
+ DO
+ '
+ DECLARE
+ BEGIN
+ IF (EXISTS (SELECT 1 FROM information_schema.tables
+ WHERE table_schema = ''${tenantname}_mod_consortia'' AND table_name = ''sharing_setting''))
+ THEN
+ INSERT INTO ${tenantname}_mod_consortia_keycloak.sharing_setting (
+ id, setting_id, tenant_id, created_by, created_date, updated_by, updated_date
+ ) SELECT
+ id, setting_id, tenant_id, created_by, created_date, updated_by,
+ (CASE WHEN updated_date IS NULL THEN created_date ELSE updated_date END) as updated_date
+ FROM ${tenantname}_mod_consortia.sharing_setting;
+ END IF;
+ END;
+ ' LANGUAGE plpgsql;
+
+
+
+
+
+ DO
+ '
+ DECLARE
+ BEGIN
+ IF (EXISTS (SELECT 1 FROM information_schema.tables
+ WHERE table_schema = ''${tenantname}_mod_consortia'' AND table_name = ''tenant''))
+ THEN
+ INSERT INTO ${tenantname}_mod_consortia_keycloak.tenant (
+ id, name, consortium_id, code, is_central, created_by, created_date, updated_by, updated_date, setup_status, is_deleted
+ ) SELECT
+ id, name, consortium_id, code, is_central, created_by, created_date, updated_by,
+ (CASE WHEN updated_date IS NULL THEN created_date ELSE updated_date END) as updated_date, setup_status, is_deleted
+ FROM ${tenantname}_mod_consortia.tenant;
+ END IF;
+ END;
+ ' LANGUAGE plpgsql;
+
+
+
+
+
+ DO
+ '
+ DECLARE
+ BEGIN
+ IF (EXISTS (SELECT 1 FROM information_schema.tables
+ WHERE table_schema = ''${tenantname}_mod_consortia'' AND table_name = ''user_tenant''))
+ THEN
+ INSERT INTO ${tenantname}_mod_consortia_keycloak.user_tenant (
+ id, user_id, username, tenant_id, is_primary, created_by, created_date, updated_by, updated_date
+ ) SELECT
+ id, user_id, username, tenant_id, is_primary, created_by, created_date, updated_by,
+ (CASE WHEN updated_date IS NULL THEN created_date ELSE updated_date END) as updated_date
+ FROM ${tenantname}_mod_consortia.user_tenant;
+ END IF;
+ END;
+ ' LANGUAGE plpgsql;
+
+
+
+
diff --git a/src/test/java/org/folio/consortia/base/BaseIT.java b/src/test/java/org/folio/consortia/base/BaseIT.java
index 4de321b3..ce078aa7 100644
--- a/src/test/java/org/folio/consortia/base/BaseIT.java
+++ b/src/test/java/org/folio/consortia/base/BaseIT.java
@@ -53,7 +53,7 @@ public abstract class BaseIT {
protected MockMvc mockMvc;
protected static final int WIRE_MOCK_PORT = TestSocketUtils.findAvailableTcpPort();
- protected static final String TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkaWt1X2FkbWluIiwidXNlcl9pZCI6IjFkM2I1OGNiLTA3YjUtNWZjZC04YTJhLTNjZTA2YTBlYjkwZiIsImlhdCI6MTYxNjQyMDM5MywidGVuYW50IjoiZGlrdSJ9.2nvEYQBbJP1PewEgxixBWLHSX_eELiBEBpjufWiJZRs";
+ public static final String TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkaWt1X2FkbWluIiwidXNlcl9pZCI6IjFkM2I1OGNiLTA3YjUtNWZjZC04YTJhLTNjZTA2YTBlYjkwZiIsImlhdCI6MTYxNjQyMDM5MywidGVuYW50IjoiZGlrdSJ9.2nvEYQBbJP1PewEgxixBWLHSX_eELiBEBpjufWiJZRs";
public static final String TENANT = "consortium";
protected static WireMockServer wireMockServer;
protected static PostgreSQLContainer> postgreDBContainer = new PostgreSQLContainer<>("postgres:12-alpine");
diff --git a/src/test/java/org/folio/consortia/base/BaseRepositoryTest.java b/src/test/java/org/folio/consortia/base/BaseRepositoryTest.java
index 5d4eb9a3..215d1af7 100644
--- a/src/test/java/org/folio/consortia/base/BaseRepositoryTest.java
+++ b/src/test/java/org/folio/consortia/base/BaseRepositoryTest.java
@@ -15,7 +15,7 @@
import org.springframework.test.annotation.DirtiesContext;
@Log4j2
-@DataJpaTest
+@DataJpaTest(properties = "spring.liquibase.parameters.tenantname=consortium")
@EnablePostgresExtension
@DirtiesContext(classMode = AFTER_CLASS)
@Import({FolioAuditorAware.class})
diff --git a/src/test/java/org/folio/consortia/repository/ConsortiaMigrationTest.java b/src/test/java/org/folio/consortia/repository/ConsortiaMigrationTest.java
new file mode 100644
index 00000000..d41504e2
--- /dev/null
+++ b/src/test/java/org/folio/consortia/repository/ConsortiaMigrationTest.java
@@ -0,0 +1,128 @@
+package org.folio.consortia.repository;
+
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.folio.consortia.base.BaseIT.TENANT;
+import static org.folio.consortia.base.BaseIT.TOKEN;
+import static org.folio.consortia.base.BaseIT.asJsonString;
+import static org.springframework.http.MediaType.APPLICATION_JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import com.github.tomakehurst.wiremock.common.ClasspathFileSource;
+import com.github.tomakehurst.wiremock.extension.responsetemplating.ResponseTemplateTransformer;
+import com.github.tomakehurst.wiremock.extension.responsetemplating.TemplateEngine;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.SneakyThrows;
+import org.folio.consortia.repository.ConsortiaMigrationTest.DockerPostgresDataSourceInitializer;
+import org.folio.consortia.support.extension.EnableKafkaExtension;
+import org.folio.spring.integration.XOkapiHeaders;
+import org.folio.tenant.domain.dto.TenantAttributes;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.http.HttpHeaders;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.kafka.test.context.EmbeddedKafka;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.annotation.DirtiesContext.ClassMode;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.springframework.test.context.DynamicPropertySource;
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.test.context.support.TestPropertySourceUtils;
+import org.springframework.test.util.TestSocketUtils;
+import org.springframework.test.web.servlet.MockMvc;
+import org.testcontainers.containers.PostgreSQLContainer;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
+@ContextConfiguration(initializers = DockerPostgresDataSourceInitializer.class)
+@AutoConfigureMockMvc
+@Testcontainers
+@EmbeddedKafka
+@EnableKafkaExtension
+@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
+class ConsortiaMigrationTest {
+
+ @Autowired
+ protected MockMvc mockMvc;
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+
+ protected static final int WIRE_MOCK_PORT = TestSocketUtils.findAvailableTcpPort();
+ protected static WireMockServer wireMockServer;
+ protected static PostgreSQLContainer> postgreDBContainer = new PostgreSQLContainer<>("postgres:12-alpine");
+
+ static {
+ postgreDBContainer.start();
+ }
+
+ @BeforeAll
+ static void beforeAll() {
+ wireMockServer = new WireMockServer(wireMockConfig().port(WIRE_MOCK_PORT)
+ .extensions(new ResponseTemplateTransformer(
+ TemplateEngine.defaultTemplateEngine(), true, new ClasspathFileSource("/"), new ArrayList<>())));
+ wireMockServer.start();
+ }
+
+ @Test
+ @Sql("classpath:/sql/consortia_data.sql")
+ void create_positive_migrateConsortiaData() {
+ setUpTenant(mockMvc);
+
+ var ids = jdbcTemplate.queryForList(
+ format("SELECT id FROM %s.%s", TENANT + "_mod_consortia_keycloak", "consortia_configuration"), String.class);
+ assertThat(ids.size()).isEqualTo(1);
+ assertThat(ids.iterator().next()).isEqualTo("e2628d7d-059a-46a1-a5ea-10a5a37b1af2");
+ }
+
+ @AfterAll
+ static void tearDown() {
+ wireMockServer.stop();
+ }
+
+ @SneakyThrows
+ static void setUpTenant(MockMvc mockMvc) {
+ mockMvc.perform(post("/_/tenant").content(asJsonString(new TenantAttributes().moduleTo("mod-consortia-keycloak")))
+ .headers(defaultHeaders())
+ .contentType(APPLICATION_JSON)).andExpect(status().isNoContent());
+ }
+
+ static HttpHeaders defaultHeaders() {
+ var httpHeaders = new HttpHeaders();
+ httpHeaders.setContentType(APPLICATION_JSON);
+ httpHeaders.put(XOkapiHeaders.TENANT, List.of(TENANT));
+ httpHeaders.add(XOkapiHeaders.URL, wireMockServer.baseUrl());
+ httpHeaders.add(XOkapiHeaders.TOKEN, TOKEN);
+ httpHeaders.add(XOkapiHeaders.USER_ID, "7698e46-c3e3-11ed-afa1-0242ac120002");
+ return httpHeaders;
+ }
+
+ static class DockerPostgresDataSourceInitializer implements
+ ApplicationContextInitializer {
+
+ @Override
+ public void initialize(ConfigurableApplicationContext context) {
+ TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context,
+ "spring.datasource.url=" + postgreDBContainer.getJdbcUrl(),
+ "spring.datasource.username=" + postgreDBContainer.getUsername(),
+ "spring.datasource.password=" + postgreDBContainer.getPassword());
+ }
+ }
+
+ @DynamicPropertySource
+ static void setFolioOkapiUrl(DynamicPropertyRegistry registry) {
+ registry.add("folio.okapi-url", () -> "http://localhost:" + WIRE_MOCK_PORT);
+ }
+}
diff --git a/src/test/resources/sql/consortia_data.sql b/src/test/resources/sql/consortia_data.sql
new file mode 100644
index 00000000..15c88d72
--- /dev/null
+++ b/src/test/resources/sql/consortia_data.sql
@@ -0,0 +1,13 @@
+CREATE SCHEMA if NOT EXISTS consortium_mod_consortia;
+
+CREATE TABLE consortium_mod_consortia.consortia_configuration (
+ id UUID PRIMARY KEY,
+ central_tenant_id TEXT NOT NULL UNIQUE,
+ created_by UUID,
+ created_date TIMESTAMP WITHOUT TIME ZONE DEFAULT now() NOT NULL,
+ updated_by UUID,
+ updated_date TIMESTAMP WITHOUT TIME ZONE
+);
+
+INSERT INTO consortium_mod_consortia.consortia_configuration (id, central_tenant_id)
+VALUES ('e2628d7d-059a-46a1-a5ea-10a5a37b1af2', 'text');