Skip to content

Commit

Permalink
tomcat session persistent policy update
Browse files Browse the repository at this point in the history
  • Loading branch information
ran-jit committed Aug 12, 2019
1 parent 4eb0e43 commit 7ea845e
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 66 deletions.
1 change: 1 addition & 0 deletions src/main/java/tomcat/request/session/SessionConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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<String> node = new ArrayList<>();
node.add(hostPortArr[0]);
Expand Down
65 changes: 59 additions & 6 deletions src/main/java/tomcat/request/session/redis/SessionManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -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> sessionContext = new ThreadLocal<>();
private Set<SessionPolicy> 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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand All @@ -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. */
Expand Down Expand Up @@ -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;
}
}
20 changes: 13 additions & 7 deletions src/main/resources/redis-data-cache.properties
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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

0 comments on commit 7ea845e

Please sign in to comment.