diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json index 64963f62..98767eff 100644 --- a/descriptors/ModuleDescriptor-template.json +++ b/descriptors/ModuleDescriptor-template.json @@ -58,7 +58,7 @@ "optional": [ { "id": "custom-fields", - "version": "2.1" + "version": "2.1 3.0" }, { "id": "pieces", diff --git a/src/main/java/org/folio/consortia/client/EurekaProxyTenantsClient.java b/src/main/java/org/folio/consortia/client/EurekaProxyTenantsClient.java new file mode 100644 index 00000000..732a6ab0 --- /dev/null +++ b/src/main/java/org/folio/consortia/client/EurekaProxyTenantsClient.java @@ -0,0 +1,18 @@ +package org.folio.consortia.client; + +import java.net.URI; +import java.util.List; + +import org.folio.consortia.domain.dto.ModuleForTenant; +import org.folio.spring.config.FeignClientConfiguration; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "eureka", configuration = FeignClientConfiguration.class) +public interface EurekaProxyTenantsClient { + + @GetMapping(value = "/proxy/tenants/{tenantId}/modules", produces = MediaType.APPLICATION_JSON_VALUE) + List getModules(URI uri, @RequestParam String tenantId); +} diff --git a/src/main/java/org/folio/consortia/client/OkapiClient.java b/src/main/java/org/folio/consortia/client/OkapiClient.java new file mode 100644 index 00000000..04a5d7fa --- /dev/null +++ b/src/main/java/org/folio/consortia/client/OkapiClient.java @@ -0,0 +1,19 @@ +package org.folio.consortia.client; + +import java.net.URI; +import java.util.List; + +import org.folio.consortia.domain.dto.ModuleForTenant; +import org.folio.spring.config.FeignClientConfiguration; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; + +@FeignClient(name = "okapi", configuration = FeignClientConfiguration.class) +public interface OkapiClient { + + @GetMapping(value = "/proxy/tenants/{tenantId}/modules", produces = MediaType.APPLICATION_JSON_VALUE) + List getModuleIds(URI uri, @PathVariable("tenantId") String tenantId, @RequestParam("filter") String moduleName); +} diff --git a/src/main/java/org/folio/consortia/config/property/RelatedModulesProperties.java b/src/main/java/org/folio/consortia/config/property/RelatedModulesProperties.java deleted file mode 100644 index 260ca6f5..00000000 --- a/src/main/java/org/folio/consortia/config/property/RelatedModulesProperties.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.folio.consortia.config.property; - -import jakarta.validation.constraints.NotNull; -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; -import org.springframework.validation.annotation.Validated; - -@Data -@Validated -@Component -@ConfigurationProperties("related-modules") -public class RelatedModulesProperties { - - @NotNull(message = "mod-users ID is required") - private String modUsersId; -} diff --git a/src/main/java/org/folio/consortia/domain/dto/ModuleForTenant.java b/src/main/java/org/folio/consortia/domain/dto/ModuleForTenant.java new file mode 100644 index 00000000..598c6ba0 --- /dev/null +++ b/src/main/java/org/folio/consortia/domain/dto/ModuleForTenant.java @@ -0,0 +1,14 @@ +package org.folio.consortia.domain.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class ModuleForTenant { + + @JsonProperty("id") + private String id; +} diff --git a/src/main/java/org/folio/consortia/service/ModuleTenantService.java b/src/main/java/org/folio/consortia/service/ModuleTenantService.java new file mode 100644 index 00000000..5689f0b3 --- /dev/null +++ b/src/main/java/org/folio/consortia/service/ModuleTenantService.java @@ -0,0 +1,13 @@ +package org.folio.consortia.service; + +public interface ModuleTenantService { + /** + * Retrieves the module ID for the "mod-users" module. + * This method determines the appropriate module ID based on the platform configuration + * and the tenant context. + * + * @return the module ID for "mod-users" + * @throws org.folio.spring.exception.NotFoundException if the module ID for "mod-users" is not found + */ + String getModUsersModuleId(); +} diff --git a/src/main/java/org/folio/consortia/service/impl/CustomFieldServiceImpl.java b/src/main/java/org/folio/consortia/service/impl/CustomFieldServiceImpl.java index 47024eb0..b2f66e2e 100644 --- a/src/main/java/org/folio/consortia/service/impl/CustomFieldServiceImpl.java +++ b/src/main/java/org/folio/consortia/service/impl/CustomFieldServiceImpl.java @@ -5,10 +5,10 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.folio.consortia.client.CustomFieldsClient; -import org.folio.consortia.config.property.RelatedModulesProperties; import org.folio.consortia.domain.dto.CustomField; import org.folio.consortia.domain.dto.CustomFieldType; import org.folio.consortia.service.CustomFieldService; +import org.folio.consortia.service.ModuleTenantService; import org.springframework.stereotype.Service; @Service @@ -16,6 +16,7 @@ @RequiredArgsConstructor public class CustomFieldServiceImpl implements CustomFieldService { + private static final String QUERY_PATTERN_NAME = "name==%s"; public static final String ORIGINAL_TENANT_ID_NAME = "originalTenantId"; public static final CustomField ORIGINAL_TENANT_ID_CUSTOM_FIELD = CustomField.builder() .name(ORIGINAL_TENANT_ID_NAME) @@ -25,22 +26,25 @@ public class CustomFieldServiceImpl implements CustomFieldService { .visible(false) .build(); - private static final String QUERY_PATTERN_NAME = "name==%s"; private final CustomFieldsClient customFieldsClient; - private final RelatedModulesProperties relatedModulesProperties; + private final ModuleTenantService moduleTenantService; @Override public void createCustomField(CustomField customField) { log.info("createCustomField::creating new custom-field with name {}", customField.getName()); - customFieldsClient.postCustomFields(relatedModulesProperties.getModUsersId(), customField); + var modUsersModuleId = moduleTenantService.getModUsersModuleId(); + customFieldsClient.postCustomFields(modUsersModuleId, customField); log.info("createCustomField::custom-field with name {} created", customField.getName()); } + @Override public CustomField getCustomFieldByName(String name) { log.debug("getCustomFieldByName::getting custom-field with name {}.", name); - return customFieldsClient.getByQuery(relatedModulesProperties.getModUsersId(), format(QUERY_PATTERN_NAME, name)) + var modUsersModuleId = moduleTenantService.getModUsersModuleId(); + return customFieldsClient.getByQuery(modUsersModuleId, format(QUERY_PATTERN_NAME, name)) .getCustomFields().stream().filter(customField -> customField.getName().equals(name)) .findFirst() .orElse(null); } } + diff --git a/src/main/java/org/folio/consortia/service/HttpRequestServiceImpl.java b/src/main/java/org/folio/consortia/service/impl/HttpRequestServiceImpl.java similarity index 96% rename from src/main/java/org/folio/consortia/service/HttpRequestServiceImpl.java rename to src/main/java/org/folio/consortia/service/impl/HttpRequestServiceImpl.java index 0ad46ac5..9152fe16 100644 --- a/src/main/java/org/folio/consortia/service/HttpRequestServiceImpl.java +++ b/src/main/java/org/folio/consortia/service/impl/HttpRequestServiceImpl.java @@ -1,4 +1,4 @@ -package org.folio.consortia.service; +package org.folio.consortia.service.impl; import java.util.ArrayList; import java.util.Collection; @@ -6,6 +6,7 @@ import java.util.Map; import org.folio.consortia.domain.dto.PublicationHttpResponse; +import org.folio.consortia.service.HttpRequestService; import org.folio.spring.FolioExecutionContext; import org.folio.spring.integration.XOkapiHeaders; import org.springframework.http.HttpEntity; diff --git a/src/main/java/org/folio/consortia/service/impl/ModuleTenantServiceImpl.java b/src/main/java/org/folio/consortia/service/impl/ModuleTenantServiceImpl.java new file mode 100644 index 00000000..1149db7d --- /dev/null +++ b/src/main/java/org/folio/consortia/service/impl/ModuleTenantServiceImpl.java @@ -0,0 +1,66 @@ +package org.folio.consortia.service.impl; + +import static org.folio.consortia.utils.Constants.EUREKA_PLATFORM; + +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.log4j.Log4j2; +import org.apache.commons.lang3.StringUtils; +import org.folio.consortia.client.EurekaProxyTenantsClient; +import org.folio.consortia.client.OkapiClient; +import org.folio.consortia.domain.dto.ModuleForTenant; +import org.folio.consortia.service.ModuleTenantService; +import org.folio.spring.FolioExecutionContext; +import org.folio.spring.exception.NotFoundException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.util.List; +import java.util.Optional; + + +@Log4j2 +@Service +@RequiredArgsConstructor +public class ModuleTenantServiceImpl implements ModuleTenantService { + + private static final String URL_PREFIX = "http://_"; + private static final String MOD_USERS = "mod-users"; + private static final String MOD_USERS_NOT_FOUND_ERROR = "Module id not found for name: " + MOD_USERS; + private static final String MOD_USERS_REGEXP = "^mod-users-\\d.*$"; + + @Setter + @Value("${folio.platform}") + private String platform; + + private final FolioExecutionContext folioExecutionContext; + private final OkapiClient okapiClient; + private final EurekaProxyTenantsClient eurekaProxyTenantsClient; + + @Override + @Cacheable(cacheNames = "modUsersModuleIds") + public String getModUsersModuleId() { + var moduleId = StringUtils.equals(EUREKA_PLATFORM, platform) ? getModUsersModuleIdForEureka() : getModUsersModuleIdForOkapi(); + return moduleId.orElseThrow(() -> new NotFoundException(MOD_USERS_NOT_FOUND_ERROR)); + } + + private Optional getModUsersModuleIdForOkapi() { + var tenantId = folioExecutionContext.getTenantId(); + var modules = okapiClient.getModuleIds(URI.create(URL_PREFIX), tenantId, MOD_USERS); + if (!modules.isEmpty()) { + return Optional.of(modules.get(0).getId()); + } + return Optional.empty(); + } + + private Optional getModUsersModuleIdForEureka() { + var modules = eurekaProxyTenantsClient.getModules(URI.create(URL_PREFIX), folioExecutionContext.getTenantId()); + return filterModUsersModuleId(modules); + } + + private Optional filterModUsersModuleId(List modules) { + return modules.stream().map(ModuleForTenant::getId).filter(id -> id.matches(MOD_USERS_REGEXP)).findFirst(); + } +} diff --git a/src/main/java/org/folio/consortia/utils/Constants.java b/src/main/java/org/folio/consortia/utils/Constants.java index 4defa37a..546054f8 100644 --- a/src/main/java/org/folio/consortia/utils/Constants.java +++ b/src/main/java/org/folio/consortia/utils/Constants.java @@ -6,4 +6,5 @@ private Constants() { } public static final String SYSTEM_USER_NAME = "mod-consortia-keycloak"; + public static final String EUREKA_PLATFORM = "eureka"; } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 81ba7986..3dae6aa7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -76,6 +76,8 @@ folio: timer: publication-records-max-age-in-seconds: 86400 max-active-threads: 5 + platform: ${PLATFORM:okapi} + feign: client: config: @@ -94,5 +96,3 @@ management: logging: level: liquibase: info -related-modules: - mod-users-id: ${MOD_USERS_ID} diff --git a/src/test/java/org/folio/consortia/service/CustomFieldServiceTest.java b/src/test/java/org/folio/consortia/service/CustomFieldServiceTest.java index 2b9d28ec..74a37f34 100644 --- a/src/test/java/org/folio/consortia/service/CustomFieldServiceTest.java +++ b/src/test/java/org/folio/consortia/service/CustomFieldServiceTest.java @@ -7,7 +7,6 @@ import static org.mockito.Mockito.when; import org.folio.consortia.client.CustomFieldsClient; -import org.folio.consortia.config.property.RelatedModulesProperties; import org.folio.consortia.domain.dto.CustomField; import org.folio.consortia.domain.dto.CustomFieldCollection; import org.folio.consortia.domain.dto.CustomFieldType; @@ -27,12 +26,13 @@ @SpringBootTest @EnableAutoConfiguration(exclude = BatchAutoConfiguration.class) class CustomFieldServiceTest { + @InjectMocks CustomFieldServiceImpl customFieldService; @Mock CustomFieldsClient customFieldsClient; @Mock - RelatedModulesProperties relatedModulesProperties; + ModuleTenantService moduleTenantService; @Mock FolioExecutionContext folioExecutionContext; @@ -48,12 +48,12 @@ class CustomFieldServiceTest { void shouldCreateCustomField() { CustomField customField = CustomField.builder().build(); when(folioExecutionContext.getTenantId()).thenReturn(CENTRAL_TENANT_ID); - when(relatedModulesProperties.getModUsersId()).thenReturn("USERS"); + when(moduleTenantService.getModUsersModuleId()).thenReturn("USERS"); Mockito.doNothing().when(customFieldsClient).postCustomFields(any(), eq(customField)); customFieldService.createCustomField(customField); Mockito.verify(customFieldsClient).postCustomFields(any(), any()); - Mockito.verify(relatedModulesProperties, times(1)).getModUsersId(); + Mockito.verify(moduleTenantService, times(1)).getModUsersModuleId(); } @Test @@ -62,12 +62,12 @@ void shouldGetCustomField() { customFieldCollection.setCustomFields(List.of(ORIGINAL_TENANT_ID_CUSTOM_FIELD)); customFieldCollection.setTotalRecords(1); when(folioExecutionContext.getTenantId()).thenReturn(CENTRAL_TENANT_ID); - when(relatedModulesProperties.getModUsersId()).thenReturn("USERS"); + when(moduleTenantService.getModUsersModuleId()).thenReturn("USERS"); when(customFieldsClient.getByQuery(any(), eq("name==originalTenantId"))).thenReturn(customFieldCollection); var customFields = customFieldService.getCustomFieldByName("originalTenantId"); Assertions.assertEquals("originalTenantId", customFields.getName()); Mockito.verify(customFieldsClient, times(1)).getByQuery(any(), any()); - Mockito.verify(relatedModulesProperties, times(1)).getModUsersId(); + Mockito.verify(moduleTenantService, times(1)).getModUsersModuleId(); } } diff --git a/src/test/java/org/folio/consortia/service/HttpRequestServiceImplTest.java b/src/test/java/org/folio/consortia/service/HttpRequestServiceImplTest.java index e1fddb09..f887c2aa 100644 --- a/src/test/java/org/folio/consortia/service/HttpRequestServiceImplTest.java +++ b/src/test/java/org/folio/consortia/service/HttpRequestServiceImplTest.java @@ -6,6 +6,7 @@ import static org.mockito.Mockito.when; import org.folio.consortia.base.BaseUnitTest; +import org.folio.consortia.service.impl.HttpRequestServiceImpl; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; diff --git a/src/test/java/org/folio/consortia/service/ModuleTenantServiceTest.java b/src/test/java/org/folio/consortia/service/ModuleTenantServiceTest.java new file mode 100644 index 00000000..a7711a74 --- /dev/null +++ b/src/test/java/org/folio/consortia/service/ModuleTenantServiceTest.java @@ -0,0 +1,75 @@ +package org.folio.consortia.service; + +import static org.folio.consortia.utils.Constants.EUREKA_PLATFORM; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.List; + +import org.folio.consortia.client.EurekaProxyTenantsClient; +import org.folio.consortia.client.OkapiClient; +import org.folio.consortia.domain.dto.ModuleForTenant; +import org.folio.consortia.service.impl.ModuleTenantServiceImpl; +import org.folio.spring.FolioExecutionContext; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + + +@ExtendWith(MockitoExtension.class) +class ModuleTenantServiceTest { + + @Mock + private FolioExecutionContext folioExecutionContext; + @Mock + private OkapiClient okapiClient; + @Mock + private EurekaProxyTenantsClient eurekaProxyTenantsClient; + + @InjectMocks + private ModuleTenantServiceImpl moduleTenantService; + + @Test + void getModuleIdForOkapiTest() { + var tenantId = "diku"; + var moduleId = "mod-users-19.4.1-SNAPSHOT.322"; + + var module = new ModuleForTenant(); + module.setId(moduleId); + when(folioExecutionContext.getTenantId()).thenReturn(tenantId); + when(okapiClient.getModuleIds(isA(URI.class), eq(tenantId), eq("mod-users"))).thenReturn(List.of(module)); + + var actual = moduleTenantService.getModUsersModuleId(); + + verify(okapiClient).getModuleIds(isA(URI.class), eq(tenantId), eq("mod-users")); + assertEquals(moduleId, actual); + } + + @Test + void getModuleIdForEurekaTest() { + var tenantId = "diku"; + var moduleId = "mod-users-19.4.1-SNAPSHOT.322"; + + var module1 = new ModuleForTenant(); + module1.setId(moduleId); + var module2 = new ModuleForTenant(); + module2.setId("mod-users-bl-7.9.2-SNAPSHOT.170"); + var module3 = new ModuleForTenant(); + module3.setId("mod-users"); + + when(folioExecutionContext.getTenantId()).thenReturn(tenantId); + when(eurekaProxyTenantsClient.getModules(isA(URI.class), eq(tenantId))).thenReturn(List.of(module1, module2, module3)); + + moduleTenantService.setPlatform(EUREKA_PLATFORM); + var actual = moduleTenantService.getModUsersModuleId(); + + verify(eurekaProxyTenantsClient).getModules(isA(URI.class), eq(tenantId)); + assertEquals(moduleId, actual); + } +}