diff --git a/networkdataapi-core/pom.xml b/networkdataapi-core/pom.xml index 4e970c2..57889a3 100644 --- a/networkdataapi-core/pom.xml +++ b/networkdataapi-core/pom.xml @@ -23,6 +23,12 @@ mongodb-driver-sync + + + redis.clients + jedis + + 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:

+ * + * + *

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:

+ * + * + *

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: + *

+ * + *

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:

+ * + * + * @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}
+ + + redis.clients + jedis + ${redis.version} + + com.github.ben-manes.caffeine