tomcat session persistent policy update

session-42 3.0.3
Ranjith Manickam 2019-08-12 19:29:32 +05:30
parent 4eb0e433b1
commit 7ea845ebca
6 changed files with 89 additions and 66 deletions

View File

@ -5,6 +5,7 @@ public interface SessionConstants {
byte[] NULL_SESSION = "null".getBytes(); byte[] NULL_SESSION = "null".getBytes();
String CATALINA_BASE = "catalina.base"; String CATALINA_BASE = "catalina.base";
String CONF = "conf"; String CONF = "conf";
String SESSION_PERSISTENT_POLICIES = "session.persistent.policies";
enum SessionPolicy { enum SessionPolicy {
DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST; DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST;

View File

@ -1,64 +1,27 @@
package tomcat.request.session.data.cache; 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.StandardDataCache;
import tomcat.request.session.data.cache.impl.redis.RedisCache; 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; import java.util.Properties;
/** author: Ranjith Manickam @ 3 Dec' 2018 */ /** author: Ranjith Manickam @ 3 Dec' 2018 */
public class DataCacheFactory { public class DataCacheFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(DataCacheFactory.class); private final Properties properties;
private final int sessionExpiryTime; private final int sessionExpiryTime;
public DataCacheFactory(int sessionExpiryTime) { public DataCacheFactory(Properties properties, int sessionExpiryTime) {
this.properties = properties;
this.sessionExpiryTime = sessionExpiryTime; this.sessionExpiryTime = sessionExpiryTime;
} }
/** To get data cache. */ /** To get data cache. */
public DataCache getDataCache() { public DataCache getDataCache() {
Properties properties = getApplicationProperties(); if (Boolean.parseBoolean(getProperty(this.properties, DataCacheConstants.LB_STICKY_SESSION_ENABLED))) {
return new StandardDataCache(this.properties, this.sessionExpiryTime);
if (Boolean.valueOf(getProperty(properties, DataCacheConstants.LB_STICKY_SESSION_ENABLED))) {
return new StandardDataCache(properties, this.sessionExpiryTime);
} }
return new RedisCache(this.properties);
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;
} }
/** /**

View File

@ -113,7 +113,7 @@ public class StandardDataCache extends RedisCache {
} }
/** Session data. */ /** Session data. */
private class SessionData implements Serializable { private static class SessionData implements Serializable {
private byte[] value; private byte[] value;
private long lastAccessedOn; private long lastAccessedOn;
@ -157,7 +157,7 @@ public class StandardDataCache extends RedisCache {
} }
/** Session data redis sync thread. */ /** Session data redis sync thread. */
private class SessionDataSyncThread implements Runnable { private static class SessionDataSyncThread implements Runnable {
private final Logger LOGGER = LoggerFactory.getLogger(SessionDataSyncThread.class); private final Logger LOGGER = LoggerFactory.getLogger(SessionDataSyncThread.class);
@ -190,7 +190,7 @@ public class StandardDataCache extends RedisCache {
} }
/** Session data expiry thread. This will takes care of the session expired data removal. */ /** 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); private final Logger LOGGER = LoggerFactory.getLogger(SessionDataExpiryThread.class);

View File

@ -56,9 +56,9 @@ public class RedisCache implements DataCache {
private void initialize(Properties properties) { private void initialize(Properties properties) {
RedisConfigType configType; 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; 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; configType = RedisConfigType.SENTINEL;
} else { } else {
configType = RedisConfigType.DEFAULT; configType = RedisConfigType.DEFAULT;
@ -73,7 +73,7 @@ public class RedisCache implements DataCache {
int database = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_DATABASE)); int database = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_DATABASE));
int timeout = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TIMEOUT)); 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); JedisPoolConfig poolConfig = getPoolConfig(properties);
switch (configType) { switch (configType) {
@ -107,7 +107,7 @@ public class RedisCache implements DataCache {
boolean testOnReturn = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_ONRETURN)); boolean testOnReturn = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_ONRETURN));
poolConfig.setTestOnReturn(testOnReturn); 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); poolConfig.setMaxIdle(maxIdle);
int minIdle = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MIN_IDLE)); int minIdle = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MIN_IDLE));
@ -143,14 +143,14 @@ public class RedisCache implements DataCache {
switch (configType) { switch (configType) {
case CLUSTER: case CLUSTER:
nodes = (nodes == null) ? new HashSet<>() : nodes; 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; break;
case SENTINEL: case SENTINEL:
nodes = (nodes == null) ? new HashSet<>() : nodes; 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; break;
default: default:
int port = Integer.valueOf(hostPortArr[1]); int port = Integer.parseInt(hostPortArr[1]);
if (!hostPortArr[0].isEmpty() && port > 0) { if (!hostPortArr[0].isEmpty() && port > 0) {
List<String> node = new ArrayList<>(); List<String> node = new ArrayList<>();
node.add(hostPortArr[0]); node.add(hostPortArr[0]);

View File

@ -16,29 +16,37 @@ import tomcat.request.session.SessionConstants.SessionPolicy;
import tomcat.request.session.SessionContext; import tomcat.request.session.SessionContext;
import tomcat.request.session.SessionMetadata; import tomcat.request.session.SessionMetadata;
import tomcat.request.session.data.cache.DataCache; 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.DataCacheFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Properties;
import java.util.Set; import java.util.Set;
/** author: Ranjith Manickam @ 12 Jul' 2018 */ /** author: Ranjith Manickam @ 12 Jul' 2018 */
public class SessionManager extends ManagerBase implements Lifecycle { public class SessionManager extends ManagerBase implements Lifecycle {
private static final Logger LOGGER = LoggerFactory.getLogger(SessionManager.class);
private DataCache dataCache; private DataCache dataCache;
private SerializationUtil serializer; private SerializationUtil serializer;
private ThreadLocal<SessionContext> sessionContext = new ThreadLocal<>(); private ThreadLocal<SessionContext> sessionContext = new ThreadLocal<>();
private Set<SessionPolicy> sessionPolicy = EnumSet.of(SessionPolicy.DEFAULT); private Set<SessionPolicy> sessionPolicy = EnumSet.of(SessionPolicy.DEFAULT);
private static final Logger LOGGER = LoggerFactory.getLogger(SessionManager.class);
public boolean getSaveOnChange() { public boolean getSaveOnChange() {
return this.sessionPolicy.contains(SessionPolicy.SAVE_ON_CHANGE); return this.sessionPolicy.contains(SessionPolicy.SAVE_ON_CHANGE);
} }
private boolean getAlwaysSaveAfterRequest() {
return this.sessionPolicy.contains(SessionPolicy.ALWAYS_SAVE_AFTER_REQUEST);
}
/** {@inheritDoc} */ /** {@inheritDoc} */
@Override @Override
public void addLifecycleListener(LifecycleListener listener) { public void addLifecycleListener(LifecycleListener listener) {
@ -203,11 +211,15 @@ public class SessionManager extends ManagerBase implements Lifecycle {
/** To initialize the session manager. */ /** To initialize the session manager. */
private void initialize() { private void initialize() {
try { try {
this.dataCache = new DataCacheFactory(getSessionTimeout(null)).getDataCache(); Properties properties = getApplicationProperties();
this.dataCache = new DataCacheFactory(properties, getSessionTimeout(null)).getDataCache();
this.serializer = new SerializationUtil(); this.serializer = new SerializationUtil();
Context context = getContextIns(); Context context = getContextIns();
ClassLoader loader = (context != null && context.getLoader() != null) ? context.getLoader().getClassLoader() : null; ClassLoader loader = (context != null && context.getLoader() != null) ? context.getLoader().getClassLoader() : null;
this.serializer.setClassLoader(loader); this.serializer.setClassLoader(loader);
setSessionPersistentPolicies(properties);
} catch (Exception ex) { } catch (Exception ex) {
LOGGER.error("Error occurred while initializing the session manager..", ex); LOGGER.error("Error occurred while initializing the session manager..", ex);
throw ex; throw ex;
@ -252,7 +264,7 @@ public class SessionManager extends ManagerBase implements Lifecycle {
session = (this.sessionContext.get() != null) ? this.sessionContext.get().getSession() : null; session = (this.sessionContext.get() != null) ? this.sessionContext.get().getSession() : null;
if (session != null) { if (session != null) {
if (session.isValid()) { if (session.isValid()) {
save(session, this.sessionPolicy.contains(SessionPolicy.ALWAYS_SAVE_AFTER_REQUEST)); save(session, getAlwaysSaveAfterRequest());
} else { } else {
remove(session); remove(session);
} }
@ -270,7 +282,7 @@ public class SessionManager extends ManagerBase implements Lifecycle {
private int getSessionTimeout(Session session) { private int getSessionTimeout(Session session) {
int timeout = getContextIns().getSessionTimeout() * 60; int timeout = getContextIns().getSessionTimeout() * 60;
int sessionTimeout = (session == null) ? 0 : session.getMaxInactiveInterval(); 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. */ /** To set values to session context. */
@ -312,4 +324,45 @@ public class SessionManager extends ManagerBase implements Lifecycle {
} }
throw new RuntimeException("Error occurred while creating container instance"); 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;
}
} }

View File

@ -1,23 +1,23 @@
#-- Redis data-cache configuration #-- 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.hosts=127.0.0.1:6379
#- redis password #- 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 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.enabled=false
# redis sentinel master name (default value: mymaster) # redis sentinel master name. (default value: mymaster)
redis.sentinel.master=mymaster redis.sentinel.master=mymaster
#- redis database (default value: 0) #- redis database. (default value: 0)
#redis.database=0 #redis.database=0
#- redis connection timeout (default value: 2000 ms) #- redis connection timeout. (default value: 2000 ms)
#redis.timeout=2000 #redis.timeout=2000
#- enable redis and standard session mode. (default value: false) #- 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. # 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. # 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 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