From f45e1b23a9c619eb0ba9688e12a1ad323aacc08a Mon Sep 17 00:00:00 2001 From: Ranjith Manickam Date: Wed, 5 Feb 2020 17:29:47 +0530 Subject: [PATCH] application config system property support changes --- pom.xml | 2 +- .../request/session/annotation/Property.java | 24 ++ .../{ => constant}/SessionConstants.java | 5 +- .../data/cache/DataCacheConstants.java | 40 --- .../session/data/cache/DataCacheFactory.java | 39 +-- .../data/cache/impl/StandardDataCache.java | 12 +- .../data/cache/impl/redis/RedisCache.java | 93 +++---- .../cache/impl/redis/RedisClusterManager.java | 8 +- .../data/cache/impl/redis/RedisManager.java | 15 +- .../impl/redis/RedisSentinelManager.java | 4 +- .../impl/redis/RedisStandardManager.java | 4 +- .../tomcat/request/session/model/Config.java | 253 ++++++++++++++++++ .../request/session/{ => model}/Session.java | 11 +- .../session/{ => model}/SessionContext.java | 2 +- .../session/{ => model}/SessionMetadata.java | 2 +- .../session/redis/SessionHandlerValve.java | 2 +- .../request/session/redis/SessionManager.java | 56 +--- .../request/session/util/ConfigUtil.java | 154 +++++++++++ .../session/{ => util}/SerializationUtil.java | 4 +- 19 files changed, 519 insertions(+), 211 deletions(-) create mode 100644 src/main/java/tomcat/request/session/annotation/Property.java rename src/main/java/tomcat/request/session/{ => constant}/SessionConstants.java (76%) delete mode 100644 src/main/java/tomcat/request/session/data/cache/DataCacheConstants.java create mode 100644 src/main/java/tomcat/request/session/model/Config.java rename src/main/java/tomcat/request/session/{ => model}/Session.java (99%) rename src/main/java/tomcat/request/session/{ => model}/SessionContext.java (96%) rename src/main/java/tomcat/request/session/{ => model}/SessionMetadata.java (97%) create mode 100644 src/main/java/tomcat/request/session/util/ConfigUtil.java rename src/main/java/tomcat/request/session/{ => util}/SerializationUtil.java (95%) diff --git a/pom.xml b/pom.xml index fab0bec..6156c33 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 tomcat-cluster-redis-session-manager tomcat-cluster-redis-session-manager - 3.0.3 + 3.0.4 jar tomcat-cluster-redis-session-manager diff --git a/src/main/java/tomcat/request/session/annotation/Property.java b/src/main/java/tomcat/request/session/annotation/Property.java new file mode 100644 index 0000000..3c452a2 --- /dev/null +++ b/src/main/java/tomcat/request/session/annotation/Property.java @@ -0,0 +1,24 @@ +package tomcat.request.session.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** author: Ranjith Manickam @ 5 Feb' 2020 */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Property { + String name() default ""; + + String defaultValue() default ""; + + PropertyType type() default PropertyType.STRING; + + enum PropertyType { + STRING, + BOOLEAN, + INTEGER, + LONG + } +} diff --git a/src/main/java/tomcat/request/session/SessionConstants.java b/src/main/java/tomcat/request/session/constant/SessionConstants.java similarity index 76% rename from src/main/java/tomcat/request/session/SessionConstants.java rename to src/main/java/tomcat/request/session/constant/SessionConstants.java index cf1a6dc..0ee190b 100644 --- a/src/main/java/tomcat/request/session/SessionConstants.java +++ b/src/main/java/tomcat/request/session/constant/SessionConstants.java @@ -1,11 +1,8 @@ -package tomcat.request.session; +package tomcat.request.session.constant; /** author: Ranjith Manickam @ 12 Jul' 2018 */ public interface SessionConstants { byte[] NULL_SESSION = "null".getBytes(); - String CATALINA_BASE = "catalina.base"; - String CONF = "conf"; - String SESSION_PERSISTENT_POLICIES = "session.persistent.policies"; enum SessionPolicy { DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST; diff --git a/src/main/java/tomcat/request/session/data/cache/DataCacheConstants.java b/src/main/java/tomcat/request/session/data/cache/DataCacheConstants.java deleted file mode 100644 index 69b9936..0000000 --- a/src/main/java/tomcat/request/session/data/cache/DataCacheConstants.java +++ /dev/null @@ -1,40 +0,0 @@ -package tomcat.request.session.data.cache; - -/** author: Ranjith Manickam @ 3 Dec' 2018 */ -public interface DataCacheConstants { - - // redis properties file name - String APPLICATION_PROPERTIES_FILE = "redis-data-cache.properties"; - - // redis properties - String REDIS_HOSTS = "redis.hosts"; - String REDIS_CLUSTER_ENABLED = "redis.cluster.enabled:false"; - String REDIS_SENTINEL_ENABLED = "redis.sentinel.enabled:false"; - String LB_STICKY_SESSION_ENABLED = "lb.sticky-session.enabled:false"; - - String REDIS_MAX_ACTIVE = "redis.max.active:10"; - String REDIS_TEST_ONBORROW = "redis.test.onBorrow:true"; - String REDIS_TEST_ONRETURN = "redis.test.onReturn:true"; - String REDIS_MAX_IDLE = "redis.max.idle:5"; - String REDIS_MIN_IDLE = "redis.min.idle:1"; - String REDIS_TEST_WHILEIDLE = "redis.test.whileIdle:true"; - String REDIS_TEST_NUMPEREVICTION = "redis.test.numPerEviction:10"; - String REDIS_TIME_BETWEENEVICTION = "redis.time.betweenEviction:60000"; - - String REDIS_PASSWORD = "redis.password"; - String REDIS_DATABASE = "redis.database:0"; - String REDIS_TIMEOUT = "redis.timeout:2000"; - - String REDIS_SENTINEL_MASTER = "redis.sentinel.master:mymaster"; - - String REDIS_CONN_FAILED_RETRY_MSG = "Jedis connection failed, retrying..."; - - String SESSION_EXPIRY_JOB_INTERVAL = "redis.session.expiry.job.interval:60"; - String SESSION_DATA_SYNC_JOB_INTERVAL = "redis.session.data-sync.job.interval:10"; - - enum RedisConfigType { - DEFAULT, - SENTINEL, - CLUSTER - } -} diff --git a/src/main/java/tomcat/request/session/data/cache/DataCacheFactory.java b/src/main/java/tomcat/request/session/data/cache/DataCacheFactory.java index 3e330c0..fe65fa3 100644 --- a/src/main/java/tomcat/request/session/data/cache/DataCacheFactory.java +++ b/src/main/java/tomcat/request/session/data/cache/DataCacheFactory.java @@ -2,49 +2,24 @@ package tomcat.request.session.data.cache; import tomcat.request.session.data.cache.impl.StandardDataCache; import tomcat.request.session.data.cache.impl.redis.RedisCache; - -import java.util.Properties; +import tomcat.request.session.model.Config; /** author: Ranjith Manickam @ 3 Dec' 2018 */ public class DataCacheFactory { - private final Properties properties; + private final Config config; private final int sessionExpiryTime; - public DataCacheFactory(Properties properties, int sessionExpiryTime) { - this.properties = properties; + public DataCacheFactory(Config config, int sessionExpiryTime) { + this.config = config; this.sessionExpiryTime = sessionExpiryTime; } /** To get data cache. */ public DataCache getDataCache() { - if (Boolean.parseBoolean(getProperty(this.properties, DataCacheConstants.LB_STICKY_SESSION_ENABLED))) { - return new StandardDataCache(this.properties, this.sessionExpiryTime); + if (this.config.getLbStickySessionEnabled()) { + return new StandardDataCache(this.config, this.sessionExpiryTime); } - return new RedisCache(this.properties); - } - - /** - * To get property with the specified key in this properties list. - * - * @param properties - properties list. - * @param key - search key. - * @return - Returns the property value. - */ - public static String getProperty(Properties properties, String key) { - return getProperty(properties, key, null); - } - - /** - * To get property with the specified key in this properties list. - * - * @param properties - properties list. - * @param key - search key. - * @param defaultValue - default value. - * @return - - Returns the property value. - */ - public static String getProperty(Properties properties, String key, String defaultValue) { - String[] keyValue = key.split(":"); - return properties.getProperty(keyValue[0], (keyValue.length > 1 && defaultValue == null) ? keyValue[1] : defaultValue); + return new RedisCache(this.config); } } diff --git a/src/main/java/tomcat/request/session/data/cache/impl/StandardDataCache.java b/src/main/java/tomcat/request/session/data/cache/impl/StandardDataCache.java index 3ba7f62..e56d4ee 100644 --- a/src/main/java/tomcat/request/session/data/cache/impl/StandardDataCache.java +++ b/src/main/java/tomcat/request/session/data/cache/impl/StandardDataCache.java @@ -3,14 +3,12 @@ package tomcat.request.session.data.cache.impl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import tomcat.request.session.data.cache.DataCache; -import tomcat.request.session.data.cache.DataCacheConstants; -import tomcat.request.session.data.cache.DataCacheFactory; import tomcat.request.session.data.cache.impl.redis.RedisCache; +import tomcat.request.session.model.Config; import java.io.Serializable; import java.util.Date; import java.util.Map; -import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -32,15 +30,15 @@ public class StandardDataCache extends RedisCache { private final Executor expiryJobExecutor; private final Executor dataSyncJobExecutor; - public StandardDataCache(Properties properties, int sessionExpiryTime) { - super(properties); + public StandardDataCache(Config config, int sessionExpiryTime) { + super(config); this.sessionExpiryTime = sessionExpiryTime; this.sessionData = new ConcurrentHashMap<>(); this.expiryJob = new Date().getTime(); this.dataSyncJob = new Date().getTime(); this.processDataSync = false; - this.expiryJobTriggerInterval = TimeUnit.MINUTES.toMillis(Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.SESSION_EXPIRY_JOB_INTERVAL))); - this.dataSyncJobTriggerInterval = TimeUnit.MINUTES.toMillis(Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.SESSION_DATA_SYNC_JOB_INTERVAL))); + this.expiryJobTriggerInterval = TimeUnit.MINUTES.toMillis(config.getRedisSessionExpiryJobInterval()); + this.dataSyncJobTriggerInterval = TimeUnit.MINUTES.toMillis(config.getRedisSessionDataSyncJobInterval()); this.expiryJobExecutor = Executors.newSingleThreadExecutor(); this.dataSyncJobExecutor = Executors.newSingleThreadExecutor(); } diff --git a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisCache.java b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisCache.java index b2c8c25..19e7e76 100644 --- a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisCache.java +++ b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisCache.java @@ -2,17 +2,14 @@ package tomcat.request.session.data.cache.impl.redis; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisPoolConfig; -import redis.clients.jedis.Protocol; import tomcat.request.session.data.cache.DataCache; -import tomcat.request.session.data.cache.DataCacheConstants; -import tomcat.request.session.data.cache.DataCacheConstants.RedisConfigType; -import tomcat.request.session.data.cache.DataCacheFactory; +import tomcat.request.session.model.Config; +import tomcat.request.session.model.Config.RedisConfigType; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Properties; import java.util.Set; /** author: Ranjith Manickam @ 12 Jul' 2018 */ @@ -20,8 +17,8 @@ public class RedisCache implements DataCache { private DataCache dataCache; - public RedisCache(Properties properties) { - initialize(properties); + public RedisCache(Config config) { + initialize(config); } /** {@inheritDoc} */ @@ -54,38 +51,31 @@ public class RedisCache implements DataCache { return dataCache.delete(key); } - private void initialize(Properties properties) { - RedisConfigType configType; - if (Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_CLUSTER_ENABLED))) { - configType = RedisConfigType.CLUSTER; - } else if (Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_SENTINEL_ENABLED))) { - configType = RedisConfigType.SENTINEL; - } else { - configType = RedisConfigType.DEFAULT; - } - - String hosts = DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_HOSTS, String.format("%s:%s", Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT)); - Collection nodes = getJedisNodes(hosts, configType); - - String password = DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_PASSWORD); - password = (password != null && !password.isEmpty()) ? password : null; - - int database = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_DATABASE)); - - int timeout = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TIMEOUT)); - timeout = Math.max(timeout, Protocol.DEFAULT_TIMEOUT); - - JedisPoolConfig poolConfig = getPoolConfig(properties); - switch (configType) { + private void initialize(Config config) { + Collection nodes = getJedisNodes(config.getRedisHosts(), config.getRedisConfigType()); + JedisPoolConfig poolConfig = getPoolConfig(config); + switch (config.getRedisConfigType()) { case CLUSTER: - dataCache = new RedisClusterManager((Set) nodes, password, timeout, poolConfig); + this.dataCache = new RedisClusterManager((Set) nodes, + config.getRedisPassword(), + config.getRedisTimeout(), + poolConfig); break; case SENTINEL: - String masterName = String.valueOf(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_SENTINEL_MASTER)); - dataCache = new RedisSentinelManager((Set) nodes, masterName, password, database, timeout, poolConfig); + this.dataCache = new RedisSentinelManager((Set) nodes, + config.getRedisSentinelMaster(), + config.getRedisPassword(), + config.getRedisDatabase(), + config.getRedisTimeout(), + poolConfig); break; default: - dataCache = new RedisStandardManager(((List) nodes).get(0), Integer.parseInt(((List) nodes).get(1)), password, database, timeout, poolConfig); + this.dataCache = new RedisStandardManager(((List) nodes).get(0), + Integer.parseInt(((List) nodes).get(1)), + config.getRedisPassword(), + config.getRedisDatabase(), + config.getRedisTimeout(), + poolConfig); break; } } @@ -93,34 +83,19 @@ public class RedisCache implements DataCache { /** * To get redis pool config. * - * @param properties - Redis data cache properties. + * @param config - Application config. * @return - Returns the redis pool config. */ - private JedisPoolConfig getPoolConfig(Properties properties) { + private JedisPoolConfig getPoolConfig(Config config) { JedisPoolConfig poolConfig = new JedisPoolConfig(); - int maxActive = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MAX_ACTIVE)); - poolConfig.setMaxTotal(maxActive); - - boolean testOnBorrow = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_ONBORROW)); - poolConfig.setTestOnBorrow(testOnBorrow); - - boolean testOnReturn = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_ONRETURN)); - poolConfig.setTestOnReturn(testOnReturn); - - int maxIdle = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MAX_IDLE)); - poolConfig.setMaxIdle(maxIdle); - - int minIdle = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MIN_IDLE)); - poolConfig.setMinIdle(minIdle); - - boolean testWhileIdle = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_WHILEIDLE)); - poolConfig.setTestWhileIdle(testWhileIdle); - - int testNumPerEviction = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_NUMPEREVICTION)); - poolConfig.setNumTestsPerEvictionRun(testNumPerEviction); - - long timeBetweenEviction = Long.parseLong(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TIME_BETWEENEVICTION)); - poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEviction); + poolConfig.setMaxTotal(config.getRedisMaxActive()); + poolConfig.setTestOnBorrow(config.getRedisTestOnBorrow()); + poolConfig.setTestOnReturn(config.getRedisTestOnReturn()); + poolConfig.setMaxIdle(config.getRedisMaxIdle()); + poolConfig.setMinIdle(config.getRedisMinIdle()); + poolConfig.setTestWhileIdle(config.getRedisTestWhileIdle()); + poolConfig.setNumTestsPerEvictionRun(config.getRedisTestNumPerEviction()); + poolConfig.setTimeBetweenEvictionRunsMillis(config.getRedisTimeBetweenEviction()); return poolConfig; } diff --git a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisClusterManager.java b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisClusterManager.java index 96b035c..6f2316a 100644 --- a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisClusterManager.java +++ b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisClusterManager.java @@ -15,15 +15,15 @@ class RedisClusterManager extends RedisManager { private final JedisCluster cluster; private static final int NUM_RETRIES = 30; - private static final int DEFAULT_MAX_REDIRECTIONS = 5; - private static final long FAILIURE_WAIT_TIME = 4000L; + private static final int DEFAULT_MAX_RE_DIRECTIONS = 5; + private static final long FAILURE_WAIT_TIME = 4000L; RedisClusterManager(Set nodes, String password, int timeout, JedisPoolConfig poolConfig) { - super(null, FAILIURE_WAIT_TIME); - this.cluster = new JedisCluster(nodes, timeout, Protocol.DEFAULT_TIMEOUT, DEFAULT_MAX_REDIRECTIONS, password, poolConfig); + super(null, FAILURE_WAIT_TIME); + this.cluster = new JedisCluster(nodes, timeout, Protocol.DEFAULT_TIMEOUT, DEFAULT_MAX_RE_DIRECTIONS, password, poolConfig); } /** {@inheritDoc} */ diff --git a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisManager.java b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisManager.java index c9aafb3..9a30bf6 100644 --- a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisManager.java +++ b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisManager.java @@ -6,7 +6,6 @@ import redis.clients.jedis.Jedis; import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.jedis.util.Pool; import tomcat.request.session.data.cache.DataCache; -import tomcat.request.session.data.cache.DataCacheConstants; /** author: Ranjith Manickam @ 12 Jul' 2018 */ abstract class RedisManager implements DataCache { @@ -14,12 +13,14 @@ abstract class RedisManager implements DataCache { private static final int NUM_RETRIES = 3; private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class); - private final Pool pool; - private final long failiureWaitTime; + private static final String REDIS_CONN_FAILED_RETRY_MSG = "Jedis connection failed, retrying..."; - RedisManager(Pool pool, long failiureWaitTime) { + private final Pool pool; + private final long failureWaitTime; + + RedisManager(Pool pool, long failureWaitTime) { this.pool = pool; - this.failiureWaitTime = failiureWaitTime; + this.failureWaitTime = failureWaitTime; } /** {@inheritDoc} */ @@ -119,12 +120,12 @@ abstract class RedisManager implements DataCache { * @param ex - jedis exception. */ void handleException(int tries, RuntimeException ex) { - LOGGER.error(DataCacheConstants.REDIS_CONN_FAILED_RETRY_MSG + tries); + LOGGER.error(REDIS_CONN_FAILED_RETRY_MSG + tries); if (tries == NUM_RETRIES) { throw ex; } try { - Thread.sleep(this.failiureWaitTime); + Thread.sleep(this.failureWaitTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } diff --git a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisSentinelManager.java b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisSentinelManager.java index 92e024b..d0f45f7 100644 --- a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisSentinelManager.java +++ b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisSentinelManager.java @@ -8,7 +8,7 @@ import java.util.Set; /** author: Ranjith Manickam @ 3 Dec' 2018 */ class RedisSentinelManager extends RedisManager { - private static final long FAILIURE_WAIT_TIME = 2000L; + private static final long FAILURE_WAIT_TIME = 2000L; RedisSentinelManager(Set nodes, String masterName, @@ -16,6 +16,6 @@ class RedisSentinelManager extends RedisManager { int database, int timeout, JedisPoolConfig poolConfig) { - super(new JedisSentinelPool(masterName, nodes, poolConfig, timeout, password, database), FAILIURE_WAIT_TIME); + super(new JedisSentinelPool(masterName, nodes, poolConfig, timeout, password, database), FAILURE_WAIT_TIME); } } diff --git a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisStandardManager.java b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisStandardManager.java index 911c9b2..62eecb7 100644 --- a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisStandardManager.java +++ b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisStandardManager.java @@ -6,7 +6,7 @@ import redis.clients.jedis.JedisPoolConfig; /** author: Ranjith Manickam @ 12 Jul' 2018 */ class RedisStandardManager extends RedisManager { - private static final long FAILIURE_WAIT_TIME = 2000L; + private static final long FAILURE_WAIT_TIME = 2000L; RedisStandardManager(String host, int port, @@ -14,6 +14,6 @@ class RedisStandardManager extends RedisManager { int database, int timeout, JedisPoolConfig poolConfig) { - super(new JedisPool(poolConfig, host, port, timeout, password, database), FAILIURE_WAIT_TIME); + super(new JedisPool(poolConfig, host, port, timeout, password, database), FAILURE_WAIT_TIME); } } diff --git a/src/main/java/tomcat/request/session/model/Config.java b/src/main/java/tomcat/request/session/model/Config.java new file mode 100644 index 0000000..f731394 --- /dev/null +++ b/src/main/java/tomcat/request/session/model/Config.java @@ -0,0 +1,253 @@ +package tomcat.request.session.model; + +import redis.clients.jedis.Protocol; +import tomcat.request.session.annotation.Property; + +import java.io.Serializable; + +import static tomcat.request.session.annotation.Property.PropertyType.BOOLEAN; +import static tomcat.request.session.annotation.Property.PropertyType.INTEGER; + +/** author: Ranjith Manickam @ 5 Feb' 2020 */ +public class Config implements Serializable { + + public static final String APPLICATION_PROPERTIES_FILE = "redis-data-cache.properties"; + + /** Redis config type. */ + public enum RedisConfigType { + DEFAULT, + SENTINEL, + CLUSTER + } + + @Property(name = "redis.hosts", defaultValue = "127.0.0.1:6379") + private String redisHosts; + + @Property(name = "redis.cluster.enabled", type = BOOLEAN, defaultValue = "false") + private Boolean redisClusterEnabled; + + @Property(name = "redis.sentinel.enabled", type = BOOLEAN, defaultValue = "false") + private Boolean redisSentinelEnabled; + + @Property(name = "lb.sticky-session.enabled", type = BOOLEAN, defaultValue = "false") + private Boolean lbStickySessionEnabled; + + @Property(name = "redis.max.active", type = INTEGER, defaultValue = "10") + private Integer redisMaxActive; + + @Property(name = "redis.test.onBorrow", type = BOOLEAN, defaultValue = "true") + private Boolean redisTestOnBorrow; + + @Property(name = "redis.test.onReturn", type = BOOLEAN, defaultValue = "true") + private Boolean redisTestOnReturn; + + @Property(name = "redis.max.idle", type = INTEGER, defaultValue = "5") + private Integer redisMaxIdle; + + @Property(name = "redis.min.idle", type = INTEGER, defaultValue = "1") + private Integer redisMinIdle; + + @Property(name = "redis.test.whileIdle", type = BOOLEAN, defaultValue = "true") + private Boolean redisTestWhileIdle; + + @Property(name = "redis.test.numPerEviction", type = INTEGER, defaultValue = "10") + private Integer redisTestNumPerEviction; + + @Property(name = "redis.time.betweenEviction", type = INTEGER, defaultValue = "60000") + private Integer redisTimeBetweenEviction; + + @Property(name = "redis.password") + private String redisPassword; + + @Property(name = "redis.database", type = INTEGER, defaultValue = "0") + private Integer redisDatabase; + + @Property(name = "redis.timeout", type = INTEGER, defaultValue = "2000") + private Integer redisTimeout; + + @Property(name = "redis.sentinel.master", defaultValue = "mymaster") + private String redisSentinelMaster; + + @Property(name = "redis.session.expiry.job.interval", type = INTEGER, defaultValue = "60") + private Integer redisSessionExpiryJobInterval; + + @Property(name = "redis.session.data-sync.job.interval", type = INTEGER, defaultValue = "10") + private Integer redisSessionDataSyncJobInterval; + + @Property(name = "session.persistent.policies", defaultValue = "DEFAULT") + private String sessionPersistentPolicies; + + public Config() { + } + + public Config(String redisHosts, + Boolean redisClusterEnabled, + Boolean redisSentinelEnabled, + Boolean lbStickySessionEnabled, + Integer redisMaxActive, + Boolean redisTestOnBorrow, + Boolean redisTestOnReturn, + Integer redisMaxIdle, + Integer redisMinIdle, + Boolean redisTestWhileIdle, + Integer redisTestNumPerEviction, + Integer redisTimeBetweenEviction, + String redisPassword, + Integer redisDatabase, + Integer redisTimeout, + String redisSentinelMaster, + Integer redisSessionExpiryJobInterval, + Integer redisSessionDataSyncJobInterval, + String sessionPersistentPolicies) { + this.redisHosts = redisHosts; + this.redisClusterEnabled = redisClusterEnabled; + this.redisSentinelEnabled = redisSentinelEnabled; + this.lbStickySessionEnabled = lbStickySessionEnabled; + this.redisMaxActive = redisMaxActive; + this.redisTestOnBorrow = redisTestOnBorrow; + this.redisTestOnReturn = redisTestOnReturn; + this.redisMaxIdle = redisMaxIdle; + this.redisMinIdle = redisMinIdle; + this.redisTestWhileIdle = redisTestWhileIdle; + this.redisTestNumPerEviction = redisTestNumPerEviction; + this.redisTimeBetweenEviction = redisTimeBetweenEviction; + this.redisPassword = redisPassword; + this.redisDatabase = redisDatabase; + this.redisTimeout = redisTimeout; + this.redisSentinelMaster = redisSentinelMaster; + this.redisSessionExpiryJobInterval = redisSessionExpiryJobInterval; + this.redisSessionDataSyncJobInterval = redisSessionDataSyncJobInterval; + this.sessionPersistentPolicies = sessionPersistentPolicies; + } + + /** To get 'redis.hosts' value. */ + public String getRedisHosts() { + return redisHosts; + } + + /** To get 'redis.cluster.enabled' value. */ + public Boolean getRedisClusterEnabled() { + return redisClusterEnabled; + } + + /** To get 'redis.sentinel.enabled' value. */ + public Boolean getRedisSentinelEnabled() { + return redisSentinelEnabled; + } + + /** To get 'lb.sticky-session.enabled' value. */ + public Boolean getLbStickySessionEnabled() { + return lbStickySessionEnabled; + } + + /** To get 'redis.max.active' value. */ + public Integer getRedisMaxActive() { + return redisMaxActive; + } + + /** To get 'redis.test.onBorrow' value. */ + public Boolean getRedisTestOnBorrow() { + return redisTestOnBorrow; + } + + /** To get 'redis.test.onReturn' value. */ + public Boolean getRedisTestOnReturn() { + return redisTestOnReturn; + } + + /** To get 'redis.max.idle' value. */ + public Integer getRedisMaxIdle() { + return redisMaxIdle; + } + + /** To get 'redis.min.idle' value. */ + public Integer getRedisMinIdle() { + return redisMinIdle; + } + + /** To get 'redis.test.whileIdle' value. */ + public Boolean getRedisTestWhileIdle() { + return redisTestWhileIdle; + } + + /** To get 'redis.test.numPerEviction' value. */ + public Integer getRedisTestNumPerEviction() { + return redisTestNumPerEviction; + } + + /** To get 'redis.time.betweenEviction' value. */ + public Integer getRedisTimeBetweenEviction() { + return redisTimeBetweenEviction; + } + + /** To get 'redis.password' value. */ + public String getRedisPassword() { + return (redisPassword == null || redisPassword.isEmpty()) ? null : redisPassword; + } + + /** To get 'redis.database' value. */ + public Integer getRedisDatabase() { + return redisDatabase; + } + + /** To get 'redis.timeout' value. */ + public Integer getRedisTimeout() { + return Math.max(redisTimeout, Protocol.DEFAULT_TIMEOUT); + } + + /** To get 'redis.sentinel.master' value. */ + public String getRedisSentinelMaster() { + return redisSentinelMaster; + } + + /** To get 'redis.session.expiry.job.interval' value. */ + public Integer getRedisSessionExpiryJobInterval() { + return redisSessionExpiryJobInterval; + } + + /** To get 'redis.session.data-sync.job.interval' value. */ + public Integer getRedisSessionDataSyncJobInterval() { + return redisSessionDataSyncJobInterval; + } + + /** To get 'session.persistent.policies' value */ + public String getSessionPersistentPolicies() { + return sessionPersistentPolicies; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "Config{" + + "redisHosts='" + redisHosts + '\'' + + ", redisClusterEnabled=" + redisClusterEnabled + + ", redisSentinelEnabled=" + redisSentinelEnabled + + ", lbStickySessionEnabled=" + lbStickySessionEnabled + + ", redisMaxActive=" + redisMaxActive + + ", redisTestOnBorrow=" + redisTestOnBorrow + + ", redisTestOnReturn=" + redisTestOnReturn + + ", redisMaxIdle=" + redisMaxIdle + + ", redisMinIdle=" + redisMinIdle + + ", redisTestWhileIdle=" + redisTestWhileIdle + + ", redisTestNumPerEviction=" + redisTestNumPerEviction + + ", redisTimeBetweenEviction=" + redisTimeBetweenEviction + + ", redisPassword='" + redisPassword + '\'' + + ", redisDatabase=" + redisDatabase + + ", redisTimeout=" + redisTimeout + + ", redisSentinelMaster='" + redisSentinelMaster + '\'' + + ", redisSessionExpiryJobInterval=" + redisSessionExpiryJobInterval + + ", redisSessionDataSyncJobInterval=" + redisSessionDataSyncJobInterval + + ", sessionPersistentPolicies='" + sessionPersistentPolicies + '\'' + + '}'; + } + + /** To get redis config type. */ + public RedisConfigType getRedisConfigType() { + if (this.getRedisClusterEnabled()) { + return RedisConfigType.CLUSTER; + } else if (this.getRedisSentinelEnabled()) { + return RedisConfigType.SENTINEL; + } + return RedisConfigType.DEFAULT; + } +} diff --git a/src/main/java/tomcat/request/session/Session.java b/src/main/java/tomcat/request/session/model/Session.java similarity index 99% rename from src/main/java/tomcat/request/session/Session.java rename to src/main/java/tomcat/request/session/model/Session.java index 4fe87c8..6f4235e 100644 --- a/src/main/java/tomcat/request/session/Session.java +++ b/src/main/java/tomcat/request/session/model/Session.java @@ -1,4 +1,8 @@ -package tomcat.request.session; +package tomcat.request.session.model; + +import org.apache.catalina.Manager; +import org.apache.catalina.session.StandardSession; +import tomcat.request.session.redis.SessionManager; import java.io.IOException; import java.io.ObjectInputStream; @@ -8,11 +12,6 @@ import java.util.Enumeration; import java.util.HashMap; import java.util.Map; -import org.apache.catalina.Manager; -import org.apache.catalina.session.StandardSession; - -import tomcat.request.session.redis.SessionManager; - /** author: Ranjith Manickam @ 12 Jul' 2018 */ public class Session extends StandardSession { diff --git a/src/main/java/tomcat/request/session/SessionContext.java b/src/main/java/tomcat/request/session/model/SessionContext.java similarity index 96% rename from src/main/java/tomcat/request/session/SessionContext.java rename to src/main/java/tomcat/request/session/model/SessionContext.java index 21feb07..502a73c 100644 --- a/src/main/java/tomcat/request/session/SessionContext.java +++ b/src/main/java/tomcat/request/session/model/SessionContext.java @@ -1,4 +1,4 @@ -package tomcat.request.session; +package tomcat.request.session.model; /** author: Ranjith Manickam @ 12 Jul' 2018 */ public class SessionContext { diff --git a/src/main/java/tomcat/request/session/SessionMetadata.java b/src/main/java/tomcat/request/session/model/SessionMetadata.java similarity index 97% rename from src/main/java/tomcat/request/session/SessionMetadata.java rename to src/main/java/tomcat/request/session/model/SessionMetadata.java index 7559f30..b1cdfdd 100644 --- a/src/main/java/tomcat/request/session/SessionMetadata.java +++ b/src/main/java/tomcat/request/session/model/SessionMetadata.java @@ -1,4 +1,4 @@ -package tomcat.request.session; +package tomcat.request.session.model; import java.io.IOException; import java.io.ObjectInputStream; diff --git a/src/main/java/tomcat/request/session/redis/SessionHandlerValve.java b/src/main/java/tomcat/request/session/redis/SessionHandlerValve.java index 782074d..aa891bc 100644 --- a/src/main/java/tomcat/request/session/redis/SessionHandlerValve.java +++ b/src/main/java/tomcat/request/session/redis/SessionHandlerValve.java @@ -31,7 +31,7 @@ public class SessionHandlerValve extends ValveBase { LOGGER.error("Error processing request", ex); throw new BackendException(); } finally { - manager.afterRequest(); + this.manager.afterRequest(); } } } diff --git a/src/main/java/tomcat/request/session/redis/SessionManager.java b/src/main/java/tomcat/request/session/redis/SessionManager.java index 90793d7..faf9bec 100644 --- a/src/main/java/tomcat/request/session/redis/SessionManager.java +++ b/src/main/java/tomcat/request/session/redis/SessionManager.java @@ -9,24 +9,21 @@ import org.apache.catalina.Valve; import org.apache.catalina.session.ManagerBase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import tomcat.request.session.SerializationUtil; -import tomcat.request.session.Session; -import tomcat.request.session.SessionConstants; -import tomcat.request.session.SessionConstants.SessionPolicy; -import tomcat.request.session.SessionContext; -import tomcat.request.session.SessionMetadata; +import tomcat.request.session.constant.SessionConstants; +import tomcat.request.session.constant.SessionConstants.SessionPolicy; import tomcat.request.session.data.cache.DataCache; -import tomcat.request.session.data.cache.DataCacheConstants; import tomcat.request.session.data.cache.DataCacheFactory; +import tomcat.request.session.model.Config; +import tomcat.request.session.model.Session; +import tomcat.request.session.model.SessionContext; +import tomcat.request.session.model.SessionMetadata; +import tomcat.request.session.util.ConfigUtil; +import tomcat.request.session.util.SerializationUtil; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.lang.reflect.Method; import java.util.Arrays; import java.util.EnumSet; -import java.util.Properties; import java.util.Set; /** author: Ranjith Manickam @ 12 Jul' 2018 */ @@ -211,15 +208,15 @@ public class SessionManager extends ManagerBase implements Lifecycle { /** To initialize the session manager. */ private void initialize() { try { - Properties properties = getApplicationProperties(); - this.dataCache = new DataCacheFactory(properties, getSessionTimeout(null)).getDataCache(); + Config config = ConfigUtil.getConfig(); + this.dataCache = new DataCacheFactory(config, getSessionTimeout(null)).getDataCache(); this.serializer = new SerializationUtil(); Context context = getContextIns(); ClassLoader loader = (context != null && context.getLoader() != null) ? context.getLoader().getClassLoader() : null; this.serializer.setClassLoader(loader); - setSessionPersistentPolicies(properties); + setSessionPersistentPolicies(config); } catch (Exception ex) { LOGGER.error("Error occurred while initializing the session manager..", ex); throw ex; @@ -326,8 +323,8 @@ public class SessionManager extends ManagerBase implements Lifecycle { } /** To set session persistent policies */ - private void setSessionPersistentPolicies(Properties properties) { - String sessionPolicies = properties.getProperty(SessionConstants.SESSION_PERSISTENT_POLICIES); + private void setSessionPersistentPolicies(Config config) { + String sessionPolicies = config.getSessionPersistentPolicies(); if (sessionPolicies == null || sessionPolicies.isEmpty()) { return; } @@ -338,31 +335,4 @@ public class SessionManager extends ManagerBase implements Lifecycle { this.sessionPolicy.add(SessionPolicy.fromName(sessionPolicyName)); } } - - /** To get redis data cache properties. */ - private Properties getApplicationProperties() { - Properties properties = new Properties(); - try { - String filePath = System.getProperty(SessionConstants.CATALINA_BASE).concat(File.separator) - .concat(SessionConstants.CONF).concat(File.separator) - .concat(DataCacheConstants.APPLICATION_PROPERTIES_FILE); - - InputStream resourceStream = null; - try { - resourceStream = (!filePath.isEmpty() && new File(filePath).exists()) ? new FileInputStream(filePath) : null; - if (resourceStream == null) { - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - resourceStream = loader.getResourceAsStream(DataCacheConstants.APPLICATION_PROPERTIES_FILE); - } - properties.load(resourceStream); - } finally { - if (resourceStream != null) { - resourceStream.close(); - } - } - } catch (IOException ex) { - LOGGER.error("Error while retrieving application properties", ex); - } - return properties; - } } diff --git a/src/main/java/tomcat/request/session/util/ConfigUtil.java b/src/main/java/tomcat/request/session/util/ConfigUtil.java new file mode 100644 index 0000000..b2ae4ad --- /dev/null +++ b/src/main/java/tomcat/request/session/util/ConfigUtil.java @@ -0,0 +1,154 @@ +package tomcat.request.session.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import tomcat.request.session.annotation.Property; +import tomcat.request.session.model.Config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.util.Properties; + +/** author: Ranjith Manickam @ 5 Feb' 2020 */ +public class ConfigUtil { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigUtil.class); + + private static final String CONF = "conf"; + private static final String CATALINA_BASE = "catalina.base"; + + /** To get application config. */ + public static Config getConfig() { + Properties properties = getApplicationProperties(); + Config config = new Config(); + + for (Field field : Config.class.getDeclaredFields()) { + Property property = field.getAnnotation(Property.class); + if (property == null) { + continue; + } + + String propertyName = property.name(); + Property.PropertyType propertyType = property.type(); + + if (propertyName.isEmpty()) { + continue; + } + + String value = properties.getProperty(propertyName); + if (isSystemProperty(value)) { + value = getSystemProperty(value); + } + + if (value == null || value.isEmpty()) { + value = property.defaultValue(); + + if (value.isEmpty()) { + continue; + } + } + + field.setAccessible(true); + try { + switch (propertyType) { + case BOOLEAN: + field.set(config, Boolean.parseBoolean(value)); + break; + case INTEGER: + field.set(config, Integer.parseInt(value)); + break; + case LONG: + field.set(config, Long.parseLong(value)); + break; + case STRING: + default: + field.set(config, value); + break; + } + } catch (Exception ex) { + LOGGER.error("Error while initializing application properties", ex); + } + } + + return config; + } + + /** To get redis data cache properties. */ + private static Properties getApplicationProperties() { + Properties properties = new Properties(); + try { + String filePath = System.getProperty(CATALINA_BASE) + .concat(File.separator) + .concat(CONF).concat(File.separator) + .concat(Config.APPLICATION_PROPERTIES_FILE); + + InputStream resourceStream = null; + try { + resourceStream = (!filePath.isEmpty() && new File(filePath).exists()) ? new FileInputStream(filePath) : null; + if (resourceStream == null) { + LOGGER.info("Initializing tomcat redis session manager with default properties"); + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + resourceStream = loader.getResourceAsStream(Config.APPLICATION_PROPERTIES_FILE); + } + properties.load(resourceStream); + } finally { + if (resourceStream != null) { + resourceStream.close(); + } + } + } catch (IOException ex) { + LOGGER.error("Error while retrieving application properties", ex); + } + return properties; + } + + /** + * To get property with the specified key from system property. + * + * @param key - search key. + * @return - Returns the system property value. + */ + private static String getSystemProperty(String key) { + int fromIndex = 0; + + while (true) { + int beginIndex = key.indexOf("${", fromIndex); + int endIndex = key.indexOf("}", fromIndex); + + if (beginIndex < 0 || endIndex < 0) { + break; + } + + String expression = key.substring(beginIndex + 2, endIndex); + String value = System.getProperty(expression); + + if (value == null || value.isEmpty()) { + fromIndex = endIndex + 1; + continue; + } + + key = key.replace(String.format("${%s}", expression), value); + } + + return key; + } + + /** + * To check if the value is from system property. + * + * @param key - search key. + * @return - Returns true if the key start with '${' and ends with '}'. + */ + private static boolean isSystemProperty(String key) { + if (key == null || key.isEmpty()) { + return false; + } + + int beginIndex = key.indexOf("${"); + int endIndex = key.indexOf("}"); + return beginIndex >= 0 && endIndex >= 0 && beginIndex < endIndex; + } +} diff --git a/src/main/java/tomcat/request/session/SerializationUtil.java b/src/main/java/tomcat/request/session/util/SerializationUtil.java similarity index 95% rename from src/main/java/tomcat/request/session/SerializationUtil.java rename to src/main/java/tomcat/request/session/util/SerializationUtil.java index 33d3612..223624c 100644 --- a/src/main/java/tomcat/request/session/SerializationUtil.java +++ b/src/main/java/tomcat/request/session/util/SerializationUtil.java @@ -1,6 +1,8 @@ -package tomcat.request.session; +package tomcat.request.session.util; import org.apache.catalina.util.CustomObjectInputStream; +import tomcat.request.session.model.Session; +import tomcat.request.session.model.SessionMetadata; import java.io.BufferedInputStream; import java.io.BufferedOutputStream;