diff --git a/src/main/java/tomcat/request/session/SessionConstants.java b/src/main/java/tomcat/request/session/SessionConstants.java index b0a4813..cf1a6dc 100644 --- a/src/main/java/tomcat/request/session/SessionConstants.java +++ b/src/main/java/tomcat/request/session/SessionConstants.java @@ -5,6 +5,7 @@ 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/DataCacheFactory.java b/src/main/java/tomcat/request/session/data/cache/DataCacheFactory.java index e081afc..3e330c0 100644 --- a/src/main/java/tomcat/request/session/data/cache/DataCacheFactory.java +++ b/src/main/java/tomcat/request/session/data/cache/DataCacheFactory.java @@ -1,64 +1,27 @@ package tomcat.request.session.data.cache; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -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 Logger LOGGER = LoggerFactory.getLogger(DataCacheFactory.class); - + private final Properties properties; private final int sessionExpiryTime; - public DataCacheFactory(int sessionExpiryTime) { + public DataCacheFactory(Properties properties, int sessionExpiryTime) { + this.properties = properties; this.sessionExpiryTime = sessionExpiryTime; } /** To get data cache. */ public DataCache getDataCache() { - Properties properties = getApplicationProperties(); - - if (Boolean.valueOf(getProperty(properties, DataCacheConstants.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); + if (Boolean.parseBoolean(getProperty(this.properties, DataCacheConstants.LB_STICKY_SESSION_ENABLED))) { + return new StandardDataCache(this.properties, this.sessionExpiryTime); } - return properties; + return new RedisCache(this.properties); } /** 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 ccbd93a..3ba7f62 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 @@ -113,7 +113,7 @@ public Long delete(String key) { } /** Session data. */ - private class SessionData implements Serializable { + private static class SessionData implements Serializable { private byte[] value; private long lastAccessedOn; @@ -157,7 +157,7 @@ private synchronized void handleSessionData() { } /** Session data redis sync thread. */ - private class SessionDataSyncThread implements Runnable { + private static class SessionDataSyncThread implements Runnable { private final Logger LOGGER = LoggerFactory.getLogger(SessionDataSyncThread.class); @@ -190,7 +190,7 @@ public void run() { } /** Session data expiry thread. This will takes care of the session expired data removal. */ - private class SessionDataExpiryThread implements Runnable { + private static class SessionDataExpiryThread implements Runnable { private final Logger LOGGER = LoggerFactory.getLogger(SessionDataExpiryThread.class); 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 2322bdb..b2c8c25 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 @@ -56,9 +56,9 @@ public Long delete(String key) { private void initialize(Properties properties) { RedisConfigType configType; - if (Boolean.valueOf(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_CLUSTER_ENABLED))) { + if (Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_CLUSTER_ENABLED))) { configType = RedisConfigType.CLUSTER; - } else if (Boolean.valueOf(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_SENTINEL_ENABLED))) { + } else if (Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_SENTINEL_ENABLED))) { configType = RedisConfigType.SENTINEL; } else { configType = RedisConfigType.DEFAULT; @@ -73,7 +73,7 @@ private void initialize(Properties properties) { int database = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_DATABASE)); int timeout = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TIMEOUT)); - timeout = (timeout < Protocol.DEFAULT_TIMEOUT) ? Protocol.DEFAULT_TIMEOUT : timeout; + timeout = Math.max(timeout, Protocol.DEFAULT_TIMEOUT); JedisPoolConfig poolConfig = getPoolConfig(properties); switch (configType) { @@ -107,7 +107,7 @@ private JedisPoolConfig getPoolConfig(Properties properties) { boolean testOnReturn = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_ONRETURN)); poolConfig.setTestOnReturn(testOnReturn); - int maxIdle = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MAX_ACTIVE)); + 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)); @@ -143,14 +143,14 @@ private Collection getJedisNodes(String hosts, RedisConfigType configType) { switch (configType) { case CLUSTER: nodes = (nodes == null) ? new HashSet<>() : nodes; - nodes.add(new HostAndPort(hostPortArr[0], Integer.valueOf(hostPortArr[1]))); + nodes.add(new HostAndPort(hostPortArr[0], Integer.parseInt(hostPortArr[1]))); break; case SENTINEL: nodes = (nodes == null) ? new HashSet<>() : nodes; - nodes.add(new HostAndPort(hostPortArr[0], Integer.valueOf(hostPortArr[1])).toString()); + nodes.add(new HostAndPort(hostPortArr[0], Integer.parseInt(hostPortArr[1])).toString()); break; default: - int port = Integer.valueOf(hostPortArr[1]); + int port = Integer.parseInt(hostPortArr[1]); if (!hostPortArr[0].isEmpty() && port > 0) { List node = new ArrayList<>(); node.add(hostPortArr[0]); diff --git a/src/main/java/tomcat/request/session/redis/SessionManager.java b/src/main/java/tomcat/request/session/redis/SessionManager.java index 35e1edc..90793d7 100644 --- a/src/main/java/tomcat/request/session/redis/SessionManager.java +++ b/src/main/java/tomcat/request/session/redis/SessionManager.java @@ -16,29 +16,37 @@ import tomcat.request.session.SessionContext; import tomcat.request.session.SessionMetadata; import tomcat.request.session.data.cache.DataCache; +import tomcat.request.session.data.cache.DataCacheConstants; 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.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 */ public class SessionManager extends ManagerBase implements Lifecycle { + private static final Logger LOGGER = LoggerFactory.getLogger(SessionManager.class); + private DataCache dataCache; private SerializationUtil serializer; - private ThreadLocal sessionContext = new ThreadLocal<>(); private Set sessionPolicy = EnumSet.of(SessionPolicy.DEFAULT); - private static final Logger LOGGER = LoggerFactory.getLogger(SessionManager.class); - public boolean getSaveOnChange() { return this.sessionPolicy.contains(SessionPolicy.SAVE_ON_CHANGE); } + private boolean getAlwaysSaveAfterRequest() { + return this.sessionPolicy.contains(SessionPolicy.ALWAYS_SAVE_AFTER_REQUEST); + } + /** {@inheritDoc} */ @Override public void addLifecycleListener(LifecycleListener listener) { @@ -203,11 +211,15 @@ public void unload() { /** To initialize the session manager. */ private void initialize() { try { - this.dataCache = new DataCacheFactory(getSessionTimeout(null)).getDataCache(); + Properties properties = getApplicationProperties(); + this.dataCache = new DataCacheFactory(properties, 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); } catch (Exception ex) { LOGGER.error("Error occurred while initializing the session manager..", ex); throw ex; @@ -252,7 +264,7 @@ void afterRequest() { session = (this.sessionContext.get() != null) ? this.sessionContext.get().getSession() : null; if (session != null) { if (session.isValid()) { - save(session, this.sessionPolicy.contains(SessionPolicy.ALWAYS_SAVE_AFTER_REQUEST)); + save(session, getAlwaysSaveAfterRequest()); } else { remove(session); } @@ -270,7 +282,7 @@ void afterRequest() { private int getSessionTimeout(Session session) { int timeout = getContextIns().getSessionTimeout() * 60; int sessionTimeout = (session == null) ? 0 : session.getMaxInactiveInterval(); - return (sessionTimeout < timeout) ? ((timeout < 1800) ? 1800 : timeout) : sessionTimeout; + return (sessionTimeout < timeout) ? (Math.max(timeout, 1800)) : sessionTimeout; } /** To set values to session context. */ @@ -312,4 +324,45 @@ private Context getContextIns() { } throw new RuntimeException("Error occurred while creating container instance"); } + + /** To set session persistent policies */ + private void setSessionPersistentPolicies(Properties properties) { + String sessionPolicies = properties.getProperty(SessionConstants.SESSION_PERSISTENT_POLICIES); + if (sessionPolicies == null || sessionPolicies.isEmpty()) { + return; + } + + sessionPolicies = sessionPolicies.replaceAll("\\s", ""); + String[] sessionPolicyNames = sessionPolicies.split(","); + for (String sessionPolicyName : sessionPolicyNames) { + 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/resources/redis-data-cache.properties b/src/main/resources/redis-data-cache.properties index d70d021..a8ce671 100644 --- a/src/main/resources/redis-data-cache.properties +++ b/src/main/resources/redis-data-cache.properties @@ -1,23 +1,23 @@ #-- Redis data-cache configuration -#- redis hosts ex: 127.0.0.1:6379, 127.0.0.2:6379, 127.0.0.2:6380, .... +#- redis hosts. ex: 127.0.0.1:6379, 127.0.0.2:6379, 127.0.0.2:6380, .... redis.hosts=127.0.0.1:6379 -#- redis password +#- redis password. #redis.password= -#- set true to enable redis cluster mode (default value: false) +#- set true to enable redis cluster mode. (default value: false) redis.cluster.enabled=false -#- set true to enable redis sentinel mode (default value: false) +#- set true to enable redis sentinel mode. (default value: false) redis.sentinel.enabled=false -# redis sentinel master name (default value: mymaster) +# redis sentinel master name. (default value: mymaster) redis.sentinel.master=mymaster -#- redis database (default value: 0) +#- redis database. (default value: 0) #redis.database=0 -#- redis connection timeout (default value: 2000 ms) +#- redis connection timeout. (default value: 2000 ms) #redis.timeout=2000 #- enable redis and standard session mode. (default value: false) @@ -26,3 +26,9 @@ redis.sentinel.master=mymaster # 2. Session values are stored in local jvm and redis. # 3. If redis is down/not responding, requests uses jvm stored session values to process user requests. Redis comes back the values will be synced. lb.sticky-session.enabled=false + +#- session persistent policies. (default value: DEFAULT) ex: DEFAULT, SAVE_ON_CHANGE +# policies - DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST +# 1. SAVE_ON_CHANGE: every time session.setAttribute() or session.removeAttribute() is called the session will be saved. +# 2. ALWAYS_SAVE_AFTER_REQUEST: force saving after every request, regardless of whether or not the manager has detected changes to the session. +session.persistent.policies=DEFAULT \ No newline at end of file