Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
"id": "authtoken",
"version": "2.0"
},
{
"id": "configuration",
"version": "2.0"
},
{
"id": "permissions",
"version": "5.3"
Expand Down Expand Up @@ -274,7 +278,8 @@
"perms.users.get",
"perms.users.assign.immutable",
"data-export.job.collection.get",
"data-export.config.collection.get"
"data-export.config.collection.get",
"configuration.entries.collection.get"
]
},
{
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/folio/client/ConfigurationClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.folio.client;

import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;

import tools.jackson.databind.JsonNode;

@HttpExchange("configurations")
public interface ConfigurationClient {

@GetExchange("/entries")
JsonNode getConfigurations(@RequestParam("query") String query, @RequestParam("limit") int limit);

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.folio.client.ConfigurationClient;
import org.folio.des.client.DataExportSpringClient;
import org.folio.des.client.ExportWorkerClient;
import org.folio.des.exceptions.RestClientErrorHandler;
Expand All @@ -20,6 +21,11 @@ public class HttpClientConfiguration {

private final RestClientErrorHandler errorHandler;

@Bean
public ConfigurationClient configurationClient(HttpServiceProxyFactory factory) {
return factory.createClient(ConfigurationClient.class);
}

@Bean
public DataExportSpringClient dataExportSpringClient(HttpServiceProxyFactory factory) {
return factory.createClient(DataExportSpringClient.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private static boolean isMigrationNeededForVersions(String moduleFrom, String mo
(moduleFromSemVer == null || moduleFromSemVer.compareTo(minQuartzSupportVersion) < 0);
}

private static SemVer moduleVersionToSemVer(String version) {
public static SemVer moduleVersionToSemVer(String version) {
try {
return new SemVer(version);
} catch (IllegalArgumentException ex) {
Expand Down
9 changes: 5 additions & 4 deletions src/main/java/org/folio/des/service/FolioTenantService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
import org.folio.des.scheduling.quartz.ScheduledJobsRemover;
import org.folio.des.service.bursarlegacy.BursarExportLegacyJobService;
import org.folio.des.service.bursarlegacy.BursarMigrationService;
import org.folio.des.service.config.ConfigurationMigrationService;
import org.folio.des.service.config.impl.BursarFeesFinesExportConfigService;
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.TenantService;
Expand All @@ -20,8 +20,6 @@
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import liquibase.exception.LiquibaseException;
import liquibase.exception.UnexpectedLiquibaseException;
import lombok.extern.log4j.Log4j2;

@Log4j2
Expand All @@ -42,14 +40,15 @@ public class FolioTenantService extends TenantService {
private final BursarMigrationService bursarMigrationService;
private final BursarFeesFinesExportConfigService bursarFeesFinesExportConfigService;
private final FolioExecutionContext folioExecutionContext;
private final ConfigurationMigrationService configurationMigrationService;

public FolioTenantService(JdbcTemplate jdbcTemplate, FolioExecutionContext context, FolioSpringLiquibase folioSpringLiquibase,
PrepareSystemUserService prepareSystemUserService, KafkaService kafka,
EdifactScheduledJobInitializer edifactScheduledJobInitializer, ScheduledJobsRemover scheduledJobsRemover,
BursarScheduledJobInitializer bursarScheduledJobInitializer, OldJobDeleteScheduler oldJobDeleteScheduler,
BursarExportLegacyJobService bursarExportLegacyJobService, JobService jobService,
BursarMigrationService bursarMigrationService, BursarFeesFinesExportConfigService bursarFeesFinesExportConfigService,
FolioExecutionContext folioExecutionContext) {
FolioExecutionContext folioExecutionContext, ConfigurationMigrationService configurationMigrationService) {
super(jdbcTemplate, context, folioSpringLiquibase);
this.prepareSystemUserService = prepareSystemUserService;
this.kafka = kafka;
Expand All @@ -62,6 +61,7 @@ public FolioTenantService(JdbcTemplate jdbcTemplate, FolioExecutionContext conte
this.bursarMigrationService = bursarMigrationService;
this.bursarFeesFinesExportConfigService = bursarFeesFinesExportConfigService;
this.folioExecutionContext = folioExecutionContext;
this.configurationMigrationService = configurationMigrationService;
}

@Override
Expand All @@ -75,6 +75,7 @@ protected void beforeLiquibaseUpdate(TenantAttributes tenantAttributes) {
protected void afterTenantUpdate(TenantAttributes tenantAttributes) {
try {
prepareSystemUserService.setupSystemUser();
configurationMigrationService.migrateConfigurationData(tenantAttributes, context.getTenantId());
bursarMigrationService.updateLegacyBursarIfNeeded(tenantAttributes, bursarFeesFinesExportConfigService,
bursarExportLegacyJobService, jobService);
bursarScheduledJobInitializer.initAllScheduledJob(tenantAttributes);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package org.folio.des.service.config;

import static org.folio.des.scheduling.util.ScheduleUtil.moduleVersionToSemVer;

import org.apache.commons.lang3.StringUtils;
import org.folio.client.ConfigurationClient;
import org.folio.des.domain.dto.ExportTypeSpecificParameters;
import org.folio.spring.FolioModuleMetadata;
import org.folio.tenant.domain.dto.TenantAttributes;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;

@Service
@RequiredArgsConstructor
@Log4j2
public class ConfigurationMigrationService {

private static final String MIGRATION_TARGET_VERSION = "3.6.0";
private static final String CONFIGURATION_QUERY = "module==mod-data-export-spring";
private static final String EXPORT_CONFIG_TABLE = "export_config";
private static final String EXPORT_CONFIG_SQL = """
INSERT INTO %s.%s (
id, config_name, type, tenant, export_type_specific_parameters,
schedule_frequency, schedule_period, schedule_time, week_days,
created_date, created_by, updated_date, updated_by
) VALUES (
?::uuid, ?, ?, ?, ?::jsonb,
?, ?, ?, ?::jsonb,
?::timestamp, ?::uuid, ?::timestamp, ?::uuid
) ON CONFLICT (id) DO NOTHING
""";

private final JdbcTemplate jdbcTemplate;
private final ObjectMapper objectMapper;
private final ConfigurationClient configurationClient;
private final FolioModuleMetadata folioModuleMetadata;

public void migrateConfigurationData(TenantAttributes attributes, String tenantId) {
log.info("migrateConfigurationData:: Attempting to migrate configuration data from mod-configuration for tenant: {}", tenantId);
if (!isMigrationNeeded(attributes)) {
log.info("migrateConfigurationData:: Migration not needed for tenant: {}. Skipping configuration data migration.", tenantId);
return;
}

try {
var configsResponse = fetchConfigurationEntries();
if (configsResponse == null || !configsResponse.has("configs") || configsResponse.get("configs").isEmpty()) {
log.info("migrateConfigurationData:: No configuration entries found to migrate");
return;
}
configsResponse.get("configs").forEach(config -> migrateConfigEntry(tenantId, config));
} catch (Exception e) {
log.warn("migrateConfigurationData:: Failed to migrate configuration data from mod-configuration. ", e);
}
}

private static boolean isMigrationNeeded(TenantAttributes tenantAttributes) {
var moduleFrom = tenantAttributes.getModuleFrom();
if (StringUtils.isBlank(moduleFrom)) {
return true;
}
return moduleVersionToSemVer(MIGRATION_TARGET_VERSION).compareTo(moduleVersionToSemVer(moduleFrom)) > 0;
}

private JsonNode fetchConfigurationEntries() {
return configurationClient.getConfigurations(CONFIGURATION_QUERY, Integer.MAX_VALUE);
}

private void migrateConfigEntry(String tenantId, JsonNode config) {
var id = config.path("id").asString();

try {
JsonNode valueObj = objectMapper.readTree(config.path("value").asString("{}"));

var type = valueObj.path("type").asString(null);
var exportTypeSpecificParameters = valueObj.path("exportTypeSpecificParameters");

if (StringUtils.isBlank(type) || type.equals("null") || !validateExportTypeSpecificParams(exportTypeSpecificParameters)) {
log.warn("migrateConfigEntry:: Skipping config {} due to missing type or exportTypeSpecificParameters", id);
return;
}

var metadata = config.path("metadata");
var weekDays = valueObj.path("weekDays");

jdbcTemplate.update(EXPORT_CONFIG_SQL.formatted(folioModuleMetadata.getDBSchemaName(tenantId), EXPORT_CONFIG_TABLE),
id,
config.path("configName").asString(),
type,
valueObj.path("tenant").asString(null),
exportTypeSpecificParameters.toString(),
valueObj.path("scheduleFrequency").asIntOpt().stream().boxed().findFirst().orElse(null),
valueObj.path("schedulePeriod").asString(null),
valueObj.path("scheduleTime").asString(null),
weekDays.isMissingNode() ? null : weekDays.toString(),
metadata.path("createdDate").asString(null),
metadata.path("createdByUserId").asString(null),
metadata.path("updatedDate").asString(null),
metadata.path("updatedByUserId").asString(null)
);
log.info("migrateConfigEntry:: Successfully migrated export config with id: {}", id);
} catch (Exception e) {
log.error("migrateConfigEntry:: Failed to insert export config with id: {}", id, e);
}
}


/**
* Validates the exportTypeSpecificParameters by attempting to convert it to the ExportTypeSpecificParameters class.

* @param exportTypeSpecificParameters the JsonNode containing the export type specific parameters to validate
*/
private boolean validateExportTypeSpecificParams(JsonNode exportTypeSpecificParameters) {
if (exportTypeSpecificParameters.isMissingNode()) {
return false;
}
try {
objectMapper.treeToValue(exportTypeSpecificParameters, ExportTypeSpecificParameters.class);
return true;
} catch (Exception e) {
return false;
}
}

}
1 change: 0 additions & 1 deletion src/main/resources/db/changelog/changelog-master.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,5 @@
<include file="changes/05_16_2025_alter_job_output_format.xml" relativeToChangelogFile="true"/>
<include file="changes/17_07_2025_create_job_deletion_interval.xml" relativeToChangelogFile="true"/>
<include file="changes/23_10_2025_create_export_config_table.xml" relativeToChangelogFile="true"/>
<include file="changes/07_11_2025_migrate_configuration_data.xml" relativeToChangelogFile="true"/>

</databaseChangeLog>

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
import java.util.Optional;
import java.util.UUID;

import org.folio.client.ConfigurationClient;
import org.folio.de.entity.Job;
import org.folio.des.client.DataExportSpringClient;
import org.folio.des.client.ExportWorkerClient;
import org.folio.des.config.HttpClientConfiguration;
import org.folio.des.config.JacksonConfiguration;
import org.folio.des.config.ServiceConfiguration;
import org.folio.des.config.scheduling.QuartzSchemaInitializer;
Expand All @@ -29,6 +29,7 @@
import org.folio.des.domain.dto.ExportType;
import org.folio.des.domain.dto.ExportTypeSpecificParameters;
import org.folio.des.domain.dto.VendorEdiOrdersExportConfig;
import org.folio.des.service.config.ConfigurationMigrationService;
import org.folio.spring.client.AuthnClient;
import org.folio.spring.client.PermissionsClient;
import org.folio.spring.client.UsersClient;
Expand Down Expand Up @@ -66,6 +67,10 @@ class JobCommandBuilderResolverTest {
private UsersClient usersClient;
@MockitoBean
private PermissionsClient permissionsClient;
@MockitoBean
private ConfigurationClient configurationClient;
@MockitoBean
private ConfigurationMigrationService configurationMigrationService;

@ParameterizedTest
@DisplayName("Should retrieve builder for specific export type if builder is registered in the resolver")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.folio.des.scheduling.quartz.OldJobDeleteScheduler;
import org.folio.des.scheduling.quartz.ScheduledJobsRemover;
import org.folio.des.service.bursarlegacy.BursarMigrationService;
import org.folio.des.service.config.ConfigurationMigrationService;
import org.folio.spring.FolioExecutionContext;
import org.folio.spring.service.PrepareSystemUserService;
import org.folio.tenant.domain.dto.TenantAttributes;
Expand Down Expand Up @@ -52,6 +53,9 @@ class FolioTenantServiceTest {
@Mock
BursarMigrationService bursarMigrationService;

@Mock
ConfigurationMigrationService configurationMigrationService;

@Test
void shouldDoProcessAfterTenantUpdating() {
TenantAttributes tenantAttributes = createTenantAttributes();
Expand All @@ -70,6 +74,8 @@ void shouldDoProcessAfterTenantUpdating() {
.scheduleOldJobDeletion(any());
doNothing().when(bursarMigrationService)
.updateLegacyBursarIfNeeded(eq(tenantAttributes), any(), any(), any());
doNothing().when(configurationMigrationService)
.migrateConfigurationData(any(), any());

folioTenantService.afterTenantUpdate(tenantAttributes);

Expand All @@ -78,6 +84,7 @@ void shouldDoProcessAfterTenantUpdating() {
verify(bursarScheduledJobInitializer, times(1)).initAllScheduledJob(tenantAttributes);
verify(oldJobDeleteScheduler, times(1)).scheduleOldJobDeletion(any());
verify(bursarMigrationService, times(1)).updateLegacyBursarIfNeeded(eq(tenantAttributes), any(), any(), any());
verify(configurationMigrationService, times(1)).migrateConfigurationData(any(), any());
verify(kafka, times(1)).createKafkaTopics();
verify(kafka, times(1)).restartEventListeners();
}
Expand Down
Loading
Loading