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);