diff --git a/src/main/java/org/folio/rest/camunda/SpringContextAccessor.java b/src/main/java/org/folio/rest/camunda/SpringContextAccessor.java new file mode 100644 index 00000000..31580e7f --- /dev/null +++ b/src/main/java/org/folio/rest/camunda/SpringContextAccessor.java @@ -0,0 +1,45 @@ +package org.folio.rest.camunda; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * SpringContextAccessor is a utility class that provides access to the Spring + * application context. + * + * It implements ApplicationContextAware to automatically set the application + * context when the Spring container starts. + */ +@Component +public class SpringContextAccessor implements ApplicationContextAware { + + private static ApplicationContext context; + + /** + * Sets the application context. This method is called by the Spring framework + * when the application context is initialized. + * + * @param applicationContext the application context to set + * @throws BeansException if an error occurs while setting the application + * context + */ + @Override + @SuppressWarnings("java:S2696") // SonarQube static assignment from non-static method. + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + context = applicationContext; + } + + /** + * Retrieves a bean from the application context by its class type. + * + * @param the type of the bean to retrieve + * @param beanClass the class of the bean to retrieve + * @return the bean instance + */ + public static T getBean(Class beanClass) { + return context.getBean(beanClass); + } + +} diff --git a/src/main/java/org/folio/rest/camunda/config/CamundaTenantInit.java b/src/main/java/org/folio/rest/camunda/config/CamundaTenantInit.java index 58a255dd..9d91de9f 100644 --- a/src/main/java/org/folio/rest/camunda/config/CamundaTenantInit.java +++ b/src/main/java/org/folio/rest/camunda/config/CamundaTenantInit.java @@ -1,12 +1,11 @@ package org.folio.rest.camunda.config; -import jakarta.annotation.PostConstruct; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.UUID; + import org.folio.spring.tenant.hibernate.HibernateTenantInit; -import org.folio.spring.tenant.properties.TenantProperties; import org.folio.spring.tenant.service.SqlTemplateService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -14,31 +13,15 @@ @Component public class CamundaTenantInit implements HibernateTenantInit { - /** - * HTTP header name for providing the authentication token. - */ - public static final String OKAPI_TOKEN_HEADER = "X-Okapi-Token"; - private static final String SCHEMA_IMPORT_TENANT = "import/tenant"; private static final String TENANT_TEMPLATE_KEY = "tenant"; - private static String tenantHeaderName; - private SqlTemplateService sqlTemplateService; - private TenantProperties tenantProperties; - @Autowired - public CamundaTenantInit(SqlTemplateService sqlTemplateService, TenantProperties tenantProperties) { + public CamundaTenantInit(SqlTemplateService sqlTemplateService) { this.sqlTemplateService = sqlTemplateService; - this.tenantProperties = tenantProperties; - } - - @SuppressWarnings("java:S2696") // SonarQube static assignment from non-static method. - @PostConstruct - public void initializeStaticTenantHeader() { - tenantHeaderName = tenantProperties.getHeaderName(); } @Override @@ -52,23 +35,6 @@ public void initialize(Connection connection, String tenant) throws SQLException } } - /** - * Provide a static way get the tenant header name value. - * - * The yaml file names this `tenant.header-name`. - * The environment variable for this is `TENANT_HEADERNAME`. - * - * The OkapiRestTemplate design prevents auto-injection because non-Java code will run the static methods via the MappingUtility. - * Load the settings in such a way that a static method may access settings injected by Spring. - * - * These settings are normally defined in the Spring-Module-Core Spring-Tenant `TenantProperties` class - * - * @return the tenant header. - */ - public static String getHeaderName() { - return tenantHeaderName; - } - public class CamundaTenant { private final String id; diff --git a/src/main/java/org/folio/rest/camunda/utility/MappingParametersUtility.java b/src/main/java/org/folio/rest/camunda/utility/MappingParametersUtility.java index d2837bd3..55a0e86e 100644 --- a/src/main/java/org/folio/rest/camunda/utility/MappingParametersUtility.java +++ b/src/main/java/org/folio/rest/camunda/utility/MappingParametersUtility.java @@ -103,7 +103,7 @@ public class MappingParametersUtility { * Used as a constant endpoint for retrieving mapping configuration specific to * MARC bibliographic records. */ - @SuppressWarnings("java:S1075") // SonarQube path false positive. + @SuppressWarnings("java:S1075") static final String MAPPING_RULES_PATH = "/mapping-rules/marc-bib"; private MappingParametersUtility() { diff --git a/src/main/java/org/folio/rest/camunda/utility/MappingUtility.java b/src/main/java/org/folio/rest/camunda/utility/MappingUtility.java index cc31b827..074f79a6 100644 --- a/src/main/java/org/folio/rest/camunda/utility/MappingUtility.java +++ b/src/main/java/org/folio/rest/camunda/utility/MappingUtility.java @@ -46,7 +46,7 @@ public class MappingUtility { private static final ObjectMapper objectMapper = new ObjectMapper(); /** Rest template for making Okapi-based REST calls. */ - static OkapiRestTemplate restTemplate = new OkapiRestTemplate(); + static OkapiRestTemplate restTemplate = OkapiRestTemplate.build(); /** * Private constructor to prevent instantiation of utility class. diff --git a/src/main/java/org/folio/rest/camunda/utility/OkapiRestTemplate.java b/src/main/java/org/folio/rest/camunda/utility/OkapiRestTemplate.java index f51f0980..59a8a491 100644 --- a/src/main/java/org/folio/rest/camunda/utility/OkapiRestTemplate.java +++ b/src/main/java/org/folio/rest/camunda/utility/OkapiRestTemplate.java @@ -1,12 +1,14 @@ package org.folio.rest.camunda.utility; +import static org.folio.rest.camunda.SpringContextAccessor.getBean; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.TEXT_PLAIN; import java.io.IOException; import java.util.Arrays; import java.util.Collections; -import org.folio.rest.camunda.config.CamundaTenantInit; + +import org.folio.spring.tenant.properties.TenantProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; @@ -19,22 +21,27 @@ import org.springframework.web.util.DefaultUriBuilderFactory; /** - * A specialized extension of Spring's {@link RestTemplate} designed specifically - * for interacting with Okapi-based microservice platforms. + * A specialized extension of Spring's {@link RestTemplate} designed + * specifically for interacting with Okapi-based microservice platforms. * - *

This custom REST template provides a fluent interface for configuring + *

+ * This custom REST template provides a fluent interface for configuring * Okapi-specific request parameters such as tenant identification, - * authentication tokens, and base URL handling.

+ * authentication tokens, and base URL handling. + *

* - *

The class follows the builder pattern, allowing for easy and flexible - * configuration of REST template instances with Okapi-specific requirements.

+ *

+ * The class follows the builder pattern, allowing for easy and flexible + * configuration of REST template instances with Okapi-specific requirements. + *

* - *

Key features include: - *

    - *
  • Automatic addition of Okapi-specific headers
  • - *
  • Fluent configuration methods
  • - *
  • Simplified REST template creation
  • - *
+ *

+ * Key features include: + *

    + *
  • Automatic addition of Okapi-specific headers
  • + *
  • Fluent configuration methods
  • + *
  • Simplified REST template creation
  • + *
*

* * @see RestTemplate @@ -45,39 +52,52 @@ public class OkapiRestTemplate extends RestTemplate { private static final Logger LOG = LoggerFactory.getLogger(OkapiRestTemplate.class); /** - * Constructor. + * HTTP header name for providing the authentication token. + */ + static final String OKAPI_TOKEN_HEADER = "X-Okapi-Token"; + + /** + * Private constructor to enforce the use of the {@link #build()} method for + * creating instances. + * + *

+ * This ensures that instances are created through a controlled, configurable + * process. + *

*/ - public OkapiRestTemplate() { - // Nothing to assign. + private OkapiRestTemplate() { + } /** * Configures the REST template with tenant and authentication details. * - *

This method sets up an HTTP request interceptor that automatically - * adds Okapi-specific headers to each request: - *

    - *
  • X-Okapi-Tenant header
  • - *
  • X-Okapi-Token header
  • - *
  • Accepted media types (JSON and plain text)
  • - *
  • Content type (JSON)
  • - *
+ *

+ * This method sets up an HTTP request interceptor that automatically adds + * Okapi-specific headers to each request: + *

    + *
  • X-Okapi-Tenant header
  • + *
  • X-Okapi-Token header
  • + *
  • Accepted media types (JSON and plain text)
  • + *
  • Content type (JSON)
  • + *
*

* * @param tenant the Okapi tenant identifier (must not be null) - * @param token the authentication token (must not be null) + * @param token the authentication token (must not be null) * @return the configured {@code OkapiRestTemplate} instance * @throws IllegalArgumentException if tenant or token is null or empty */ public OkapiRestTemplate with(@NonNull String tenant, @NonNull String token) { + final TenantProperties tenantProperties = getBean(TenantProperties.class); this.setInterceptors(Collections.singletonList(new ClientHttpRequestInterceptor() { @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { HttpHeaders headers = request.getHeaders(); - headers.set(CamundaTenantInit.getHeaderName(), tenant); - headers.set(CamundaTenantInit.OKAPI_TOKEN_HEADER, token); + headers.set(tenantProperties.getHeaderName(), tenant); + headers.set(OKAPI_TOKEN_HEADER, token); headers.setAccept(Arrays.asList(APPLICATION_JSON, TEXT_PLAIN)); headers.setContentType(APPLICATION_JSON); @@ -94,8 +114,10 @@ public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttp /** * Configures the base URL for all requests made through this REST template. * - *

This method sets up a {@link DefaultUriBuilderFactory} with the provided - * Okapi URL, which will be used as the base for all subsequent REST requests.

+ *

+ * This method sets up a {@link DefaultUriBuilderFactory} with the provided + * Okapi URL, which will be used as the base for all subsequent REST requests. + *

* * @param okapiUrl the base URL for the Okapi service (must not be null) * @return the configured {@code OkapiRestTemplate} instance @@ -108,4 +130,30 @@ public OkapiRestTemplate at(@NonNull String okapiUrl) { return this; } + /** + * Creates a new instance of {@code OkapiRestTemplate}. + * + *

+ * This static factory method provides a fluent way to create and configure an + * Okapi-specific REST template. It follows the builder pattern, allowing method + * chaining for configuration. + *

+ * + *

+ * Example usage: + * + *

{@code
+   * OkapiRestTemplate restTemplate = OkapiRestTemplate.build()
+   *     .with("tenant-id", "auth-token")
+   *     .at("https://okapi.example.edu");
+   * }
+   * 
+ *

+ * + * @return a new, unconfigured {@code OkapiRestTemplate} instance + */ + public static OkapiRestTemplate build() { + return new OkapiRestTemplate(); + } + } diff --git a/src/test/java/org/folio/rest/camunda/SpringContextAccessorTest.java b/src/test/java/org/folio/rest/camunda/SpringContextAccessorTest.java new file mode 100644 index 00000000..84fd236b --- /dev/null +++ b/src/test/java/org/folio/rest/camunda/SpringContextAccessorTest.java @@ -0,0 +1,28 @@ +package org.folio.rest.camunda; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.folio.spring.tenant.properties.TenantProperties; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; + +@SpringBootTest( + classes = { + SpringContextAccessor.class, + TenantProperties.class + }, + webEnvironment = WebEnvironment.MOCK +) +class SpringContextAccessorTest { + + @ParameterizedTest + @ValueSource(classes = { + TenantProperties.class + }) + void testGetTenantPropertiesBean(Class bean) { + assertNotNull(SpringContextAccessor.getBean(bean)); + } + +} diff --git a/src/test/java/org/folio/rest/camunda/utility/OkapiRestTemplateTest.java b/src/test/java/org/folio/rest/camunda/utility/OkapiRestTemplateTest.java index 0406a19d..5b31474d 100644 --- a/src/test/java/org/folio/rest/camunda/utility/OkapiRestTemplateTest.java +++ b/src/test/java/org/folio/rest/camunda/utility/OkapiRestTemplateTest.java @@ -14,13 +14,14 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; -import org.folio.rest.camunda.config.CamundaTenantInit; + +import org.folio.rest.camunda.SpringContextAccessor; import org.folio.spring.tenant.properties.TenantProperties; -import org.folio.spring.tenant.service.SqlTemplateService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.http.HttpHeaders; @@ -29,13 +30,10 @@ import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.web.util.DefaultUriBuilderFactory; -import org.thymeleaf.spring6.SpringTemplateEngine; @SpringBootTest( classes = { - CamundaTenantInit.class, - SpringTemplateEngine.class, - SqlTemplateService.class, + SpringContextAccessor.class, TenantProperties.class }, webEnvironment = WebEnvironment.MOCK @@ -46,9 +44,12 @@ class OkapiRestTemplateTest { @Spy OkapiRestTemplate okapiRestTemplate; + @Autowired + TenantProperties tenantProperties; + @Test void testBuild() { - OkapiRestTemplate restTemplate = new OkapiRestTemplate(); + OkapiRestTemplate restTemplate = OkapiRestTemplate.build(); assertNotNull(restTemplate); } @@ -56,11 +57,12 @@ void testBuild() { void testAt() { String okapiUrl = "http:://localhost:9130"; - OkapiRestTemplate updated = okapiRestTemplate.at(okapiUrl); + okapiRestTemplate.at(okapiUrl); - verify(updated, times(1)).setUriTemplateHandler(any(DefaultUriBuilderFactory.class)); + verify(okapiRestTemplate, times(1)) + .setUriTemplateHandler(any(DefaultUriBuilderFactory.class)); - assertTrue(((DefaultUriBuilderFactory) updated.getUriTemplateHandler()).hasBaseUri()); + assertTrue(((DefaultUriBuilderFactory) okapiRestTemplate.getUriTemplateHandler()).hasBaseUri()); } @Test @@ -68,11 +70,12 @@ void testWith() throws IOException { String tenant = "diku"; String token = "token"; - OkapiRestTemplate updated = okapiRestTemplate.with(tenant, token); + okapiRestTemplate.with(tenant, token); - verify(updated, times(1)).setInterceptors(any(List.class)); + verify(okapiRestTemplate, times(1)) + .setInterceptors(any(List.class)); - List interceptor = updated.getInterceptors(); + List interceptor = okapiRestTemplate.getInterceptors(); assertNotNull(interceptor); assertEquals(1, interceptor.size()); @@ -89,8 +92,8 @@ void testWith() throws IOException { interceptor.get(0).intercept(request, body, execution); - verify(headers).set(CamundaTenantInit.getHeaderName(), tenant); - verify(headers).set(CamundaTenantInit.OKAPI_TOKEN_HEADER, token); + verify(headers).set(tenantProperties.getHeaderName(), tenant); + verify(headers).set(OkapiRestTemplate.OKAPI_TOKEN_HEADER, token); verify(headers).setAccept(Arrays.asList(APPLICATION_JSON, TEXT_PLAIN)); verify(headers).setContentType(APPLICATION_JSON);