com.github.ben-manes.caffeine
diff --git a/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/CoreManager.java b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/CoreManager.java
index 93f9912..97e110e 100644
--- a/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/CoreManager.java
+++ b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/CoreManager.java
@@ -5,8 +5,10 @@
import com.astroid.stijnjakobs.networkdataapi.core.config.ConfigurationManager;
import com.astroid.stijnjakobs.networkdataapi.core.database.DatabaseManager;
import com.astroid.stijnjakobs.networkdataapi.core.environment.EnvironmentDetector;
+import com.astroid.stijnjakobs.networkdataapi.core.redis.RedisManager;
import com.astroid.stijnjakobs.networkdataapi.core.rest.RESTApiService;
import com.astroid.stijnjakobs.networkdataapi.core.service.PlayerDataService;
+import com.astroid.stijnjakobs.networkdataapi.core.service.RedisDataService;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,9 +46,11 @@ public class CoreManager {
private ConfigurationManager configurationManager;
private DatabaseManager databaseManager;
+ private RedisManager redisManager;
private CacheManager cacheManager;
private AsyncExecutor asyncExecutor;
private PlayerDataService playerDataService;
+ private RedisDataService redisDataService;
private RESTApiService restApiService;
private boolean initialized = false;
@@ -90,6 +94,19 @@ public void initialize(File dataFolder) throws Exception {
databaseManager = new DatabaseManager(configurationManager);
databaseManager.connect();
+ // Initialize Redis manager (if enabled)
+ if (configurationManager.getBoolean("redis.enabled", false)) {
+ logger.info("Connecting to Redis...");
+ redisManager = new RedisManager(configurationManager);
+ redisManager.connect();
+
+ // Initialize Redis data service
+ logger.info("Initializing Redis data service...");
+ redisDataService = new RedisDataService(redisManager, asyncExecutor);
+ } else {
+ logger.info("Redis is disabled in configuration");
+ }
+
// Initialize services
logger.info("Initializing player data service...");
playerDataService = new PlayerDataService(databaseManager, cacheManager, asyncExecutor);
@@ -132,6 +149,15 @@ private void scheduleMaintenanceTasks() {
databaseManager.reconnect();
}
}, 1, TimeUnit.MINUTES);
+
+ // Redis health check every minute (if enabled)
+ if (redisManager != null) {
+ asyncExecutor.schedule(() -> {
+ if (!redisManager.isAlive()) {
+ logger.warn("Redis health check failed");
+ }
+ }, 1, TimeUnit.MINUTES);
+ }
}
/**
@@ -163,7 +189,16 @@ public void shutdown() {
try {
databaseManager.shutdown();
} catch (Exception e) {
- logger.error("Error closing database connection", e);
+ logger.error("Error shutting down database", e);
+ }
+ }
+
+ // Close Redis connection
+ if (redisManager != null) {
+ try {
+ redisManager.shutdown();
+ } catch (Exception e) {
+ logger.error("Error shutting down Redis", e);
}
}
@@ -188,5 +223,77 @@ public void shutdown() {
public boolean isInitialized() {
return initialized;
}
+
+ /**
+ * Gets the configuration manager.
+ *
+ * @return the configuration manager
+ */
+ public ConfigurationManager getConfigurationManager() {
+ return configurationManager;
+ }
+
+ /**
+ * Gets the database manager.
+ *
+ * @return the database manager
+ */
+ public DatabaseManager getDatabaseManager() {
+ return databaseManager;
+ }
+
+ /**
+ * Gets the Redis manager.
+ *
+ * @return the Redis manager
+ */
+ public RedisManager getRedisManager() {
+ return redisManager;
+ }
+
+ /**
+ * Gets the cache manager.
+ *
+ * @return the cache manager
+ */
+ public CacheManager getCacheManager() {
+ return cacheManager;
+ }
+
+ /**
+ * Gets the async executor.
+ *
+ * @return the async executor
+ */
+ public AsyncExecutor getAsyncExecutor() {
+ return asyncExecutor;
+ }
+
+ /**
+ * Gets the player data service.
+ *
+ * @return the player data service
+ */
+ public PlayerDataService getPlayerDataService() {
+ return playerDataService;
+ }
+
+ /**
+ * Gets the Redis data service.
+ *
+ * @return the Redis data service
+ */
+ public RedisDataService getRedisDataService() {
+ return redisDataService;
+ }
+
+ /**
+ * Gets the REST API service.
+ *
+ * @return the REST API service
+ */
+ public RESTApiService getRestApiService() {
+ return restApiService;
+ }
}
diff --git a/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/api/APIRegistry.java b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/api/APIRegistry.java
index b89be8b..8de86bb 100644
--- a/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/api/APIRegistry.java
+++ b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/api/APIRegistry.java
@@ -2,6 +2,8 @@
import com.astroid.stijnjakobs.networkdataapi.core.CoreManager;
import com.astroid.stijnjakobs.networkdataapi.core.service.PlayerDataService;
+import com.astroid.stijnjakobs.networkdataapi.core.service.RedisDataService;
+import redis.clients.jedis.JedisPool;
/**
* Public API registry for accessing NetworkDataAPI services.
@@ -107,5 +109,24 @@ public String getVersion() {
public boolean isHealthy() {
return coreManager.getDatabaseManager().isHealthy();
}
+
+ @Override
+ public RedisDataService getRedisDataService() {
+ return coreManager.getRedisDataService();
+ }
+
+ @Override
+ public JedisPool getRedisPool() {
+ if (coreManager.getRedisManager() == null) {
+ return null;
+ }
+ return coreManager.getRedisManager().getJedisPool();
+ }
+
+ @Override
+ public boolean isRedisEnabled() {
+ return coreManager.getRedisManager() != null &&
+ coreManager.getRedisManager().isConnected();
+ }
}
}
diff --git a/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/api/NetworkDataAPIProvider.java b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/api/NetworkDataAPIProvider.java
index 6a8fed6..147c74f 100644
--- a/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/api/NetworkDataAPIProvider.java
+++ b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/api/NetworkDataAPIProvider.java
@@ -1,7 +1,9 @@
package com.astroid.stijnjakobs.networkdataapi.core.api;
import com.astroid.stijnjakobs.networkdataapi.core.service.PlayerDataService;
+import com.astroid.stijnjakobs.networkdataapi.core.service.RedisDataService;
import com.mongodb.client.MongoDatabase;
+import redis.clients.jedis.JedisPool;
/**
* Public API interface for NetworkDataAPI.
@@ -139,4 +141,76 @@ public interface NetworkDataAPIProvider {
* @return true if healthy, false otherwise
*/
boolean isHealthy();
+
+ /**
+ * Gets the Redis data service for caching and messaging.
+ *
+ * The Redis data service provides methods for:
+ *
+ * - String operations (get, set, with TTL)
+ * - Hash operations (field-value storage)
+ * - Set operations (unique members)
+ * - List operations (ordered data)
+ * - Pub/Sub messaging
+ * - Counter operations
+ *
+ *
+ * Example - Caching player data:
+ * {@code
+ * RedisDataService redis = api.getRedisDataService();
+ *
+ * // Cache with 5 minute TTL
+ * redis.setWithExpiry("player:" + uuid, playerData, 300);
+ *
+ * // Retrieve cached data
+ * String data = redis.get("player:" + uuid);
+ * }
+ *
+ * Example - Pub/Sub messaging:
+ * {@code
+ * // Publish to other servers
+ * redis.publish("player-join", uuid.toString());
+ * }
+ *
+ * @return the Redis data service, or null if Redis is disabled
+ */
+ RedisDataService getRedisDataService();
+
+ /**
+ * Gets direct access to the Redis connection pool.
+ *
+ * This allows plugins to use the shared Redis connection pool
+ * for custom operations not covered by RedisDataService.
+ *
+ * Example - Custom Redis operations:
+ * {@code
+ * JedisPool pool = api.getRedisPool();
+ * try (Jedis jedis = pool.getResource()) {
+ * // Custom Redis commands
+ * jedis.zadd("leaderboard", 1000, "player1");
+ * Set top10 = jedis.zrevrange("leaderboard", 0, 9);
+ * }
+ * }
+ *
+ * Benefits:
+ *
+ * - No separate Redis connection needed
+ * - Uses shared connection pool (efficient)
+ * - Automatic reconnection
+ * - Full Jedis API access
+ *
+ *
+ * Important: Always use try-with-resources to ensure
+ * connections are returned to the pool!
+ *
+ * @return the Jedis pool, or null if Redis is disabled
+ */
+ JedisPool getRedisPool();
+
+ /**
+ * Checks if Redis is enabled and connected.
+ *
+ * @return true if Redis is available
+ */
+ boolean isRedisEnabled();
}
diff --git a/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/config/ConfigurationManager.java b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/config/ConfigurationManager.java
index eeba35a..bcc8923 100644
--- a/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/config/ConfigurationManager.java
+++ b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/config/ConfigurationManager.java
@@ -71,6 +71,26 @@ private void createDefaultConfig() throws IOException {
max-connection-idle-time-ms: 60000
max-connection-life-time-ms: 600000
+ # Redis Connection Settings
+ redis:
+ enabled: false
+ host: "localhost"
+ port: 6379
+ password: ""
+ database: 0
+ timeout-ms: 2000
+ # Connection pool settings
+ max-pool-size: 100
+ max-idle: 50
+ min-idle: 10
+ test-on-borrow: true
+ test-on-return: false
+ test-while-idle: true
+ min-evictable-idle-time-ms: 60000
+ time-between-eviction-runs-ms: 30000
+ block-when-exhausted: true
+ max-wait-ms: 3000
+
# Cache Settings
cache:
enabled: true
diff --git a/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/database/DatabaseManager.java b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/database/DatabaseManager.java
index b985fec..064d52b 100644
--- a/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/database/DatabaseManager.java
+++ b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/database/DatabaseManager.java
@@ -41,10 +41,8 @@ public class DatabaseManager {
private MongoClient mongoClient;
- @Getter
private MongoDatabase database;
- @Getter
private boolean connected = false;
private final ConfigurationManager config;
@@ -58,6 +56,24 @@ public DatabaseManager(ConfigurationManager config) {
this.config = config;
}
+ /**
+ * Gets the MongoDB database instance.
+ *
+ * @return the MongoDatabase instance
+ */
+ public MongoDatabase getDatabase() {
+ return database;
+ }
+
+ /**
+ * Checks if the database is connected.
+ *
+ * @return true if connected, false otherwise
+ */
+ public boolean isConnected() {
+ return connected;
+ }
+
/**
* Initializes the database connection.
*
diff --git a/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/redis/RedisManager.java b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/redis/RedisManager.java
new file mode 100644
index 0000000..a626667
--- /dev/null
+++ b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/redis/RedisManager.java
@@ -0,0 +1,208 @@
+package com.astroid.stijnjakobs.networkdataapi.core.redis;
+
+import com.astroid.stijnjakobs.networkdataapi.core.config.ConfigurationManager;
+import lombok.Getter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPoolConfig;
+import redis.clients.jedis.Protocol;
+
+import java.time.Duration;
+
+/**
+ * Manages Redis connection and pool access.
+ *
+ * This class handles the lifecycle of the Redis client, including:
+ *
+ * - Connection pool configuration
+ * - Automatic reconnection and retry logic
+ * - Graceful shutdown
+ * - Thread-safe pool access
+ *
+ *
+ * Thread Safety: This class is thread-safe. Jedis pool
+ * handles connection pooling and thread safety internally.
+ *
+ * Resource Management: Call {@link #shutdown()} to properly
+ * close connections before application termination.
+ *
+ * @author Stijn Jakobs
+ * @version 1.0
+ * @since 1.0
+ */
+public class RedisManager {
+
+ private static final Logger logger = LoggerFactory.getLogger(RedisManager.class);
+
+ private JedisPool jedisPool;
+
+ private boolean connected = false;
+
+ private final ConfigurationManager config;
+
+ /**
+ * Creates a new Redis manager.
+ *
+ * @param config the configuration manager
+ */
+ public RedisManager(ConfigurationManager config) {
+ this.config = config;
+ }
+
+ /**
+ * Gets the Jedis pool.
+ *
+ * @return the JedisPool instance
+ */
+ public JedisPool getJedisPool() {
+ return jedisPool;
+ }
+
+ /**
+ * Checks if Redis is connected.
+ *
+ * @return true if connected, false otherwise
+ */
+ public boolean isConnected() {
+ return connected;
+ }
+
+ /**
+ * Initializes the Redis connection pool.
+ *
+ * This method configures the Jedis pool with connection pooling,
+ * timeouts, and other settings from the configuration file.
+ *
+ * @throws Exception if connection fails
+ */
+ public void connect() throws Exception {
+ logger.info("Initializing Redis connection...");
+
+ try {
+ // Get connection settings
+ String host = config.getString("redis.host", "localhost");
+ int port = config.getInt("redis.port", 6379);
+ String password = config.getString("redis.password", "");
+ int database = config.getInt("redis.database", 0);
+ int timeout = config.getInt("redis.timeout-ms", 2000);
+
+ // Configure pool settings
+ JedisPoolConfig poolConfig = new JedisPoolConfig();
+ poolConfig.setMaxTotal(config.getInt("redis.max-pool-size", 100));
+ poolConfig.setMaxIdle(config.getInt("redis.max-idle", 50));
+ poolConfig.setMinIdle(config.getInt("redis.min-idle", 10));
+ poolConfig.setTestOnBorrow(config.getBoolean("redis.test-on-borrow", true));
+ poolConfig.setTestOnReturn(config.getBoolean("redis.test-on-return", false));
+ poolConfig.setTestWhileIdle(config.getBoolean("redis.test-while-idle", true));
+ poolConfig.setMinEvictableIdleTime(Duration.ofMillis(
+ config.getLong("redis.min-evictable-idle-time-ms", 60000)));
+ poolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(
+ config.getLong("redis.time-between-eviction-runs-ms", 30000)));
+ poolConfig.setBlockWhenExhausted(config.getBoolean("redis.block-when-exhausted", true));
+ poolConfig.setMaxWait(Duration.ofMillis(
+ config.getLong("redis.max-wait-ms", 3000)));
+
+ // Create the pool
+ if (password != null && !password.isEmpty()) {
+ jedisPool = new JedisPool(poolConfig, host, port, timeout, password, database);
+ } else {
+ jedisPool = new JedisPool(poolConfig, host, port, timeout, null, database);
+ }
+
+ // Test connection
+ try (Jedis jedis = jedisPool.getResource()) {
+ String pong = jedis.ping();
+ if (!"PONG".equals(pong)) {
+ throw new Exception("Failed to ping Redis server");
+ }
+ }
+
+ connected = true;
+ logger.info("Successfully connected to Redis at {}:{} (database: {})", host, port, database);
+ logger.info("Redis pool configured with max size: {}, max idle: {}, min idle: {}",
+ poolConfig.getMaxTotal(), poolConfig.getMaxIdle(), poolConfig.getMinIdle());
+
+ } catch (Exception e) {
+ connected = false;
+ logger.error("Failed to connect to Redis", e);
+ throw new Exception("Redis connection failed: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Gets a Redis connection from the pool.
+ *
+ * Important: The returned Jedis instance must be closed
+ * after use to return it to the pool. Use try-with-resources:
+ * {@code
+ * try (Jedis jedis = redisManager.getResource()) {
+ * jedis.set("key", "value");
+ * }
+ * }
+ *
+ * @return a Jedis instance from the pool
+ * @throws IllegalStateException if not connected
+ */
+ public Jedis getResource() {
+ if (!connected || jedisPool == null) {
+ throw new IllegalStateException("Redis is not connected");
+ }
+ return jedisPool.getResource();
+ }
+
+ /**
+ * Gracefully shuts down the Redis connection pool.
+ *
+ * This method closes all connections in the pool and releases resources.
+ * It should be called during plugin shutdown.
+ */
+ public void shutdown() {
+ if (jedisPool != null) {
+ logger.info("Closing Redis connection pool...");
+ try {
+ jedisPool.close();
+ connected = false;
+ logger.info("Redis connection pool closed successfully");
+ } catch (Exception e) {
+ logger.error("Error while closing Redis connection pool", e);
+ }
+ }
+ }
+
+ /**
+ * Checks if the Redis connection is alive.
+ *
+ * @return true if connected and responsive
+ */
+ public boolean isAlive() {
+ if (!connected || jedisPool == null) {
+ return false;
+ }
+
+ try (Jedis jedis = jedisPool.getResource()) {
+ return "PONG".equals(jedis.ping());
+ } catch (Exception e) {
+ logger.warn("Redis health check failed", e);
+ return false;
+ }
+ }
+
+ /**
+ * Gets pool statistics for monitoring.
+ *
+ * @return a string with pool statistics
+ */
+ public String getPoolStats() {
+ if (jedisPool == null) {
+ return "Pool not initialized";
+ }
+
+ return String.format("Active: %d, Idle: %d, Waiting: %d",
+ jedisPool.getNumActive(),
+ jedisPool.getNumIdle(),
+ jedisPool.getNumWaiters());
+ }
+}
+
diff --git a/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/service/RedisDataService.java b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/service/RedisDataService.java
new file mode 100644
index 0000000..4b0efed
--- /dev/null
+++ b/networkdataapi-core/src/main/java/com/astroid/stijnjakobs/networkdataapi/core/service/RedisDataService.java
@@ -0,0 +1,563 @@
+package com.astroid.stijnjakobs.networkdataapi.core.service;
+
+import com.astroid.stijnjakobs.networkdataapi.core.async.AsyncExecutor;
+import com.astroid.stijnjakobs.networkdataapi.core.redis.RedisManager;
+import lombok.RequiredArgsConstructor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.params.SetParams;
+
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Service for managing Redis data operations.
+ *
+ * This service provides high-level methods for interacting with Redis,
+ * including string operations, hash operations, set operations, and more.
+ *
+ * Features:
+ *
+ * - Thread-safe operations
+ * - Async variants for all operations
+ * - Automatic connection handling
+ * - Comprehensive error handling
+ * - Support for TTL and expiration
+ *
+ *
+ * @author Stijn Jakobs
+ * @version 1.0
+ * @since 1.0
+ */
+public class RedisDataService {
+
+ private static final Logger logger = LoggerFactory.getLogger(RedisDataService.class);
+
+ private final RedisManager redisManager;
+ private final AsyncExecutor asyncExecutor;
+
+ /**
+ * Creates a new Redis data service.
+ *
+ * @param redisManager the Redis manager
+ * @param asyncExecutor the async executor
+ */
+ public RedisDataService(RedisManager redisManager, AsyncExecutor asyncExecutor) {
+ this.redisManager = redisManager;
+ this.asyncExecutor = asyncExecutor;
+ }
+
+ // ========== String Operations ==========
+
+ /**
+ * Sets a string value in Redis.
+ *
+ * @param key the key
+ * @param value the value
+ */
+ public void set(String key, String value) {
+ try (Jedis jedis = redisManager.getResource()) {
+ jedis.set(key, value);
+ } catch (Exception e) {
+ logger.error("Failed to set key: {}", key, e);
+ }
+ }
+
+ /**
+ * Sets a string value with expiration in Redis.
+ *
+ * @param key the key
+ * @param value the value
+ * @param ttlSeconds time to live in seconds
+ */
+ public void setWithExpiry(String key, String value, long ttlSeconds) {
+ try (Jedis jedis = redisManager.getResource()) {
+ jedis.setex(key, ttlSeconds, value);
+ } catch (Exception e) {
+ logger.error("Failed to set key with expiry: {}", key, e);
+ }
+ }
+
+ /**
+ * Gets a string value from Redis.
+ *
+ * @param key the key
+ * @return the value, or null if not found
+ */
+ public String get(String key) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.get(key);
+ } catch (Exception e) {
+ logger.error("Failed to get key: {}", key, e);
+ return null;
+ }
+ }
+
+ /**
+ * Sets a string value asynchronously.
+ *
+ * @param key the key
+ * @param value the value
+ * @return a CompletableFuture that completes when the operation is done
+ */
+ public CompletableFuture setAsync(String key, String value) {
+ return asyncExecutor.supply(() -> {
+ set(key, value);
+ return null;
+ });
+ }
+
+ /**
+ * Gets a string value asynchronously.
+ *
+ * @param key the key
+ * @return a CompletableFuture with the value
+ */
+ public CompletableFuture getAsync(String key) {
+ return asyncExecutor.supply(() -> get(key));
+ }
+
+ /**
+ * Deletes one or more keys.
+ *
+ * @param keys the keys to delete
+ * @return the number of keys deleted
+ */
+ public long delete(String... keys) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.del(keys);
+ } catch (Exception e) {
+ logger.error("Failed to delete keys: {}", Arrays.toString(keys), e);
+ return 0;
+ }
+ }
+
+ /**
+ * Checks if a key exists.
+ *
+ * @param key the key
+ * @return true if the key exists
+ */
+ public boolean exists(String key) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.exists(key);
+ } catch (Exception e) {
+ logger.error("Failed to check key existence: {}", key, e);
+ return false;
+ }
+ }
+
+ /**
+ * Sets the expiration for a key.
+ *
+ * @param key the key
+ * @param seconds time to live in seconds
+ * @return true if successful
+ */
+ public boolean expire(String key, long seconds) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.expire(key, seconds) == 1;
+ } catch (Exception e) {
+ logger.error("Failed to set expiration for key: {}", key, e);
+ return false;
+ }
+ }
+
+ /**
+ * Gets the time to live for a key.
+ *
+ * @param key the key
+ * @return the TTL in seconds, -1 if no expiry, -2 if key doesn't exist
+ */
+ public long getTTL(String key) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.ttl(key);
+ } catch (Exception e) {
+ logger.error("Failed to get TTL for key: {}", key, e);
+ return -2;
+ }
+ }
+
+ // ========== Hash Operations ==========
+
+ /**
+ * Sets a field in a hash.
+ *
+ * @param key the hash key
+ * @param field the field name
+ * @param value the field value
+ */
+ public void hset(String key, String field, String value) {
+ try (Jedis jedis = redisManager.getResource()) {
+ jedis.hset(key, field, value);
+ } catch (Exception e) {
+ logger.error("Failed to set hash field: {} -> {}", key, field, e);
+ }
+ }
+
+ /**
+ * Gets a field from a hash.
+ *
+ * @param key the hash key
+ * @param field the field name
+ * @return the field value, or null if not found
+ */
+ public String hget(String key, String field) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.hget(key, field);
+ } catch (Exception e) {
+ logger.error("Failed to get hash field: {} -> {}", key, field, e);
+ return null;
+ }
+ }
+
+ /**
+ * Sets multiple fields in a hash.
+ *
+ * @param key the hash key
+ * @param hash the map of field-value pairs
+ */
+ public void hmset(String key, Map hash) {
+ try (Jedis jedis = redisManager.getResource()) {
+ jedis.hmset(key, hash);
+ } catch (Exception e) {
+ logger.error("Failed to set hash fields: {}", key, e);
+ }
+ }
+
+ /**
+ * Gets all fields and values from a hash.
+ *
+ * @param key the hash key
+ * @return a map of field-value pairs
+ */
+ public Map hgetAll(String key) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.hgetAll(key);
+ } catch (Exception e) {
+ logger.error("Failed to get all hash fields: {}", key, e);
+ return Collections.emptyMap();
+ }
+ }
+
+ /**
+ * Deletes one or more fields from a hash.
+ *
+ * @param key the hash key
+ * @param fields the fields to delete
+ * @return the number of fields deleted
+ */
+ public long hdel(String key, String... fields) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.hdel(key, fields);
+ } catch (Exception e) {
+ logger.error("Failed to delete hash fields: {}", key, e);
+ return 0;
+ }
+ }
+
+ /**
+ * Checks if a field exists in a hash.
+ *
+ * @param key the hash key
+ * @param field the field name
+ * @return true if the field exists
+ */
+ public boolean hexists(String key, String field) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.hexists(key, field);
+ } catch (Exception e) {
+ logger.error("Failed to check hash field existence: {} -> {}", key, field, e);
+ return false;
+ }
+ }
+
+ /**
+ * Increments a hash field by a value.
+ *
+ * @param key the hash key
+ * @param field the field name
+ * @param increment the increment value
+ * @return the new value after incrementing
+ */
+ public long hincrBy(String key, String field, long increment) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.hincrBy(key, field, increment);
+ } catch (Exception e) {
+ logger.error("Failed to increment hash field: {} -> {}", key, field, e);
+ return 0;
+ }
+ }
+
+ // ========== Set Operations ==========
+
+ /**
+ * Adds one or more members to a set.
+ *
+ * @param key the set key
+ * @param members the members to add
+ * @return the number of members added
+ */
+ public long sadd(String key, String... members) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.sadd(key, members);
+ } catch (Exception e) {
+ logger.error("Failed to add to set: {}", key, e);
+ return 0;
+ }
+ }
+
+ /**
+ * Removes one or more members from a set.
+ *
+ * @param key the set key
+ * @param members the members to remove
+ * @return the number of members removed
+ */
+ public long srem(String key, String... members) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.srem(key, members);
+ } catch (Exception e) {
+ logger.error("Failed to remove from set: {}", key, e);
+ return 0;
+ }
+ }
+
+ /**
+ * Gets all members of a set.
+ *
+ * @param key the set key
+ * @return a set of all members
+ */
+ public Set smembers(String key) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.smembers(key);
+ } catch (Exception e) {
+ logger.error("Failed to get set members: {}", key, e);
+ return Collections.emptySet();
+ }
+ }
+
+ /**
+ * Checks if a member exists in a set.
+ *
+ * @param key the set key
+ * @param member the member to check
+ * @return true if the member exists
+ */
+ public boolean sismember(String key, String member) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.sismember(key, member);
+ } catch (Exception e) {
+ logger.error("Failed to check set membership: {}", key, e);
+ return false;
+ }
+ }
+
+ /**
+ * Gets the number of members in a set.
+ *
+ * @param key the set key
+ * @return the number of members
+ */
+ public long scard(String key) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.scard(key);
+ } catch (Exception e) {
+ logger.error("Failed to get set cardinality: {}", key, e);
+ return 0;
+ }
+ }
+
+ // ========== List Operations ==========
+
+ /**
+ * Pushes one or more values to the head of a list.
+ *
+ * @param key the list key
+ * @param values the values to push
+ * @return the length of the list after the push
+ */
+ public long lpush(String key, String... values) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.lpush(key, values);
+ } catch (Exception e) {
+ logger.error("Failed to push to list: {}", key, e);
+ return 0;
+ }
+ }
+
+ /**
+ * Pushes one or more values to the tail of a list.
+ *
+ * @param key the list key
+ * @param values the values to push
+ * @return the length of the list after the push
+ */
+ public long rpush(String key, String... values) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.rpush(key, values);
+ } catch (Exception e) {
+ logger.error("Failed to push to list: {}", key, e);
+ return 0;
+ }
+ }
+
+ /**
+ * Pops a value from the head of a list.
+ *
+ * @param key the list key
+ * @return the popped value, or null if the list is empty
+ */
+ public String lpop(String key) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.lpop(key);
+ } catch (Exception e) {
+ logger.error("Failed to pop from list: {}", key, e);
+ return null;
+ }
+ }
+
+ /**
+ * Pops a value from the tail of a list.
+ *
+ * @param key the list key
+ * @return the popped value, or null if the list is empty
+ */
+ public String rpop(String key) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.rpop(key);
+ } catch (Exception e) {
+ logger.error("Failed to pop from list: {}", key, e);
+ return null;
+ }
+ }
+
+ /**
+ * Gets a range of elements from a list.
+ *
+ * @param key the list key
+ * @param start the start index
+ * @param end the end index (-1 for end of list)
+ * @return a list of elements
+ */
+ public List lrange(String key, long start, long end) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.lrange(key, start, end);
+ } catch (Exception e) {
+ logger.error("Failed to get list range: {}", key, e);
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Gets the length of a list.
+ *
+ * @param key the list key
+ * @return the length of the list
+ */
+ public long llen(String key) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.llen(key);
+ } catch (Exception e) {
+ logger.error("Failed to get list length: {}", key, e);
+ return 0;
+ }
+ }
+
+ // ========== Pub/Sub Operations ==========
+
+ /**
+ * Publishes a message to a channel.
+ *
+ * @param channel the channel name
+ * @param message the message to publish
+ * @return the number of subscribers that received the message
+ */
+ public long publish(String channel, String message) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.publish(channel, message);
+ } catch (Exception e) {
+ logger.error("Failed to publish message to channel: {}", channel, e);
+ return 0;
+ }
+ }
+
+ /**
+ * Publishes a message asynchronously.
+ *
+ * @param channel the channel name
+ * @param message the message to publish
+ * @return a CompletableFuture with the number of subscribers
+ */
+ public CompletableFuture publishAsync(String channel, String message) {
+ return asyncExecutor.supply(() -> publish(channel, message));
+ }
+
+ // ========== Counter Operations ==========
+
+ /**
+ * Increments a counter by 1.
+ *
+ * @param key the counter key
+ * @return the new value after incrementing
+ */
+ public long incr(String key) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.incr(key);
+ } catch (Exception e) {
+ logger.error("Failed to increment counter: {}", key, e);
+ return 0;
+ }
+ }
+
+ /**
+ * Increments a counter by a specific value.
+ *
+ * @param key the counter key
+ * @param increment the increment value
+ * @return the new value after incrementing
+ */
+ public long incrBy(String key, long increment) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.incrBy(key, increment);
+ } catch (Exception e) {
+ logger.error("Failed to increment counter: {}", key, e);
+ return 0;
+ }
+ }
+
+ /**
+ * Decrements a counter by 1.
+ *
+ * @param key the counter key
+ * @return the new value after decrementing
+ */
+ public long decr(String key) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.decr(key);
+ } catch (Exception e) {
+ logger.error("Failed to decrement counter: {}", key, e);
+ return 0;
+ }
+ }
+
+ /**
+ * Decrements a counter by a specific value.
+ *
+ * @param key the counter key
+ * @param decrement the decrement value
+ * @return the new value after decrementing
+ */
+ public long decrBy(String key, long decrement) {
+ try (Jedis jedis = redisManager.getResource()) {
+ return jedis.decrBy(key, decrement);
+ } catch (Exception e) {
+ logger.error("Failed to decrement counter: {}", key, e);
+ return 0;
+ }
+ }
+}
+
diff --git a/pom.xml b/pom.xml
index b732e08..d17a2cb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,6 +52,7 @@
4.11.1
+ 5.1.0
3.1.8
2.9.4
9.4.48.v20220622
@@ -82,6 +83,13 @@
${mongodb.version}
+
+