Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
6245518
feat: encapsulate cache replacement strategies into enum
DanielDango Jan 20, 2026
95ecd2d
feat: add cache initialization logic to EvaluationConfiguration and u…
DanielDango Apr 18, 2026
c34b4e5
feat: make LocalCache implement Cache and revamp CacheReplacementStra…
DanielDango Apr 18, 2026
cc8c041
feat: retrieve cache replacement strategy from ModuleConfiguration
DanielDango Apr 18, 2026
5186194
feat: implement HierarchicalCache for multi-layer caching and update …
DanielDango Apr 19, 2026
b239c14
docs: update caching documentation to include HierarchicalCache
DanielDango Apr 20, 2026
9c804e1
Merge remote-tracking branch 'refs/remotes/upstream/main' into featur…
DanielDango Apr 29, 2026
b4aebe6
feat: support environment-based cache configuration
DanielDango Apr 29, 2026
a515e5f
Merge remote-tracking branch 'upstream/main' into fork/DanielDango/fe…
DanielDango May 4, 2026
2135004
revert: remove module config for caches, instead get cache configurat…
DanielDango May 4, 2026
11aa1bb
test: enhance CacheTest with new entry writing, retrieval, serializat…
DanielDango May 4, 2026
68b5c9e
fix: resolve sonar cube issues
DanielDango May 4, 2026
4cadcf7
refactor: streamline cache manager configuration
DanielDango May 4, 2026
53ebb05
refactor: update cache conflict resolution to replacement strategy te…
DanielDango May 4, 2026
8ca6260
revert: remove unused argumentAsEnum method from ModuleConfiguration
DanielDango May 4, 2026
c1ff184
refactor: enhance cache hierarchy parsing to support quoted strings a…
DanielDango May 4, 2026
0940083
refactor: make default cache configuration identical to previous beha…
DanielDango May 5, 2026
1f4b55e
refactor: improve cache inconsistency handling and resolution methods
DanielDango May 5, 2026
c1db4b5
refactor: mark resolveViaInternalKey method as deprecated with clarif…
DanielDango May 5, 2026
0e00965
feat: add test-specific environment configuration and enforce env ove…
DanielDango May 5, 2026
ae89375
refactor: update documentation for CacheManagers reset usage and remo…
DanielDango May 5, 2026
f5aea19
refactor: add deprecated javadoc annotation and standardize dotenv va…
DanielDango May 5, 2026
574d9fa
refactor: simplify cache creation logic and enforce non-null parameters
DanielDango May 26, 2026
b8cb713
test: added Mockito tests for hierarchical cache behaviour
DanielDango May 26, 2026
63380ff
refactor: introduce helper methods for cache value insertion using ap…
DanielDango May 26, 2026
148c2f7
refactor: replace string-based cache type with enum for improved type…
DanielDango Jun 1, 2026
1cfe0c7
refactor: update cache hierarchy configuration to use enum and enforc…
DanielDango Jun 1, 2026
62b0b0c
refactor: unify json serialization
DanielDango Jun 10, 2026
5d1b624
docs: remove resolved comment
DanielDango Jun 14, 2026
b08093a
feat: implement RestRedisCache and RedisClient for REST-based Redis s…
DanielDango May 26, 2026
87b3d62
test: add hierarchical cache tests for local and REST Redis integration
DanielDango May 27, 2026
a868903
refactor: apply feedback
DanielDango Jun 14, 2026
1bf95e2
docs: add javadoc
DanielDango Jun 14, 2026
65a4181
test: migrate RestRedisTest to testcontainer instead of remote runnin…
DanielDango Jun 17, 2026
d95c0ba
docs: update redis behaviour when unavailable
DanielDango Jun 17, 2026
88b4544
docs: add reference to REST redis cache in documentation
DanielDango Jun 17, 2026
e621170
fix: remove unfitting defaults for rest-redis connection
DanielDango Jun 17, 2026
e0d17be
Potential fix for pull request finding
dfuchss Jun 20, 2026
9c85916
Update dependencies
dfuchss Jun 20, 2026
c2b321d
Fix comments
dfuchss Jun 20, 2026
d9e9c82
docs: fix old references to fallback behaviour if redis is unavailable
DanielDango Jun 23, 2026
ba0ac7a
fix: address minor copilot feedback
DanielDango Jun 23, 2026
62281fd
Potential fix for pull request finding
dfuchss Jun 24, 2026
5ed61d9
refactor: change access modifiers to package-private for Redis-relate…
DanielDango Jul 15, 2026
a5b2278
Potential fix for pull request finding
dfuchss Jul 15, 2026
8bb72e5
Fix copilot fix
dfuchss Jul 15, 2026
165cfc9
Merge remote-tracking branch 'DanielDango/feature/add-cache-configura…
dfuchss Jul 15, 2026
0cb600d
Merge branch 'main' into feature/add-rest-redis-cache
dfuchss Jul 15, 2026
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
9 changes: 8 additions & 1 deletion docs/caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ LiSSA implements a sophisticated caching system to improve performance and ensur
- [`RedisCache`](../src/main/java/edu/kit/kastel/sdq/lissa/ratlr/cache/RedisCache.java): Redis-based cache implementation
- Uses Redis for high-performance caching
- Supports both string and object serialization
- [`RestRedisCache`](../src/main/java/edu/kit/kastel/sdq/lissa/ratlr/cache/RestRedisCache.java): REST-based Redis cache implementation
Comment thread
DanielDango marked this conversation as resolved.
- Uses REST API to interact with Redis server
- Provides an alternative to direct Redis connections, useful for shared caches
- Configuration through environment variables, see [Usage Instructions](#usage-instructions)
3. **Cache Management**
- [`CacheManager`](../src/main/java/edu/kit/kastel/sdq/lissa/ratlr/cache/CacheManager.java): Central manager for cache instances
- Manages cache directory configuration
Expand Down Expand Up @@ -99,13 +103,16 @@ The `Cache` interface provides two API levels:
The caching system supports the following environment variables:
- **CACHE_HIERARCHY**: Comma-separated list of cache types in order (e.g., "LOCAL,REDIS")
- Default: "LOCAL"
- Supported values: "LOCAL", "REDIS"
- Supported values: "LOCAL", "REDIS", "REST_REDIS"
- **CACHE_REPLACEMENT_STRATEGY**: Strategy for handling conflicts between cache layers
- Default: "NONE"
- Supported values: "NONE", "ERROR", "OVERWRITE"
- **REDIS_URL**: Redis connection URL for RedisCache
- Default: "redis://localhost:6379"
- Example: "redis://redis-server:6379"
- **REST_REDIS_URI**: URI for REST Redis server (if using REST_REDIS cache type)
- **REST_REDIS_USERNAME**: Username for REST Redis authentication
- **REST_REDIS_PASSWORD**: Password for REST Redis authentication

3. **Redis Setup**
To use Redis for caching, you need to set up a Redis server. Here's a recommended Docker Compose configuration:
Expand Down
2 changes: 1 addition & 1 deletion env-template
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ OPENAI_API_KEY=
# LOCAL - only local file-based cache
# LOCAL,REDIS - local cache as primary, Redis as secondary fallback
# REDIS,LOCAL - Redis as primary, local cache as secondary fallback
# Valid cache types: LOCAL, REDIS
# Valid cache types: LOCAL, REDIS, REST_REDIS
#CACHE_HIERARCHY=REDIS,LOCAL

23 changes: 22 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>2.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Expand All @@ -50,7 +57,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 +120,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>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
Expand All @@ -134,6 +145,16 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,13 @@ public interface Cache<K extends CacheKey> {
* Factory method to create a cache instance by type name.
* Supported types:
* <ul>
* <li>"local" - LocalCache for file-based storage</li>
* <li>"redis" - RedisCache for Redis-based storage</li>
* <li>{@link CacheType#LOCAL} - file-based storage</li>
* <li>{@link CacheType#REDIS} - Redis-based storage</li>
* <li>{@link CacheType#REST_REDIS} - REST-based Redis storage</li>
* </ul>
*
* @see CacheType
*
* @param <K> The type of cache key
* @param type The cache type name (case-insensitive)
* @param cacheDir The directory for local cache storage
Expand All @@ -137,6 +140,7 @@ static <K extends CacheKey> Cache<K> createByType(
return switch (type) {
case LOCAL -> new LocalCache<>(cacheDir, parameters);
case REDIS -> new RedisCache<>(parameters, mapper);
case REST_REDIS -> new RestRedisCache<>(parameters, mapper);
};
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
/* Licensed under MIT 2026. */
package edu.kit.kastel.sdq.lissa.ratlr.cache;

/**
* Enum representing the types of caches supported by the system.
*/
public enum CacheType {
/**
* File based local cache
*/
LOCAL,
/**
* Redis based local docker container for caching
*/
REDIS,
/**
* Remote Redis instance accessible via a REST API
*/
REST_REDIS
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* Licensed under MIT 2026. */
package edu.kit.kastel.sdq.lissa.ratlr.cache;

import java.util.Objects;

import redis.clients.jedis.UnifiedJedis;

/**
* Adapter class that wraps a Jedis client to conform to the UnifiedRedisClient interface.
*/
/*package-private*/ class RedisAdapter implements UnifiedRedisClient {

private final UnifiedJedis jedis;

/**
* Creates a new RedisAdapter instance with the given Jedis client.
*
* @param jedis The Jedis client to wrap
*/
/*package-private*/ RedisAdapter(UnifiedJedis jedis) {
this.jedis = Objects.requireNonNull(jedis);
}

/**
* Pings the Redis server to check if it is available.
*
* @return true if the server responds, false otherwise.
*/
@Override
public boolean ping() {
return jedis.ping().equals("PONG");
}
Comment on lines +29 to +32

/**
* Checks if a key exists in the Redis cache.
*
* @param key the key to check for existence
* @return true if the key exists, false otherwise.
*/
@Override
public boolean exists(String key) {
return jedis.exists(key);
}

/**
* Retrieves the value of a field in a hash stored at key.
*
* @param key The key of the hash
* @param field The field whose value is to be retrieved
* @return Value for the field. If the key or field does not exist, null is returned.
*/
@Override
public String hget(String key, String field) {
return jedis.hget(key, field);
}

/**
* Sets the value of a field in a hash stored at a key
*
* @param key The key of the hash
* @param field The field whose value is to be set
* @param value The value to be set
* @return The number of added fields
*/
@Override
public long hset(String key, String field, String value) {
return jedis.hset(key, field, value);
}

/**
* Shuts down the connection to the jedis instance.
*/
@Override
public void close() {
jedis.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import edu.kit.kastel.sdq.lissa.ratlr.utils.Environment;

import redis.clients.jedis.RedisClient;
import redis.clients.jedis.UnifiedJedis;

/**
* Implements a Redis-based cache for storing and retrieving values. For multi-layer caching with
Expand All @@ -30,23 +29,31 @@ class RedisCache<K extends CacheKey> implements Cache<K> {
/**
* Redis client instance.
*/
private UnifiedJedis jedis;
private final UnifiedRedisClient redis;

/**
* Creates a new Redis cache instance.
* This constructor will throw an exception if Redis is unavailable.
*
* @param cacheParameter The cache parameter configuration
* @param mapper The ObjectMapper for JSON operations
* @throws IllegalArgumentException If Redis connection cannot be established
* @throws IllegalStateException If Redis connection cannot be established
*/
RedisCache(CacheParameter<K> cacheParameter, ObjectMapper mapper) {
this(cacheParameter, mapper, createRedisConnection());
}
Comment thread
DanielDango marked this conversation as resolved.

/**
* Creates a Redis Cache instance with a custom redis connection
*
* @param cacheParameter The cache parameter configuration
* @param mapper The ObjectMapper for JSON operations
* @param redis The connected redis instance
*/
protected RedisCache(CacheParameter<K> cacheParameter, ObjectMapper mapper, UnifiedRedisClient redis) {
this.cacheParameter = Objects.requireNonNull(cacheParameter);
this.mapper = Objects.requireNonNull(mapper);
createRedisConnection();
if (jedis == null) {
throw new IllegalArgumentException("Could not connect to Redis");
}
this.redis = Objects.requireNonNull(redis);
}

@Override
Expand All @@ -57,21 +64,27 @@ public void flush() {
@Override
public boolean containsKey(String key) {
K cacheKey = cacheParameter.createCacheKey(key);
return jedis.exists(cacheKey.toJsonKey());
return redis.exists(cacheKey.toJsonKey());
}

/**
* Establishes a connection to the Redis server.
* The Redis URL can be configured through the REDIS_URL environment variable.
*
* @throws IllegalStateException if Redis connection could not be established
*/
private void createRedisConnection() {
private static RedisAdapter createRedisConnection() {
String redisUrl = "redis://localhost:6379";
if (Environment.getenv("REDIS_URL") != null) {
redisUrl = Environment.getenv("REDIS_URL");
}
jedis = RedisClient.create(redisUrl);
RedisAdapter redis = new RedisAdapter(RedisClient.create(redisUrl));
Comment thread
DanielDango marked this conversation as resolved.
// Check if connection is working
jedis.ping();
if (!redis.ping()) {
redis.close();
throw new IllegalStateException("Could not connect to Redis. Make sure the container is up and running.");
}
return redis;
}

/**
Expand All @@ -85,14 +98,14 @@ private void createRedisConnection() {
@Override
public synchronized <T> @Nullable T get(String key, Class<T> clazz) {
K cacheKey = cacheParameter.createCacheKey(key);
String jsonData = jedis.hget(cacheKey.toJsonKey(), "data");
String jsonData = redis.hget(cacheKey.toJsonKey(), "data");
return Cache.convert(jsonData, clazz, mapper);
}

@Override
@SuppressWarnings("deprecation")
public synchronized <T> @Nullable T getViaInternalKey(K cacheKey, Class<T> clazz) {
String jsonData = jedis.hget(cacheKey.toJsonKey(), "data");
String jsonData = redis.hget(cacheKey.toJsonKey(), "data");
return Cache.convert(jsonData, clazz, mapper);
}

Expand All @@ -107,8 +120,8 @@ private void createRedisConnection() {
public synchronized void put(String key, String value) {
K cacheKey = cacheParameter.createCacheKey(key);
String jsonKey = cacheKey.toJsonKey();
jedis.hset(jsonKey, "data", value);
jedis.hset(jsonKey, "timestamp", String.valueOf(Instant.now().getEpochSecond()));
redis.hset(jsonKey, "data", value);
redis.hset(jsonKey, "timestamp", String.valueOf(Instant.now().getEpochSecond()));
}

/**
Expand Down Expand Up @@ -140,8 +153,8 @@ public synchronized <T> void putViaInternalKey(K key, T value) {
throw new IllegalArgumentException("Could not serialize object", e);
}
String jsonKey = key.toJsonKey();
jedis.hset(jsonKey, "data", data);
jedis.hset(jsonKey, "timestamp", String.valueOf(Instant.now().getEpochSecond()));
redis.hset(jsonKey, "data", data);
redis.hset(jsonKey, "timestamp", String.valueOf(Instant.now().getEpochSecond()));
}

@Override
Expand Down
Loading
Loading