Skip to content
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
6 changes: 5 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
<artifactId>jtokkit</artifactId>
<version>1.1.0</version>
</dependency>

<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
Expand Down Expand Up @@ -114,6 +113,11 @@
<version>${record-builder.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.fuchss</groupId>
<artifactId>rest-redis</artifactId>
<version>0.1.4</version>
</dependency>
Comment on lines +116 to +120
Copy link
Copy Markdown

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

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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");
Expand All @@ -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");
};
Expand Down
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();
}
}
19 changes: 13 additions & 6 deletions src/main/java/edu/kit/kastel/sdq/lissa/ratlr/cache/RedisCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename var

// Check if connection is working
jedis.ping();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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

}
Expand Down Expand Up @@ -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?
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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"));
}
}