-
Notifications
You must be signed in to change notification settings - Fork 0
feat: implement RestRedisCache and RedisClient for REST-based Redis s… #1
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
base: feature/add-cache-configurations
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -124,6 +124,7 @@ public interface Cache<K extends CacheKey> { | |
| * <ul> | ||
| * <li>"local" - LocalCache for file-based storage</li> | ||
| * <li>"redis" - RedisCache for Redis-based storage</li> | ||
| * <li>"rest_redis" - RestRedisCache for REST-based Redis storage</li> | ||
| * </ul> | ||
| * | ||
| * @param <K> The type of cache key | ||
|
|
@@ -136,7 +137,7 @@ public interface Cache<K extends CacheKey> { | |
| */ | ||
| static <K extends CacheKey> Cache<K> createByType( | ||
| String type, CacheParameter<K> parameters, @Nullable String cacheDir, @Nullable ObjectMapper mapper) { | ||
| return switch (type) { | ||
| return switch (type.toLowerCase()) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Enum |
||
| case LOCAL_CACHE_NAME -> { | ||
| if (cacheDir == null) { | ||
| throw new IllegalArgumentException("Cache directory must be provided for local cache"); | ||
|
|
@@ -149,6 +150,7 @@ static <K extends CacheKey> Cache<K> createByType( | |
| } | ||
| yield new RedisCache<>(parameters, mapper); | ||
| } | ||
| case "rest_redis" -> new RestRedisCache<>(parameters, mapper); | ||
|
|
||
| default -> | ||
| throw new IllegalArgumentException("Unknown cache type: " + type + ". Supported types: local, redis"); | ||
|
|
||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| /* Licensed under MIT 2026. */ | ||
| package edu.kit.kastel.sdq.lissa.ratlr.cache; | ||
|
|
||
| import redis.clients.jedis.UnifiedJedis; | ||
|
|
||
| public class RedisAdapter implements UnifiedRedisClient { | ||
|
|
||
| private final UnifiedJedis jedis; | ||
|
|
||
| RedisAdapter(UnifiedJedis jedis) { | ||
| this.jedis = jedis; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean ping() { | ||
| // TODO Find out what ping should return | ||
| return jedis.ping() != null; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean exists(String key) { | ||
| return jedis.exists(key); | ||
| } | ||
|
|
||
| @Override | ||
| public String hget(String key, String field) { | ||
| return jedis.hget(key, field); | ||
| } | ||
|
|
||
| @Override | ||
| public long hset(String key, String field, String value) { | ||
| return jedis.hset(key, field, value); | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| jedis.close(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,15 +5,13 @@ | |
| import java.util.*; | ||
|
|
||
| import org.jspecify.annotations.Nullable; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import com.fasterxml.jackson.core.JsonProcessingException; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
|
||
| import edu.kit.kastel.sdq.lissa.ratlr.utils.Environment; | ||
|
|
||
| import redis.clients.jedis.UnifiedJedis; | ||
| import redis.clients.jedis.RedisClient; | ||
|
|
||
| /** | ||
| * Implements a Redis-based cache for storing and retrieving values. For multi-layer caching with | ||
|
|
@@ -24,15 +22,14 @@ | |
| * @param <K> The type of cache key used in this cache | ||
| */ | ||
| class RedisCache<K extends CacheKey> implements Cache<K> { | ||
| private static final Logger logger = LoggerFactory.getLogger(RedisCache.class); | ||
|
|
||
| private final CacheParameter<K> cacheParameter; | ||
| private final ObjectMapper mapper; | ||
|
|
||
| /** | ||
| * Redis client instance. | ||
| */ | ||
| private UnifiedJedis jedis; | ||
| private UnifiedRedisClient jedis; | ||
|
|
||
| /** | ||
| * Creates a new Redis cache instance. | ||
|
|
@@ -51,6 +48,12 @@ class RedisCache<K extends CacheKey> implements Cache<K> { | |
| } | ||
| } | ||
|
|
||
| protected RedisCache(CacheParameter<K> cacheParameter, ObjectMapper mapper, UnifiedRedisClient jedis) { | ||
| this.cacheParameter = Objects.requireNonNull(cacheParameter); | ||
| this.mapper = Objects.requireNonNull(mapper); | ||
| this.jedis = Objects.requireNonNull(jedis); | ||
| } | ||
|
|
||
| @Override | ||
| public void flush() { | ||
| // Redis doesn't require manual flushing | ||
|
|
@@ -71,7 +74,7 @@ private void createRedisConnection() { | |
| if (Environment.getenv("REDIS_URL") != null) { | ||
| redisUrl = Environment.getenv("REDIS_URL"); | ||
| } | ||
| jedis = new UnifiedJedis(redisUrl); | ||
| jedis = new RedisAdapter(RedisClient.create(redisUrl)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rename var |
||
| // Check if connection is working | ||
| jedis.ping(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. check whether this is now a valid check for ping after you use the adapter |
||
| } | ||
|
|
@@ -150,4 +153,8 @@ public synchronized <T> void putViaInternalKey(K key, T value) { | |
| public CacheParameter<K> getCacheParameter() { | ||
| return this.cacheParameter; | ||
| } | ||
|
|
||
| public boolean exists(String key) { | ||
| return jedis.exists(key); | ||
|
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| /* Licensed under MIT 2026. */ | ||
| package edu.kit.kastel.sdq.lissa.ratlr.cache; | ||
|
|
||
| import org.fuchss.restredis.client.Client; | ||
|
|
||
| public class RestRedisAdapter implements UnifiedRedisClient { | ||
|
|
||
| private final Client restRedisClient; | ||
|
|
||
| RestRedisAdapter(Client restRedisClient) { | ||
| this.restRedisClient = restRedisClient; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean ping() { | ||
| return restRedisClient.ping(); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean exists(String key) { | ||
| return restRedisClient.exists(key); | ||
| } | ||
|
|
||
| @Override | ||
| public String hget(String key, String field) { | ||
| return restRedisClient.hget(key, field); | ||
| } | ||
|
|
||
| @Override | ||
| public long hset(String key, String field, String value) { | ||
| return restRedisClient.hset(key, field, value); | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| restRedisClient.close(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| /* Licensed under MIT 2026. */ | ||
| package edu.kit.kastel.sdq.lissa.ratlr.cache; | ||
|
|
||
| import org.fuchss.restredis.client.Client; | ||
| import org.fuchss.restredis.client.ClientConfiguration; | ||
|
|
||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
|
||
| import edu.kit.kastel.sdq.lissa.ratlr.utils.Environment; | ||
|
|
||
| public class RestRedisCache<K extends CacheKey> extends RedisCache<K> { | ||
|
|
||
| /** | ||
| * Creates a new Rest Redis cache instance. | ||
| * This constructor will throw an exception if Rest Redis is unavailable. | ||
| * | ||
| * @param cacheParameter The cache parameter configuration | ||
| * @param mapper The ObjectMapper for JSON operations | ||
| * @throws IllegalArgumentException If Redis connection cannot be established | ||
| */ | ||
| RestRedisCache(CacheParameter<K> cacheParameter, ObjectMapper mapper) { | ||
| super(cacheParameter, mapper, createRedisConnection()); | ||
| } | ||
|
|
||
| private static UnifiedRedisClient createRedisConnection() { | ||
| String restRedisUri = "localhost"; | ||
| if (Environment.getenv("REST_REDIS_URI") != null) { | ||
| restRedisUri = Environment.getenv("REST_REDIS_URI"); | ||
| } | ||
| String restRedisUsername = "admin"; | ||
| if (Environment.getenv("REST_REDIS_USERNAME") != null) { | ||
| restRedisUsername = Environment.getenv("REST_REDIS_USERNAME"); | ||
| } | ||
| String restRedisPassword = "dummy"; | ||
| if (Environment.getenv("REST_REDIS_PASSWORD") != null) { | ||
| restRedisPassword = Environment.getenv("REST_REDIS_PASSWORD"); | ||
| } | ||
|
|
||
| ClientConfiguration config = new ClientConfiguration(restRedisUri, restRedisUsername, restRedisPassword); | ||
| UnifiedRedisClient redis = new RestRedisAdapter(new Client(config)); | ||
|
|
||
| // Check if connection is working | ||
| if (!redis.ping()) { | ||
| throw new IllegalStateException("Could not connect to redis"); | ||
|
Comment on lines
+43
to
+44
|
||
| } | ||
|
|
||
| return redis; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| /* Licensed under MIT 2026. */ | ||
| package edu.kit.kastel.sdq.lissa.ratlr.cache; | ||
|
|
||
| public interface UnifiedRedisClient { | ||
| boolean ping(); | ||
|
|
||
| boolean exists(String key); | ||
|
|
||
| String hget(String key, String field); | ||
|
|
||
| long hset(String key, String field, String value); | ||
|
|
||
| void close(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| /* Licensed under MIT 2026. */ | ||
| package edu.kit.kastel.sdq.lissa.ratlr.cache; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertFalse; | ||
| import static org.junit.jupiter.api.Assertions.assertNull; | ||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
|
||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
|
||
| import edu.kit.kastel.sdq.lissa.ratlr.cache.classifier.ClassifierCacheKey; | ||
| import edu.kit.kastel.sdq.lissa.ratlr.cache.classifier.ClassifierCacheParameter; | ||
|
|
||
| /** | ||
| * Integration test for the REST Redis interface. | ||
| * TODO: maybe testcontainer or something? | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can look into the rest-redis repo for examples with testcontainers |
||
| */ | ||
| public class RestRedisIntegrationTest { | ||
|
|
||
| RestRedisCache<ClassifierCacheKey> restCache; | ||
| private final ClassifierCacheParameter cacheParameter = new ClassifierCacheParameter("test", 1, 0.0); | ||
|
|
||
| @BeforeEach | ||
| public void setup() { | ||
| restCache = new RestRedisCache<>(cacheParameter, new ObjectMapper()); | ||
|
Comment on lines
+27
to
+29
|
||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Test REST Redis client connection") | ||
| void testRestRedisConnection() { | ||
| Cache<ClassifierCacheKey> cache = Cache.createByType( | ||
| "REST_REDIS", new ClassifierCacheParameter("test", 1, 0.0), null, new ObjectMapper()); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Test REST Redis cache set and get") | ||
| void testRestRedisCacheSetAndGet() { | ||
| restCache.put("key", "value"); | ||
| String value = restCache.get("key", String.class); | ||
| assertEquals("value", value); | ||
| String nonExistingValue = restCache.get("ajhosadljhjyhxcjkhljysdhjk", String.class); | ||
| assertNull(nonExistingValue); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("Test if key exists") | ||
| void testExistsMethod() { | ||
| restCache.put("key", "value"); | ||
| String value = restCache.get("key", String.class); | ||
| assertEquals("value", value); | ||
| ClassifierCacheKey cacheKey = cacheParameter.createCacheKey("key"); | ||
| assertTrue(restCache.exists(cacheKey.toJsonKey())); | ||
| assertFalse(restCache.exists("nonExistingKey")); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove jedis (because rest-redis has the jedis dependency already) :)