diff --git a/src/main/java/uk/gov/hmcts/opal/cache/CacheConfig.java b/src/main/java/uk/gov/hmcts/opal/cache/CacheConfig.java new file mode 100644 index 000000000..05645d4ef --- /dev/null +++ b/src/main/java/uk/gov/hmcts/opal/cache/CacheConfig.java @@ -0,0 +1,104 @@ +package uk.gov.hmcts.opal.cache; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.cache.RedisCacheConfiguration; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair; + +import java.time.Duration; + +@Slf4j +@Configuration +@EnableCaching +public class CacheConfig { + + @Value("${opal.redis.enabled}") + private boolean redisEnabled; + + @Value("${spring.data.redis.host}") + private String redisHost; + + @Value("${spring.data.redis.port}") + private int redisPort; + + @Value("${opal.redis.ttl-hours}") + private long redisTtlHours; + + private CacheManager cacheManager; + + @Bean + @ConditionalOnProperty(name = "opal.redis.enabled", havingValue = "true") + public RedisConnectionFactory redisConnectionFactory() { + RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisHost, redisPort); + return new LettuceConnectionFactory(config); + } + + @Bean + @ConditionalOnProperty(name = "opal.redis.enabled", havingValue = "true") + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory); + return template; + } + + @Bean + @Primary + @ConditionalOnProperty(name = "opal.redis.enabled", havingValue = "true") + public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) { + RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofHours(redisTtlHours)) + .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); + + this.cacheManager = RedisCacheManager.builder(redisConnectionFactory) + .cacheDefaults(cacheConfig) + .build(); + logCacheDetails(cacheManager); + return cacheManager; + } + + @Bean + @ConditionalOnProperty(name = "opal.redis.enabled", havingValue = "false", matchIfMissing = true) + public CacheManager simpleCacheManager() { + this.cacheManager = new ConcurrentMapCacheManager(); + logCacheDetails(cacheManager); + return cacheManager; + } + + public void logCacheDetails(CacheManager cacheManager) { + log.info("------------------------------"); + log.info("Cache Configuration Details:"); + log.info("Redis Enabled: {}", redisEnabled); + log.info("Redis Host: {}", redisHost); + log.info("Redis Port: {}", redisPort); + log.info("Redis TTL (hours): {}", redisTtlHours); + if (cacheManager != null) { + log.info("Cache Manager: {}", cacheManager.getClass().getName()); + if (cacheManager instanceof RedisCacheManager) { + log.info("Using Redis Cache Manager"); + } else if (cacheManager instanceof ConcurrentMapCacheManager) { + log.info("Using Concurrent Map Cache Manager (local cache)"); + } + } else { + log.warn("Cache Manager is null. This might indicate a configuration issue."); + } + + log.info("Available Caches:"); + if (cacheManager != null) { + cacheManager.getCacheNames().forEach(cacheName -> log.info("- {}", cacheName)); + } + log.info("------------------------------"); + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index f428d48f6..6f03beb3c 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -155,7 +155,7 @@ opal: file-handler-job: cron: ${OPAL_FILE_HANDLER_JOB_CRON:0 0 * * * ?} frontend: - url: ${OPAL_FRONTEND_URL:http://localhost:4200} + url: ${OPAL_FRONTEND_URL:http://localhost:4200} azure: active-directory-justice-auth-uri: https://login.microsoftonline.com testing-support-endpoints: diff --git a/src/test/java/uk/gov/hmcts/opal/cache/CacheConfigTest.java b/src/test/java/uk/gov/hmcts/opal/cache/CacheConfigTest.java new file mode 100644 index 000000000..460a470da --- /dev/null +++ b/src/test/java/uk/gov/hmcts/opal/cache/CacheConfigTest.java @@ -0,0 +1,68 @@ +package uk.gov.hmcts.opal.cache; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.cache.CacheManager; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.data.redis.cache.RedisCacheManager; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +@SpringBootTest(classes = {CacheConfig.class, CacheConfigTest.TestConfig.class}) +class CacheConfigTest { + + @Autowired + private CacheManager cacheManager; + + @Autowired + private RedisConnectionFactory redisConnectionFactory; + + @DynamicPropertySource + static void registerProperties(DynamicPropertyRegistry registry) { + registry.add("spring.data.redis.host", () -> "localhost"); + registry.add("spring.data.redis.port", () -> "6379"); + registry.add("opal.redis.ttl.hours", () -> "8"); + registry.add("opal.redis.enabled", () -> "false"); + } + + @Test + void whenRedisEnabled_thenReturnRedisCacheManager() { + assertTrue(cacheManager instanceof RedisCacheManager); + } + + @Test + void whenRedisDisabled_thenReturnSimpleCacheManager() { + CacheConfig config = new CacheConfig(); + CacheManager simpleCacheManager = config.simpleCacheManager(); + assertTrue(simpleCacheManager instanceof ConcurrentMapCacheManager); + } + + @Test + void testRedisConnectionFactory() { + assertNotNull(redisConnectionFactory); + } + + @TestConfiguration + static class TestConfig { + @Bean + @Primary + public RedisConnectionFactory redisConnectionFactory() { + return mock(RedisConnectionFactory.class); + } + + @Bean + @Primary + public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) { + return RedisCacheManager.builder(redisConnectionFactory).build(); + } + } +}