Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Spring context accessor for accessing tenant properties statically #271

Open
wants to merge 1 commit into
base: investigation-2024_12_02-staging
Choose a base branch
from
Open
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
44 changes: 44 additions & 0 deletions src/main/java/org/folio/rest/camunda/SpringContextAccessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}

/**
* Retrieves a bean from the application context by its class type.
*
* @param <T> the type of the bean to retrieve
* @param beanClass the class of the bean to retrieve
* @return the bean instance
*/
public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}

}
37 changes: 2 additions & 35 deletions src/main/java/org/folio/rest/camunda/config/CamundaTenantInit.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,27 @@
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;

@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 TENANT_HEADER_NAME;

private SqlTemplateService sqlTemplateService;

private TenantProperties tenantProperties;

@Autowired
public CamundaTenantInit(SqlTemplateService sqlTemplateService, TenantProperties tenantProperties) {
public CamundaTenantInit(SqlTemplateService sqlTemplateService) {
this.sqlTemplateService = sqlTemplateService;
this.tenantProperties = tenantProperties;
}

@PostConstruct
public void initializeStaticTenantHeader() {
TENANT_HEADER_NAME = tenantProperties.getHeaderName();
}

@Override
Expand All @@ -51,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 TENANT_HEADER_NAME;
}

public class CamundaTenant {

private final String id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public class MappingParametersUtility {
* Used as a constant endpoint for retrieving mapping configuration specific to
* MARC bibliographic records.
*/
@SuppressWarnings("java:S1075")
static final String MAPPING_RULES_PATH = "/mapping-rules/marc-bib";

private MappingParametersUtility() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
104 changes: 76 additions & 28 deletions src/main/java/org/folio/rest/camunda/utility/OkapiRestTemplate.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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.
*
* <p>This custom REST template provides a fluent interface for configuring
* <p>
* This custom REST template provides a fluent interface for configuring
* Okapi-specific request parameters such as tenant identification,
* authentication tokens, and base URL handling.</p>
* authentication tokens, and base URL handling.
* </p>
*
* <p>The class follows the builder pattern, allowing for easy and flexible
* configuration of REST template instances with Okapi-specific requirements.</p>
* <p>
* The class follows the builder pattern, allowing for easy and flexible
* configuration of REST template instances with Okapi-specific requirements.
* </p>
*
* <p>Key features include:
* <ul>
* <li>Automatic addition of Okapi-specific headers</li>
* <li>Fluent configuration methods</li>
* <li>Simplified REST template creation</li>
* </ul>
* <p>
* Key features include:
* <ul>
* <li>Automatic addition of Okapi-specific headers</li>
* <li>Fluent configuration methods</li>
* <li>Simplified REST template creation</li>
* </ul>
* </p>
*
* @see RestTemplate
Expand All @@ -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.
*
* <p>
* This ensures that instances are created through a controlled, configurable
* process.
* </p>
*/
public OkapiRestTemplate() {
private OkapiRestTemplate() {

}

/**
* Configures the REST template with tenant and authentication details.
*
* <p>This method sets up an HTTP request interceptor that automatically
* adds Okapi-specific headers to each request:
* <ul>
* <li>X-Okapi-Tenant header</li>
* <li>X-Okapi-Token header</li>
* <li>Accepted media types (JSON and plain text)</li>
* <li>Content type (JSON)</li>
* </ul>
* <p>
* This method sets up an HTTP request interceptor that automatically adds
* Okapi-specific headers to each request:
* <ul>
* <li>X-Okapi-Tenant header</li>
* <li>X-Okapi-Token header</li>
* <li>Accepted media types (JSON and plain text)</li>
* <li>Content type (JSON)</li>
* </ul>
* </p>
*
* @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);
Expand All @@ -94,8 +114,10 @@ public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttp
/**
* Configures the base URL for all requests made through this REST template.
*
* <p>This method sets up a {@link DefaultUriBuilderFactory} with the provided
* Okapi URL, which will be used as the base for all subsequent REST requests.</p>
* <p>
* This method sets up a {@link DefaultUriBuilderFactory} with the provided
* Okapi URL, which will be used as the base for all subsequent REST requests.
* </p>
*
* @param okapiUrl the base URL for the Okapi service (must not be null)
* @return the configured {@code OkapiRestTemplate} instance
Expand All @@ -108,4 +130,30 @@ public OkapiRestTemplate at(@NonNull String okapiUrl) {
return this;
}

/**
* Creates a new instance of {@code OkapiRestTemplate}.
*
* <p>
* 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.
* </p>
*
* <p>
* Example usage:
*
* <pre>{@code
* OkapiRestTemplate restTemplate = OkapiRestTemplate.build()
* .with("tenant-id", "auth-token")
* .at("https://okapi.example.edu");
* }
* </pre>
* </p>
*
* @return a new, unconfigured {@code OkapiRestTemplate} instance
*/
public static OkapiRestTemplate build() {
return new OkapiRestTemplate();
}

}
Original file line number Diff line number Diff line change
@@ -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));
}

}
Loading