diff --git a/src/main/java/tomcat/request/session/SerializationUtil.java b/src/main/java/tomcat/request/session/SerializationUtil.java index 2d7fd4c..33d3612 100644 --- a/src/main/java/tomcat/request/session/SerializationUtil.java +++ b/src/main/java/tomcat/request/session/SerializationUtil.java @@ -74,5 +74,4 @@ public class SerializationUtil { session.readObjectData(ois); } } - } diff --git a/src/main/java/tomcat/request/session/Session.java b/src/main/java/tomcat/request/session/Session.java index 2ccb873..4fe87c8 100644 --- a/src/main/java/tomcat/request/session/Session.java +++ b/src/main/java/tomcat/request/session/Session.java @@ -18,11 +18,11 @@ public class Session extends StandardSession { private static final long serialVersionUID = -6056744304016869278L; - protected Boolean dirty; - protected Map changedAttributes; + private Boolean dirty; + private Map changedAttributes; - protected static Boolean manualDirtyTrackingSupportEnabled = false; - protected static String manualDirtyTrackingAttributeKey = "__changed__"; + private static Boolean manualDirtyTrackingSupportEnabled = false; + private static String manualDirtyTrackingAttributeKey = "__changed__"; public Session(Manager manager) { super(manager); @@ -131,5 +131,4 @@ public class Session extends StandardSession { public void invalidate() { super.invalidate(); } - } diff --git a/src/main/java/tomcat/request/session/SessionConstants.java b/src/main/java/tomcat/request/session/SessionConstants.java index 44f2b2d..b0a4813 100644 --- a/src/main/java/tomcat/request/session/SessionConstants.java +++ b/src/main/java/tomcat/request/session/SessionConstants.java @@ -18,5 +18,4 @@ public interface SessionConstants { throw new IllegalArgumentException("Invalid session policy [" + name + "]"); } } - } diff --git a/src/main/java/tomcat/request/session/SessionContext.java b/src/main/java/tomcat/request/session/SessionContext.java index 263615b..21feb07 100644 --- a/src/main/java/tomcat/request/session/SessionContext.java +++ b/src/main/java/tomcat/request/session/SessionContext.java @@ -53,5 +53,4 @@ public class SessionContext { public String toString() { return "SessionContext [id=" + id + "]"; } - } diff --git a/src/main/java/tomcat/request/session/SessionMetadata.java b/src/main/java/tomcat/request/session/SessionMetadata.java index 989cf08..7559f30 100644 --- a/src/main/java/tomcat/request/session/SessionMetadata.java +++ b/src/main/java/tomcat/request/session/SessionMetadata.java @@ -44,5 +44,4 @@ public class SessionMetadata implements Serializable { in.read(attributesHash, 0, hashLength); this.attributesHash = attributesHash; } - } diff --git a/src/main/java/tomcat/request/session/data/cache/DataCache.java b/src/main/java/tomcat/request/session/data/cache/DataCache.java index be3b045..84d4a81 100644 --- a/src/main/java/tomcat/request/session/data/cache/DataCache.java +++ b/src/main/java/tomcat/request/session/data/cache/DataCache.java @@ -45,5 +45,4 @@ public interface DataCache { * @return - Returns the number of keys that were removed. */ Long delete(String key); - } diff --git a/src/main/java/tomcat/request/session/data/cache/DataCacheConstants.java b/src/main/java/tomcat/request/session/data/cache/DataCacheConstants.java new file mode 100644 index 0000000..aef62a6 --- /dev/null +++ b/src/main/java/tomcat/request/session/data/cache/DataCacheConstants.java @@ -0,0 +1,38 @@ +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:127.0.0.1:6379"; + String REDIS_CLUSTER_ENABLED = "redis.cluster.enabled:false"; + String REDIS_SENTINEL_ENABLED = "redis.sentinel.enabled:false"; + String REDIS_LB_STICKY_SESSION_ENABLED = "redis.load-balancer.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"; + String REDIS_TIMEOUT = "redis.timeout"; + + String REDIS_SENTINEL_MASTER = "redis.sentinel.master"; + String REDIS_DEFAULT_SENTINEL_MASTER = "mymaster"; + + String REDIS_CONN_FAILED_RETRY_MSG = "Jedis connection failed, retrying..."; + + 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 new file mode 100644 index 0000000..f8c99e2 --- /dev/null +++ b/src/main/java/tomcat/request/session/data/cache/DataCacheFactory.java @@ -0,0 +1,87 @@ +package tomcat.request.session.data.cache; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import tomcat.request.session.SessionConstants; +import tomcat.request.session.data.cache.impl.StandardDataCache; +import tomcat.request.session.data.cache.impl.redis.RedisCache; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** author: Ranjith Manickam @ 3 Dec' 2018 */ +public class DataCacheFactory { + + private static final Log LOGGER = LogFactory.getLog(DataCacheFactory.class); + + private final long sessionExpiryTime; + + public DataCacheFactory(long sessionExpiryTime) { + this.sessionExpiryTime = sessionExpiryTime; + } + + /** To get data cache. */ + public DataCache getDataCache() { + Properties properties = getApplicationProperties(); + + if (Boolean.valueOf(getProperty(properties, DataCacheConstants.REDIS_LB_STICKY_SESSION_ENABLED))) { + return new StandardDataCache(properties, this.sessionExpiryTime); + } + + return new RedisCache(properties); + } + + /** 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; + } + + /** + * 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); + } +} 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 new file mode 100644 index 0000000..9681096 --- /dev/null +++ b/src/main/java/tomcat/request/session/data/cache/impl/StandardDataCache.java @@ -0,0 +1,142 @@ +package tomcat.request.session.data.cache.impl; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import tomcat.request.session.data.cache.impl.redis.RedisCache; + +import java.io.Serializable; +import java.util.Date; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +/** author: Ranjith Manickam @ 3 Dec' 2018 */ +public class StandardDataCache extends RedisCache { + + private Date lastSessionJobRun; + private final Map sessionData = new ConcurrentHashMap<>(); + + private final long sessionExpiryTime; + + public StandardDataCache(Properties properties, long sessionExpiryTime) { + super(properties); + this.sessionExpiryTime = (sessionExpiryTime + 60) / 60; + this.lastSessionJobRun = new Date(); + } + + /** {@inheritDoc} */ + @Override + public byte[] set(String key, byte[] value) { + triggerSessionExpiry(); + this.sessionData.put(key, new SessionData(value)); + return super.set(key, value); + } + + /** {@inheritDoc} */ + @Override + public Long setnx(String key, byte[] value) { + triggerSessionExpiry(); + Long retValue = super.setnx(key, value); + if (retValue == 1L) { + this.sessionData.put(key, new SessionData(value)); + } + return retValue; + } + + /** {@inheritDoc} */ + @Override + public Long expire(String key, int seconds) { + return super.expire(key, seconds); + } + + /** {@inheritDoc} */ + @Override + public byte[] get(String key) { + triggerSessionExpiry(); + if (this.sessionData.containsKey(key)) { + return this.sessionData.get(key).getValue(); + } + return super.get(key); + } + + /** {@inheritDoc} */ + @Override + public Long delete(String key) { + this.sessionData.remove(key); + return super.delete(key); + } + + /** Session data. */ + private class SessionData implements Serializable { + private byte[] value; + private Date lastAccessedOn; + + SessionData(byte[] value) { + this.value = value; + updatedLastAccessedOn(); + } + + void updatedLastAccessedOn() { + this.lastAccessedOn = new Date(); + } + + byte[] getValue() { + updatedLastAccessedOn(); + return this.value; + } + + Date getLastAccessedOn() { + return this.lastAccessedOn; + } + } + + /** To trigger session expiry thread. */ + private void triggerSessionExpiry() { + long diff = new Date().getTime() - this.lastSessionJobRun.getTime(); + long diffMinutes = diff / (60 * 1000) % 60; + + if (diffMinutes > 0L) { + synchronized (this) { + new SessionDataExpiryThread(this.sessionData, this.sessionExpiryTime); + this.lastSessionJobRun = new Date(); + } + } + } + + /** Session data expiry thread. This will takes care of the session expired data removal. */ + private class SessionDataExpiryThread implements Runnable { + + private final Log LOGGER = LogFactory.getLog(SessionDataExpiryThread.class); + + private final long sessionExpiryTime; + private final Map sessionData; + + SessionDataExpiryThread(Map sessionData, long sessionExpiryTime) { + this.sessionData = sessionData; + this.sessionExpiryTime = sessionExpiryTime; + new Thread(this).start(); + } + + /** {@inheritDoc} */ + @Override + public void run() { + try { + for (String key : this.sessionData.keySet()) { + SessionData data = this.sessionData.get(key); + if (data == null) { + continue; + } + + long diff = new Date().getTime() - data.getLastAccessedOn().getTime(); + long diffMinutes = diff / (60 * 1000) % 60; + + if (diffMinutes > this.sessionExpiryTime) { + this.sessionData.remove(key); + } + } + } catch (Exception ex) { + LOGGER.error("Error processing session data expiry thread", ex); + } + } + } +} 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 e83dd3b..2322bdb 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 @@ -1,18 +1,13 @@ package tomcat.request.session.data.cache.impl.redis; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import redis.clients.jedis.HostAndPort; import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.Protocol; -import tomcat.request.session.SessionConstants; import tomcat.request.session.data.cache.DataCache; -import tomcat.request.session.data.cache.impl.redis.RedisConstants.RedisConfigType; +import tomcat.request.session.data.cache.DataCacheConstants; +import tomcat.request.session.data.cache.DataCacheConstants.RedisConfigType; +import tomcat.request.session.data.cache.DataCacheFactory; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -23,12 +18,10 @@ import java.util.Set; /** author: Ranjith Manickam @ 12 Jul' 2018 */ public class RedisCache implements DataCache { - private static DataCache dataCache; + private DataCache dataCache; - private static final Log LOGGER = LogFactory.getLog(RedisCache.class); - - public RedisCache() { - initialize(); + public RedisCache(Properties properties) { + initialize(properties); } /** {@inheritDoc} */ @@ -61,44 +54,38 @@ public class RedisCache implements DataCache { return dataCache.delete(key); } - /** To initialize the redis data cache. */ - private void initialize() { - if (dataCache != null) { - return; - } - Properties properties = getProperties(); - + private void initialize(Properties properties) { RedisConfigType configType; - if (Boolean.valueOf(properties.getProperty(RedisConstants.CLUSTER_ENABLED))) { + if (Boolean.valueOf(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_CLUSTER_ENABLED))) { configType = RedisConfigType.CLUSTER; - } else if (Boolean.valueOf(properties.getProperty(RedisConstants.SENTINEL_ENABLED))) { + } else if (Boolean.valueOf(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_SENTINEL_ENABLED))) { configType = RedisConfigType.SENTINEL; } else { configType = RedisConfigType.DEFAULT; } - String hosts = properties.getProperty(RedisConstants.HOSTS, Protocol.DEFAULT_HOST.concat(":").concat(String.valueOf(Protocol.DEFAULT_PORT))); + 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 = properties.getProperty(RedisConstants.PASSWORD); + String password = DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_PASSWORD); password = (password != null && !password.isEmpty()) ? password : null; - int database = Integer.parseInt(properties.getProperty(RedisConstants.DATABASE, String.valueOf(Protocol.DEFAULT_DATABASE))); + int database = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_DATABASE)); - int timeout = Integer.parseInt(properties.getProperty(RedisConstants.TIMEOUT, String.valueOf(Protocol.DEFAULT_TIMEOUT))); + int timeout = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TIMEOUT)); timeout = (timeout < Protocol.DEFAULT_TIMEOUT) ? Protocol.DEFAULT_TIMEOUT : timeout; JedisPoolConfig poolConfig = getPoolConfig(properties); switch (configType) { case CLUSTER: - dataCache = new RedisClusterUtil((Set) nodes, password, timeout, poolConfig); + dataCache = new RedisClusterManager((Set) nodes, password, timeout, poolConfig); break; case SENTINEL: - String masterName = String.valueOf(properties.getProperty(RedisConstants.SENTINEL_MASTER, RedisConstants.DEFAULT_SENTINEL_MASTER)); - dataCache = new RedisSentinelUtil((Set) nodes, masterName, password, database, timeout, poolConfig); + String masterName = String.valueOf(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_SENTINEL_MASTER)); + dataCache = new RedisSentinelManager((Set) nodes, masterName, password, database, timeout, poolConfig); break; default: - dataCache = new RedisUtil(((List) nodes).get(0), Integer.parseInt(((List) nodes).get(1)), password, database, timeout, poolConfig); + dataCache = new RedisStandardManager(((List) nodes).get(0), Integer.parseInt(((List) nodes).get(1)), password, database, timeout, poolConfig); break; } } @@ -111,28 +98,28 @@ public class RedisCache implements DataCache { */ private JedisPoolConfig getPoolConfig(Properties properties) { JedisPoolConfig poolConfig = new JedisPoolConfig(); - int maxActive = Integer.parseInt(properties.getProperty(RedisConstants.MAX_ACTIVE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE)); + int maxActive = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MAX_ACTIVE)); poolConfig.setMaxTotal(maxActive); - boolean testOnBorrow = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_ONBORROW, RedisConstants.DEFAULT_TEST_ONBORROW_VALUE)); + boolean testOnBorrow = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_ONBORROW)); poolConfig.setTestOnBorrow(testOnBorrow); - boolean testOnReturn = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_ONRETURN, RedisConstants.DEFAULT_TEST_ONRETURN_VALUE)); + boolean testOnReturn = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_ONRETURN)); poolConfig.setTestOnReturn(testOnReturn); - int maxIdle = Integer.parseInt(properties.getProperty(RedisConstants.MAX_ACTIVE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE)); + int maxIdle = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MAX_ACTIVE)); poolConfig.setMaxIdle(maxIdle); - int minIdle = Integer.parseInt(properties.getProperty(RedisConstants.MIN_IDLE, RedisConstants.DEFAULT_MIN_IDLE_VALUE)); + int minIdle = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MIN_IDLE)); poolConfig.setMinIdle(minIdle); - boolean testWhileIdle = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_WHILEIDLE, RedisConstants.DEFAULT_TEST_WHILEIDLE_VALUE)); + boolean testWhileIdle = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_WHILEIDLE)); poolConfig.setTestWhileIdle(testWhileIdle); - int testNumPerEviction = Integer.parseInt(properties.getProperty(RedisConstants.TEST_NUMPEREVICTION, RedisConstants.DEFAULT_TEST_NUMPEREVICTION_VALUE)); + int testNumPerEviction = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_NUMPEREVICTION)); poolConfig.setNumTestsPerEvictionRun(testNumPerEviction); - long timeBetweenEviction = Long.parseLong(properties.getProperty(RedisConstants.TIME_BETWEENEVICTION, RedisConstants.DEFAULT_TIME_BETWEENEVICTION_VALUE)); + long timeBetweenEviction = Long.parseLong(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TIME_BETWEENEVICTION)); poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEviction); return poolConfig; } @@ -174,33 +161,4 @@ public class RedisCache implements DataCache { } return nodes; } - - /** To get redis data cache properties. */ - private Properties getProperties() { - Properties properties = new Properties(); - try { - String filePath = System.getProperty(SessionConstants.CATALINA_BASE).concat(File.separator) - .concat(SessionConstants.CONF).concat(File.separator) - .concat(RedisConstants.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(RedisConstants.PROPERTIES_FILE); - } - properties.load(resourceStream); - } finally { - if (resourceStream != null) { - resourceStream.close(); - } - } - } catch (IOException ex) { - LOGGER.error("Error while loading task scheduler properties", ex); - } - return properties; - } - } diff --git a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisClusterUtil.java b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisClusterManager.java similarity index 91% rename from src/main/java/tomcat/request/session/data/cache/impl/redis/RedisClusterUtil.java rename to src/main/java/tomcat/request/session/data/cache/impl/redis/RedisClusterManager.java index 95ad109..345bfda 100644 --- a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisClusterUtil.java +++ b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisClusterManager.java @@ -10,7 +10,7 @@ import redis.clients.jedis.exceptions.JedisConnectionException; import java.util.Set; /** author: Ranjith Manickam @ 12 Jul' 2018 */ -public class RedisClusterUtil extends AbstractRedisUtil { +class RedisClusterManager extends RedisManager { private final JedisCluster cluster; @@ -18,13 +18,12 @@ public class RedisClusterUtil extends AbstractRedisUtil { private static final int DEFAULT_MAX_REDIRECTIONS = 5; private static final long FAILIURE_WAIT_TIME = 4000L; - RedisClusterUtil(Set nodes, - String password, - int timeout, - JedisPoolConfig poolConfig) { + 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); + this.cluster = new JedisCluster(nodes, timeout, Protocol.DEFAULT_TIMEOUT, DEFAULT_MAX_REDIRECTIONS, password, poolConfig); } /** {@inheritDoc} */ @@ -116,5 +115,4 @@ public class RedisClusterUtil extends AbstractRedisUtil { } while (retry && tries <= NUM_RETRIES); return retVal; } - } diff --git a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisConstants.java b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisConstants.java deleted file mode 100644 index 3009898..0000000 --- a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisConstants.java +++ /dev/null @@ -1,48 +0,0 @@ -package tomcat.request.session.data.cache.impl.redis; - -/** author: Ranjith Manickam @ 12 Jul' 2018 */ -interface RedisConstants { - - // redis properties file name - String PROPERTIES_FILE = "redis-data-cache.properties"; - - // redis properties - String HOSTS = "redis.hosts"; - String CLUSTER_ENABLED = "redis.cluster.enabled"; - String SENTINEL_ENABLED = "redis.sentinel.enabled"; - - String MAX_ACTIVE = "redis.max.active"; - String TEST_ONBORROW = "redis.test.onBorrow"; - String TEST_ONRETURN = "redis.test.onReturn"; - String MAX_IDLE = "redis.max.idle"; - String MIN_IDLE = "redis.min.idle"; - String TEST_WHILEIDLE = "redis.test.whileIdle"; - String TEST_NUMPEREVICTION = "redis.test.numPerEviction"; - String TIME_BETWEENEVICTION = "redis.time.betweenEviction"; - - String PASSWORD = "redis.password"; - String DATABASE = "redis.database"; - String TIMEOUT = "redis.timeout"; - - String SENTINEL_MASTER = "redis.sentinel.master"; - String DEFAULT_SENTINEL_MASTER = "mymaster"; - - // redis property default values - String DEFAULT_MAX_ACTIVE_VALUE = "10"; - String DEFAULT_TEST_ONBORROW_VALUE = "true"; - String DEFAULT_TEST_ONRETURN_VALUE = "true"; - String DEFAULT_MAX_IDLE_VALUE = "5"; - String DEFAULT_MIN_IDLE_VALUE = "1"; - String DEFAULT_TEST_WHILEIDLE_VALUE = "true"; - String DEFAULT_TEST_NUMPEREVICTION_VALUE = "10"; - String DEFAULT_TIME_BETWEENEVICTION_VALUE = "60000"; - - String CONN_FAILED_RETRY_MSG = "Jedis connection failed, retrying..."; - - enum RedisConfigType { - DEFAULT, - SENTINEL, - CLUSTER - } - -} diff --git a/src/main/java/tomcat/request/session/data/cache/impl/redis/AbstractRedisUtil.java b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisManager.java similarity index 90% rename from src/main/java/tomcat/request/session/data/cache/impl/redis/AbstractRedisUtil.java rename to src/main/java/tomcat/request/session/data/cache/impl/redis/RedisManager.java index 0f6872c..203ccaa 100644 --- a/src/main/java/tomcat/request/session/data/cache/impl/redis/AbstractRedisUtil.java +++ b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisManager.java @@ -6,17 +6,18 @@ import redis.clients.jedis.Jedis; import redis.clients.jedis.exceptions.JedisConnectionException; import redis.clients.util.Pool; import tomcat.request.session.data.cache.DataCache; +import tomcat.request.session.data.cache.DataCacheConstants; -/** author: Ranjith Manickam @ 3 Dec' 2018 */ -public abstract class AbstractRedisUtil implements DataCache { +/** author: Ranjith Manickam @ 12 Jul' 2018 */ +abstract class RedisManager implements DataCache { private static final int NUM_RETRIES = 3; - private static final Log LOGGER = LogFactory.getLog(RedisUtil.class); + private static final Log LOGGER = LogFactory.getLog(RedisManager.class); private final Pool pool; private final long failiureWaitTime; - AbstractRedisUtil(Pool pool, long failiureWaitTime) { + RedisManager(Pool pool, long failiureWaitTime) { this.pool = pool; this.failiureWaitTime = failiureWaitTime; } @@ -118,7 +119,7 @@ public abstract class AbstractRedisUtil implements DataCache { * @param ex - jedis exception. */ void handleException(int tries, RuntimeException ex) { - LOGGER.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries); + LOGGER.error(DataCacheConstants.REDIS_CONN_FAILED_RETRY_MSG + tries); if (tries == NUM_RETRIES) { throw ex; } @@ -128,5 +129,4 @@ public abstract class AbstractRedisUtil implements DataCache { Thread.currentThread().interrupt(); } } - } diff --git a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisSentinelUtil.java b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisSentinelManager.java similarity index 56% rename from src/main/java/tomcat/request/session/data/cache/impl/redis/RedisSentinelUtil.java rename to src/main/java/tomcat/request/session/data/cache/impl/redis/RedisSentinelManager.java index f703392..92e024b 100644 --- a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisSentinelUtil.java +++ b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisSentinelManager.java @@ -6,17 +6,16 @@ import redis.clients.jedis.JedisSentinelPool; import java.util.Set; /** author: Ranjith Manickam @ 3 Dec' 2018 */ -class RedisSentinelUtil extends AbstractRedisUtil { +class RedisSentinelManager extends RedisManager { private static final long FAILIURE_WAIT_TIME = 2000L; - RedisSentinelUtil(Set nodes, - String masterName, - String password, - int database, - int timeout, - JedisPoolConfig poolConfig) { + RedisSentinelManager(Set nodes, + String masterName, + String password, + int database, + int timeout, + JedisPoolConfig poolConfig) { super(new JedisSentinelPool(masterName, nodes, poolConfig, timeout, password, database), FAILIURE_WAIT_TIME); } - } diff --git a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisUtil.java b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisStandardManager.java similarity index 54% rename from src/main/java/tomcat/request/session/data/cache/impl/redis/RedisUtil.java rename to src/main/java/tomcat/request/session/data/cache/impl/redis/RedisStandardManager.java index 81aab84..911c9b2 100644 --- a/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisUtil.java +++ b/src/main/java/tomcat/request/session/data/cache/impl/redis/RedisStandardManager.java @@ -4,17 +4,16 @@ import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; /** author: Ranjith Manickam @ 12 Jul' 2018 */ -class RedisUtil extends AbstractRedisUtil { +class RedisStandardManager extends RedisManager { private static final long FAILIURE_WAIT_TIME = 2000L; - RedisUtil(String host, - int port, - String password, - int database, - int timeout, - JedisPoolConfig poolConfig) { + RedisStandardManager(String host, + int port, + String password, + int database, + int timeout, + JedisPoolConfig poolConfig) { super(new JedisPool(poolConfig, host, port, timeout, password, database), FAILIURE_WAIT_TIME); } - } diff --git a/src/main/java/tomcat/request/session/redis/SessionHandlerValve.java b/src/main/java/tomcat/request/session/redis/SessionHandlerValve.java index 023c956..1fec7f5 100644 --- a/src/main/java/tomcat/request/session/redis/SessionHandlerValve.java +++ b/src/main/java/tomcat/request/session/redis/SessionHandlerValve.java @@ -18,7 +18,7 @@ public class SessionHandlerValve extends ValveBase { private SessionManager manager; /** To set session manager */ - public void setSessionManager(SessionManager manager) { + void setSessionManager(SessionManager manager) { this.manager = manager; } @@ -31,8 +31,7 @@ public class SessionHandlerValve extends ValveBase { LOGGER.error("Error processing request", ex); throw new BackendException(); } finally { - manager.afterRequest(request); + 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 ba6172d..0f3e7ca 100644 --- a/src/main/java/tomcat/request/session/redis/SessionManager.java +++ b/src/main/java/tomcat/request/session/redis/SessionManager.java @@ -6,7 +6,6 @@ import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleListener; import org.apache.catalina.LifecycleState; import org.apache.catalina.Valve; -import org.apache.catalina.connector.Request; import org.apache.catalina.session.ManagerBase; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -17,7 +16,7 @@ import tomcat.request.session.SessionConstants.SessionPolicy; import tomcat.request.session.SessionContext; import tomcat.request.session.SessionMetadata; import tomcat.request.session.data.cache.DataCache; -import tomcat.request.session.data.cache.impl.redis.RedisCache; +import tomcat.request.session.data.cache.DataCacheFactory; import java.io.IOException; import java.lang.reflect.Method; @@ -29,42 +28,17 @@ import java.util.Set; public class SessionManager extends ManagerBase implements Lifecycle { private DataCache dataCache; - protected SerializationUtil serializer; - protected SessionHandlerValve handlerValve; + private SerializationUtil serializer; - protected ThreadLocal sessionContext = new ThreadLocal<>(); - protected Set sessionPolicy = EnumSet.of(SessionPolicy.DEFAULT); + private ThreadLocal sessionContext = new ThreadLocal<>(); + private Set sessionPolicy = EnumSet.of(SessionPolicy.DEFAULT); private static final Log LOGGER = LogFactory.getLog(SessionManager.class); - /** To get session persist policies */ - public String getSessionPersistPolicies() { - String policyStr = null; - for (SessionPolicy policy : this.sessionPolicy) { - policyStr = (policyStr == null) ? policy.name() : policyStr.concat(",").concat(policy.name()); - } - return policyStr; - } - - /** To set session persist policies */ - public void setSessionPersistPolicies(String policyStr) { - Set policySet = EnumSet.of(SessionPolicy.DEFAULT); - String[] policyArray = policyStr.split(","); - - for (String policy : policyArray) { - policySet.add(SessionPolicy.fromName(policy)); - } - this.sessionPolicy = policySet; - } - public boolean getSaveOnChange() { return this.sessionPolicy.contains(SessionPolicy.SAVE_ON_CHANGE); } - public boolean getAlwaysSaveAfterRequest() { - return this.sessionPolicy.contains(SessionPolicy.ALWAYS_SAVE_AFTER_REQUEST); - } - /** {@inheritDoc} */ @Override public void addLifecycleListener(LifecycleListener listener) { @@ -93,8 +67,8 @@ public class SessionManager extends ManagerBase implements Lifecycle { Context context = getContextIns(); for (Valve valve : context.getPipeline().getValves()) { if (valve instanceof SessionHandlerValve) { - this.handlerValve = (SessionHandlerValve) valve; - this.handlerValve.setSessionManager(this); + SessionHandlerValve handlerValve = (SessionHandlerValve) valve; + handlerValve.setSessionManager(this); initializedValve = true; break; } @@ -103,9 +77,7 @@ public class SessionManager extends ManagerBase implements Lifecycle { if (!initializedValve) { throw new LifecycleException("Session handling valve is not initialized.."); } - initialize(); - LOGGER.info("The sessions will expire after " + (getSessionTimeout(null)) + " seconds."); context.setDistributable(true); } @@ -166,41 +138,41 @@ public class SessionManager extends ManagerBase implements Lifecycle { /** {@inheritDoc} */ @Override public Session findSession(String sessionId) throws IOException { - Session session = null; if (sessionId != null && this.sessionContext.get() != null && sessionId.equals(this.sessionContext.get().getId())) { - session = this.sessionContext.get().getSession(); - } else { - byte[] data = this.dataCache.get(sessionId); - - boolean isPersisted = false; - SessionMetadata metadata = null; - if (data == null) { - sessionId = null; - isPersisted = false; - } else { - if (Arrays.equals(SessionConstants.NULL_SESSION, data)) { - throw new IOException("NULL session data"); - } - try { - metadata = new SessionMetadata(); - Session newSession = createEmptySession(); - this.serializer.deserializeSessionData(data, newSession, metadata); - - newSession.setId(sessionId); - newSession.access(); - newSession.setNew(false); - newSession.setValid(true); - newSession.resetDirtyTracking(); - newSession.setMaxInactiveInterval(getSessionTimeout(newSession)); - - session = newSession; - isPersisted = true; - } catch (Exception ex) { - LOGGER.error("Error occurred while de-serializing the session object..", ex); - } - } - setValues(sessionId, session, isPersisted, metadata); + return this.sessionContext.get().getSession(); } + + Session session = null; + boolean isPersisted = false; + SessionMetadata metadata = null; + + byte[] data = this.dataCache.get(sessionId); + if (data == null) { + sessionId = null; + isPersisted = false; + } else { + if (Arrays.equals(SessionConstants.NULL_SESSION, data)) { + throw new IOException("NULL session data"); + } + try { + metadata = new SessionMetadata(); + Session newSession = createEmptySession(); + this.serializer.deserializeSessionData(data, newSession, metadata); + + newSession.setId(sessionId); + newSession.access(); + newSession.setNew(false); + newSession.setValid(true); + newSession.resetDirtyTracking(); + newSession.setMaxInactiveInterval(getSessionTimeout(newSession)); + + session = newSession; + isPersisted = true; + } catch (Exception ex) { + LOGGER.error("Error occurred while de-serializing the session object..", ex); + } + } + setValues(sessionId, session, isPersisted, metadata); return session; } @@ -218,20 +190,20 @@ public class SessionManager extends ManagerBase implements Lifecycle { /** {@inheritDoc} */ @Override - public void load() throws ClassNotFoundException, IOException { + public void load() { // Auto-generated method stub } /** {@inheritDoc} */ @Override - public void unload() throws IOException { + public void unload() { // Auto-generated method stub } /** To initialize the session manager. */ private void initialize() { try { - this.dataCache = new RedisCache(); + this.dataCache = new DataCacheFactory(getSessionTimeout(null)).getDataCache(); this.serializer = new SerializationUtil(); Context context = getContextIns(); ClassLoader loader = (context != null && context.getLoader() != null) ? context.getLoader().getClassLoader() : null; @@ -249,7 +221,7 @@ public class SessionManager extends ManagerBase implements Lifecycle { Session newSession = (Session) session; byte[] hash = (this.sessionContext.get() != null && this.sessionContext.get().getMetadata() != null) ? this.sessionContext.get().getMetadata().getAttributesHash() : null; - byte[] currentHash = serializer.getSessionAttributesHashCode(newSession); + byte[] currentHash = this.serializer.getSessionAttributesHashCode(newSession); if (forceSave || newSession.isDirty() @@ -259,7 +231,7 @@ public class SessionManager extends ManagerBase implements Lifecycle { SessionMetadata metadata = new SessionMetadata(); metadata.setAttributesHash(currentHash); - this.dataCache.set(newSession.getId(), serializer.serializeSessionData(newSession, metadata)); + this.dataCache.set(newSession.getId(), this.serializer.serializeSessionData(newSession, metadata)); newSession.resetDirtyTracking(); setValues(true, metadata); } @@ -274,13 +246,13 @@ public class SessionManager extends ManagerBase implements Lifecycle { } /** To process post request process. */ - public void afterRequest(Request request) { + void afterRequest() { Session session = null; try { session = (this.sessionContext.get() != null) ? this.sessionContext.get().getSession() : null; if (session != null) { if (session.isValid()) { - save(session, getAlwaysSaveAfterRequest()); + save(session, this.sessionPolicy.contains(SessionPolicy.ALWAYS_SAVE_AFTER_REQUEST)); } else { remove(session); } @@ -340,5 +312,4 @@ public class SessionManager extends ManagerBase implements Lifecycle { } throw new RuntimeException("Error occurred while creating container instance"); } - }