Tomcat 7 redis cluster session manager
Tomcat 7 redis cluster session managerpull/1/merge
parent
119a084815
commit
cee0ead739
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry including="**/*.java" kind="src" path="resources"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="lib" path="lib/catalina.jar"/>
|
||||
<classpathentry kind="lib" path="lib/servlet-api.jar"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>TomcatRedisSessionManager</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.wst.common.project.facet.core.builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
|
@ -0,0 +1,46 @@
|
|||
# Tomcat-Redis-Cluster-Enabled-Session-Manager
|
||||
|
||||
Redis session manager is pluggable one. It uses to store sessions into Redis for easy distribution of HTTP Requests across a cluster of Tomcat servers. Sessions are implemented as as non-sticky i.e, each request is forwarded to any server in round-robin manner.
|
||||
|
||||
The HTTP Requests session setAttribute(name, value) method stores the session into Redis (must be Serializable) immediately and the session getAttribute(name) method request directly from Redis. Also, the inactive sessions has been removed based on the session time-out configuration.
|
||||
|
||||
It supports, both single redis master and redis cluster based on the RedisDataCache.properties configuration.
|
||||
|
||||
Going forward, we no need to enable sticky session (JSESSIONID) in Load balancer.
|
||||
|
||||
## Supports:
|
||||
* Apache Tomcat 7
|
||||
|
||||
## Downloads:
|
||||
|
||||
##### Pre-requisite:
|
||||
1. jedis-2.8.0.jar
|
||||
2. commons-pool2-2.4.2.jar
|
||||
3. commons-logging-1.2.jar
|
||||
|
||||
**Tomcat Redis Cluster Enabled Session Manager jar is available in below location**
|
||||
|
||||
https://github.com/ran-jit/TomcatRedisClusterEnabledSessionManager/releases/download/1.0/TomcatClusterEnabledRedisSessionManager-1.0.zip
|
||||
|
||||
|
||||
####Steps to be done,
|
||||
1. Move the downloaded jars to tomcat/lib directory
|
||||
* **$catalina.home/lib/**
|
||||
|
||||
2. Add tomcat system property "catalina.base"
|
||||
* **catalina.base="TOMCAT_LOCATION"**
|
||||
|
||||
3. Extract downloaded jar (TomcatClusterEnabledRedisSessionManager-1.0.jar) to configure Redis credentials in RedisDataCache.properties file and move the file to tomcat/conf directory
|
||||
* **tomcat/conf/RedisDataCache.properties**
|
||||
|
||||
4. Add the below two lines in tomcat/conf/context.xml
|
||||
* **<Valve className="com.r.tomcat.session.management.RequestSessionHandlerValve" />**
|
||||
* **<Manager className="com.r.tomcat.session.management.RequestSessionManager" />**
|
||||
|
||||
5. Verify the session expiration time in tomcat/conf/web.xml
|
||||
* **<session-config>**
|
||||
* **<session-timeout>60</session-timeout>**
|
||||
* **</session-config>**
|
||||
|
||||
###Note:
|
||||
* The Redis session manager supports, both single redis master and redis cluster based on the redis.properties configuration.
|
|
@ -0,0 +1,47 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>TomcatClusterEnabledRedisSessionManager</groupId>
|
||||
<artifactId>TomcatClusterEnabledRedisSessionManager</artifactId>
|
||||
<version>1.0</version>
|
||||
<name>TomcatClusterEnabledRedisSessionManager</name>
|
||||
<description>Tomcat 7 cluster enabled redis session manager. it supports Redis both single master and cluster</description>
|
||||
<build>
|
||||
<sourceDirectory>src</sourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>resources</directory>
|
||||
<excludes>
|
||||
<exclude>**/*.java</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>2.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
<version>2.4.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<version>1.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* Redis session manager is the pluggable one. It uses to store session objects from Tomcat catalina to Redis data cache.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
Pre-requisite:
|
||||
--------------
|
||||
1. jedis-2.8.0.jar (Available in - http://central.maven.org/maven2/redis/clients/jedis/2.8.0/jedis-2.8.0.jar)
|
||||
2. commons-pool2-2.2.jar (Available in - http://central.maven.org/maven2/org/apache/commons/commons-pool2/2.2/commons-pool2-2.2.jar)
|
||||
3. commons-logging-1.1.jar (Available in - http://central.maven.org/maven2/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar)
|
||||
Note: Download all the above three jars and move it into tomcat/lib directory
|
||||
|
||||
|
||||
Steps to be done,
|
||||
-----------------
|
||||
1. Add Tomcat system property "catalina.home"
|
||||
catalina.home="TOMCAT_ROOT_LOCATION"
|
||||
example: c:/ranjith/apache-tomcat-8.0.32
|
||||
|
||||
2. Download the TomcatRedisSessionManager-1.1.jar
|
||||
Available in -
|
||||
|
||||
3. Move the downloaded jar (TomcatRedisSessionManager-1.1.jar) to tomcat/lib directory
|
||||
$catalina.home/lib/TomcatRedisSessionManager-1.1.jar
|
||||
example: c:/ranjith/apache-tomcat-8.0.32/lib/TomcatRedisSessionManager-1.1.jar
|
||||
|
||||
4. Extract downloaded jar (TomcatRedisSessionManager-1.1.jar) to configure redis data cache credentials in "RedisDataCache.properties" file and move the file to $catalina.home/conf directory
|
||||
$catalina.home/conf/RedisDataCache.properties
|
||||
example: c:/ranjith/apache-tomcat-8.0.32/conf/RedisDataCache.properties
|
||||
|
||||
5. Add the below two lines in $catalina.home/conf/context.xml
|
||||
<Valve className="com.r.tomcat.session.management.RequestSessionHandlerValve" />
|
||||
<Manager className="com.r.tomcat.session.management.RequestSessionManager" />
|
||||
|
||||
6. Verify the session expiration time in $catalina.home/conf/web.xml
|
||||
<session-config>
|
||||
<session-timeout>EXPIRATION TIME IN MINUTES</session-timeout>
|
||||
</session-config>
|
||||
|
||||
Note:
|
||||
-----
|
||||
* This clustering implementation supports, both redis single node and cluster environment based on the RedisDataCache.properties configuration.
|
|
@ -0,0 +1,6 @@
|
|||
# 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=
|
||||
# set true to enable redis cluster mode
|
||||
redis.cluster.enabled=false
|
|
@ -0,0 +1,24 @@
|
|||
package com.r.tomcat.session.data.cache;
|
||||
|
||||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* This interface holds cache implementation
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
public interface IRequestSessionCacheUtils
|
||||
{
|
||||
public boolean isAvailable();
|
||||
|
||||
public void setByteArray(String key, byte[] value);
|
||||
|
||||
public byte[] getByteArray(String key);
|
||||
|
||||
public void deleteKey(String key);
|
||||
|
||||
public Long setStringIfKeyNotExists(byte[] key, byte[] value);
|
||||
|
||||
public void expire(String key, int ttl);
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
package com.r.tomcat.session.data.cache;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
|
||||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* This cache util is uses to store and retrieve the session object in Redis data cache non cluster
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
public class RedisCacheUtil implements IRequestSessionCacheUtils
|
||||
{
|
||||
private Log log = LogFactory.getLog(RedisCacheUtil.class);
|
||||
|
||||
public boolean available = false;
|
||||
|
||||
private static int numRetries = 3;
|
||||
|
||||
private RedisManager manager = null;
|
||||
|
||||
RedisCacheUtil(Properties properties) throws Exception {
|
||||
try {
|
||||
manager = RedisManager.createInstance(properties);
|
||||
} catch (Exception e) {
|
||||
this.available = false;
|
||||
log.error("Exception initializing Redis: ", e);
|
||||
}
|
||||
this.available = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return available;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setByteArray(String key, byte[] value) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
if (key != null && value != null) {
|
||||
Jedis jedis = manager.getJedis();
|
||||
jedis.set(key.getBytes(), value);
|
||||
jedis.close();
|
||||
}
|
||||
sucess = true;
|
||||
} catch (JedisConnectionException e) {
|
||||
log.error("Jedis connection failed, retrying..." + tries);
|
||||
}
|
||||
} while (!sucess && tries <= numRetries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long setStringIfKeyNotExists(byte[] key, byte[] value) {
|
||||
int tries = 0;
|
||||
Long retVal = null;
|
||||
boolean sucess = false;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
if (key != null && value != null) {
|
||||
Jedis jedis = manager.getJedis();
|
||||
retVal = jedis.setnx(key, value);
|
||||
jedis.close();
|
||||
}
|
||||
sucess = true;
|
||||
} catch (JedisConnectionException e) {
|
||||
log.error("Jedis connection failed, retrying..." + tries);
|
||||
}
|
||||
} while (!sucess && tries <= numRetries);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expire(String key, int ttl) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
Jedis jedis = manager.getJedis();
|
||||
jedis.expire(key, ttl);
|
||||
jedis.close();
|
||||
sucess = true;
|
||||
} catch (JedisConnectionException e) {
|
||||
log.error("Jedis connection failed, retrying..." + tries);
|
||||
}
|
||||
} while (!sucess && tries <= numRetries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getByteArray(String key) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
byte[] array = new byte[1];
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
if (key != null) {
|
||||
Jedis jedis = manager.getJedis();
|
||||
array = jedis.get(key.getBytes());
|
||||
jedis.close();
|
||||
}
|
||||
sucess = true;
|
||||
} catch (JedisConnectionException e) {
|
||||
log.error("Jedis connection failed, retrying..." + tries);
|
||||
}
|
||||
} while (!sucess && tries <= numRetries);
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteKey(String key) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
if (key != null) {
|
||||
Jedis jedis = manager.getJedis();
|
||||
jedis.del(key);
|
||||
jedis.close();
|
||||
}
|
||||
sucess = true;
|
||||
} catch (JedisConnectionException e) {
|
||||
log.error("Jedis connection failed, retrying..." + tries);
|
||||
}
|
||||
} while (!sucess && tries <= numRetries);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
package com.r.tomcat.session.data.cache;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
|
||||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* This cache util is uses to store and retrieve the session object in Redis data cache cluster
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
public class RedisClusterCacheUtil implements IRequestSessionCacheUtils
|
||||
{
|
||||
private Log log = LogFactory.getLog(RedisClusterCacheUtil.class);
|
||||
|
||||
public boolean available = false;
|
||||
|
||||
private static int numRetries = 30;
|
||||
|
||||
private RedisClusterManager clusterManager = null;
|
||||
|
||||
RedisClusterCacheUtil(Properties properties) throws Exception {
|
||||
try {
|
||||
clusterManager = RedisClusterManager.createInstance(properties);
|
||||
} catch (Exception e) {
|
||||
this.available = false;
|
||||
log.error("Exception initializing Redis cluster: " + e);
|
||||
}
|
||||
this.available = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return available;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setByteArray(String key, byte[] value) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
if (key != null && value != null) {
|
||||
clusterManager.getJedis().set(key.getBytes(), value);
|
||||
}
|
||||
sucess = true;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException e) {
|
||||
log.error("Jedis connection failed, retrying..." + tries);
|
||||
waitforFailover();
|
||||
}
|
||||
} while (!sucess && tries <= numRetries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long setStringIfKeyNotExists(byte[] key, byte[] value) {
|
||||
int tries = 0;
|
||||
Long retVal = null;
|
||||
boolean sucess = false;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
if (key != null && value != null) {
|
||||
retVal = clusterManager.getJedis().setnx(key, value);
|
||||
}
|
||||
sucess = true;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException e) {
|
||||
log.error("Jedis connection failed, retrying..." + tries);
|
||||
waitforFailover();
|
||||
}
|
||||
} while (!sucess && tries <= numRetries);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expire(String key, int ttl) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
clusterManager.getJedis().expire(key, ttl);
|
||||
sucess = true;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException e) {
|
||||
log.error("Jedis connection failed, retrying..." + tries);
|
||||
waitforFailover();
|
||||
}
|
||||
} while (!sucess && tries <= numRetries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getByteArray(String key) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
byte[] array = new byte[1];
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
if (key != null) {
|
||||
array = clusterManager.getJedis().get(key.getBytes());
|
||||
}
|
||||
sucess = true;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException e) {
|
||||
log.error("Jedis connection failed, retrying..." + tries);
|
||||
waitforFailover();
|
||||
}
|
||||
} while (!sucess && tries <= numRetries);
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteKey(String key) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
if (key != null) {
|
||||
clusterManager.getJedis().del(key);
|
||||
}
|
||||
sucess = true;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException e) {
|
||||
log.error("Jedis connection failed, retrying..." + tries);
|
||||
waitforFailover();
|
||||
}
|
||||
} while (!sucess && tries <= numRetries);
|
||||
}
|
||||
|
||||
private void waitforFailover() {
|
||||
try {
|
||||
Thread.sleep(4000);
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package com.r.tomcat.session.data.cache;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import redis.clients.jedis.HostAndPort;
|
||||
import redis.clients.jedis.JedisCluster;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.Protocol;
|
||||
|
||||
import com.r.tomcat.session.data.cache.constants.RedisConstants;
|
||||
|
||||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* This manager is uses to initialize the Redis data cache cluster client
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
public class RedisClusterManager
|
||||
{
|
||||
private Properties properties;
|
||||
|
||||
private static JedisCluster jedisCluster;
|
||||
|
||||
private static RedisClusterManager instance;
|
||||
|
||||
private RedisClusterManager(Properties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public static RedisClusterManager createInstance(Properties properties) {
|
||||
instance = new RedisClusterManager(properties);
|
||||
instance.connect();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public final static RedisClusterManager getInstance() throws Exception {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
JedisPoolConfig poolConfig = new JedisPoolConfig();
|
||||
int maxActive = Integer.parseInt(properties.getProperty(RedisConstants.MAX_ACTIVE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE));
|
||||
poolConfig.setMaxTotal(maxActive);
|
||||
boolean testOnBorrow = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_ONBORROW, RedisConstants.DEFAULT_TEST_ONBORROW_VALUE));
|
||||
poolConfig.setTestOnBorrow(testOnBorrow);
|
||||
boolean testOnReturn = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_ONRETURN, RedisConstants.DEFAULT_TEST_ONRETURN_VALUE));
|
||||
poolConfig.setTestOnReturn(testOnReturn);
|
||||
int maxIdle = Integer.parseInt(properties.getProperty(RedisConstants.MAX_IDLE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE));
|
||||
poolConfig.setMaxIdle(maxIdle);
|
||||
int minIdle = Integer.parseInt(properties.getProperty(RedisConstants.MIN_IDLE, RedisConstants.DEFAULT_MIN_IDLE_VALUE));
|
||||
poolConfig.setMinIdle(minIdle);
|
||||
boolean testWhileIdle = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_WHILEIDLE, RedisConstants.DEFAULT_TEST_WHILEIDLE_VALUE));
|
||||
poolConfig.setTestWhileIdle(testWhileIdle);
|
||||
int testNumPerEviction = Integer.parseInt(properties.getProperty(RedisConstants.TEST_NUMPEREVICTION, RedisConstants.DEFAULT_TEST_NUMPEREVICTION_VALUE));
|
||||
poolConfig.setNumTestsPerEvictionRun(testNumPerEviction);
|
||||
long timeBetweenEviction = Long.parseLong(properties.getProperty(RedisConstants.TIME_BETWEENEVICTION, RedisConstants.DEFAULT_TIME_BETWEENEVICTION_VALUE));
|
||||
poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEviction);
|
||||
jedisCluster = new JedisCluster(getJedisClusterNodesSet(properties.getProperty(RedisConstants.HOSTS, Protocol.DEFAULT_HOST.concat(":").concat(String.valueOf(Protocol.DEFAULT_PORT)))), poolConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* method to get the cluster nodes
|
||||
*
|
||||
* @param hosts
|
||||
* @return
|
||||
*/
|
||||
private Set<HostAndPort> getJedisClusterNodesSet(String hosts) {
|
||||
Set<HostAndPort> nodes = new HashSet<HostAndPort>();
|
||||
hosts = hosts.replaceAll("\\s", "");
|
||||
String[] hostPorts = hosts.split(",");
|
||||
for (String hostPort : hostPorts) {
|
||||
String[] hostPortArr = hostPort.split(":");
|
||||
nodes.add(new HostAndPort(hostPortArr[0], Integer.valueOf(hostPortArr[1])));
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public JedisCluster getJedis() {
|
||||
return jedisCluster;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package com.r.tomcat.session.data.cache;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.Protocol;
|
||||
|
||||
import com.r.tomcat.session.data.cache.constants.RedisConstants;
|
||||
|
||||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* This manager is uses to initialize the Redis data cache client
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
public class RedisManager
|
||||
{
|
||||
private static RedisManager instance;
|
||||
|
||||
private static JedisPool pool;
|
||||
|
||||
private Properties properties;
|
||||
|
||||
private RedisManager(Properties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public static RedisManager createInstance(Properties properties) {
|
||||
instance = new RedisManager(properties);
|
||||
instance.connect();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public final static RedisManager getInstance() throws Exception {
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
JedisPoolConfig poolConfig = new JedisPoolConfig();
|
||||
int maxActive = Integer.parseInt(properties.getProperty(RedisConstants.MAX_ACTIVE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE));
|
||||
poolConfig.setMaxTotal(maxActive);
|
||||
boolean testOnBorrow = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_ONBORROW, RedisConstants.DEFAULT_TEST_ONBORROW_VALUE));
|
||||
poolConfig.setTestOnBorrow(testOnBorrow);
|
||||
boolean testOnReturn = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_ONRETURN, RedisConstants.DEFAULT_TEST_ONRETURN_VALUE));
|
||||
poolConfig.setTestOnReturn(testOnReturn);
|
||||
int maxIdle = Integer.parseInt(properties.getProperty(RedisConstants.MAX_ACTIVE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE));
|
||||
poolConfig.setMaxIdle(maxIdle);
|
||||
int minIdle = Integer.parseInt(properties.getProperty(RedisConstants.MIN_IDLE, RedisConstants.DEFAULT_MIN_IDLE_VALUE));
|
||||
poolConfig.setMinIdle(minIdle);
|
||||
boolean testWhileIdle = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_WHILEIDLE, RedisConstants.DEFAULT_TEST_WHILEIDLE_VALUE));
|
||||
poolConfig.setTestWhileIdle(testWhileIdle);
|
||||
int testNumPerEviction = Integer.parseInt(properties.getProperty(RedisConstants.TEST_NUMPEREVICTION, RedisConstants.DEFAULT_TEST_NUMPEREVICTION_VALUE));
|
||||
poolConfig.setNumTestsPerEvictionRun(testNumPerEviction);
|
||||
long timeBetweenEviction = Long.parseLong(properties.getProperty(RedisConstants.TIME_BETWEENEVICTION, RedisConstants.DEFAULT_TIME_BETWEENEVICTION_VALUE));
|
||||
poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEviction);
|
||||
String hosts = properties.getProperty(RedisConstants.HOSTS, Protocol.DEFAULT_HOST.concat(":").concat(String.valueOf(Protocol.DEFAULT_PORT)));
|
||||
String host = null;
|
||||
int port = 0;
|
||||
hosts = hosts.replaceAll("\\s", "");
|
||||
String[] hostPorts = hosts.split(",");
|
||||
for (String hostPort : hostPorts) {
|
||||
String[] hostPortArr = hostPort.split(":");
|
||||
host = hostPortArr[0];
|
||||
port = Integer.valueOf(hostPortArr[1]);
|
||||
if (!host.isEmpty() && port != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
String password = properties.getProperty(RedisConstants.PASSWORD);
|
||||
if (password == null || password == "" || password.isEmpty()) {
|
||||
pool = new JedisPool(poolConfig, host, port);
|
||||
} else {
|
||||
pool = new JedisPool(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT, password);
|
||||
}
|
||||
}
|
||||
|
||||
public Jedis getJedis() {
|
||||
return pool.getResource();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package com.r.tomcat.session.data.cache;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import com.r.tomcat.session.data.cache.constants.RedisConstants;
|
||||
|
||||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* This factory is uses to get the request session data cache
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
public class RequestSessionCacheFactory
|
||||
{
|
||||
private static Log log = LogFactory.getLog(RequestSessionCacheFactory.class);
|
||||
|
||||
protected static IRequestSessionCacheUtils requestCache;
|
||||
|
||||
public static synchronized IRequestSessionCacheUtils getInstance() {
|
||||
try {
|
||||
if (requestCache == null) {
|
||||
Properties properties = getRedisProperties();
|
||||
if (!Boolean.valueOf(properties.getProperty(RedisConstants.IS_CLUSTER_ENABLED, RedisConstants.DEFAULT_IS_CLUSTER_ENABLED))) {
|
||||
requestCache = new RedisCacheUtil(properties);
|
||||
} else {
|
||||
requestCache = new RedisClusterCacheUtil(properties);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error occurred initializing redis", e);
|
||||
}
|
||||
return requestCache;
|
||||
}
|
||||
|
||||
private static Properties getRedisProperties() throws Exception {
|
||||
Properties properties = null;
|
||||
try {
|
||||
if (properties == null || properties.isEmpty()) {
|
||||
InputStream resourceStream = null;
|
||||
try {
|
||||
resourceStream = null;
|
||||
properties = new Properties();
|
||||
File file = new File(System.getProperty("catalina.base").concat(File.separator).concat("conf").concat(File.separator).concat(RedisConstants.REDIS_DATA_CACHE_PROPERTIES_FILE));
|
||||
if (file.exists()) {
|
||||
resourceStream = new FileInputStream(file);
|
||||
}
|
||||
if (resourceStream == null) {
|
||||
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
resourceStream = loader.getResourceAsStream(RedisConstants.REDIS_DATA_CACHE_PROPERTIES_FILE);
|
||||
}
|
||||
properties.load(resourceStream);
|
||||
} finally {
|
||||
resourceStream.close();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error occurred fetching redis informations", e);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package com.r.tomcat.session.data.cache.constants;
|
||||
|
||||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* Redis data cache constants
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
public class RedisConstants
|
||||
{
|
||||
public static final String REDIS_DATA_CACHE_PROPERTIES_FILE = "RedisDataCache.properties";
|
||||
|
||||
// Redis properties
|
||||
public static final String MAX_ACTIVE = "redis.max.active";
|
||||
public static final String TEST_ONBORROW = "redis.test.onBorrow";
|
||||
public static final String TEST_ONRETURN = "redis.test.onReturn";
|
||||
public static final String MAX_IDLE = "redis.max.idle";
|
||||
public static final String MIN_IDLE = "redis.min.idle";
|
||||
public static final String TEST_WHILEIDLE = "redis.test.whileIdle";
|
||||
public static final String TEST_NUMPEREVICTION = "redis.test.numPerEviction";
|
||||
public static final String TIME_BETWEENEVICTION = "redis.time.betweenEviction";
|
||||
public static final String HOSTS = "redis.hosts";
|
||||
public static final String PASSWORD = "redis.password";
|
||||
public static final String IS_CLUSTER_ENABLED = "redis.cluster.enabled";
|
||||
|
||||
// Redis property default values
|
||||
public static final String DEFAULT_MAX_ACTIVE_VALUE = "10";
|
||||
public static final String DEFAULT_TEST_ONBORROW_VALUE = "true";
|
||||
public static final String DEFAULT_TEST_ONRETURN_VALUE = "true";
|
||||
public static final String DEFAULT_MAX_IDLE_VALUE = "5";
|
||||
public static final String DEFAULT_MIN_IDLE_VALUE = "1";
|
||||
public static final String DEFAULT_TEST_WHILEIDLE_VALUE = "true";
|
||||
public static final String DEFAULT_TEST_NUMPEREVICTION_VALUE = "10";
|
||||
public static final String DEFAULT_TIME_BETWEENEVICTION_VALUE = "60000";
|
||||
public static final String DEFAULT_IS_CLUSTER_ENABLED = "false";
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package com.r.tomcat.session.management;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.apache.catalina.Manager;
|
||||
import org.apache.catalina.session.StandardSession;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* This class is uses to store and retrieve the HTTP request session objects from catalina to data cache
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
public class CustomRequestSession extends StandardSession
|
||||
{
|
||||
private static final long serialVersionUID = 8237845843135996014L;
|
||||
|
||||
private final Log log = LogFactory.getLog(CustomRequestSession.class);
|
||||
|
||||
protected Boolean dirty;
|
||||
|
||||
protected HashMap<String, Object> changedAttributes;
|
||||
|
||||
protected static Boolean manualDirtyTrackingSupportEnabled = false;
|
||||
|
||||
protected static String manualDirtyTrackingAttributeKey = "__changed__";
|
||||
|
||||
public static void setManualDirtyTrackingSupportEnabled(Boolean enabled) {
|
||||
manualDirtyTrackingSupportEnabled = enabled;
|
||||
}
|
||||
|
||||
public static void setManualDirtyTrackingAttributeKey(String key) {
|
||||
manualDirtyTrackingAttributeKey = key;
|
||||
}
|
||||
|
||||
public CustomRequestSession(Manager manager) {
|
||||
super(manager);
|
||||
resetDirtyTracking();
|
||||
}
|
||||
|
||||
public Boolean isDirty() {
|
||||
return dirty || !changedAttributes.isEmpty();
|
||||
}
|
||||
|
||||
public HashMap<String, Object> getChangedAttributes() {
|
||||
return changedAttributes;
|
||||
}
|
||||
|
||||
public void resetDirtyTracking() {
|
||||
changedAttributes = new HashMap<>();
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String key, Object value) {
|
||||
if (manualDirtyTrackingSupportEnabled && manualDirtyTrackingAttributeKey.equals(key)) {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
Object oldValue = getAttribute(key);
|
||||
super.setAttribute(key, value);
|
||||
|
||||
if ((value != null || oldValue != null) && (value == null && oldValue != null || oldValue == null && value != null || !value.getClass().isInstance(oldValue) || !value.equals(oldValue))) {
|
||||
if (this.manager instanceof RequestSessionManager && ((RequestSessionManager) this.manager).getSaveOnChange()) {
|
||||
try {
|
||||
((RequestSessionManager) this.manager).save(this, true);
|
||||
} catch (Exception ex) {
|
||||
log.error("Error saving session on setAttribute (triggered by saveOnChange=true): " + ex.getMessage());
|
||||
}
|
||||
} else {
|
||||
changedAttributes.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name) {
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getAttributeNames() {
|
||||
return super.getAttributeNames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
super.removeAttribute(name);
|
||||
if (this.manager instanceof RequestSessionManager && ((RequestSessionManager) this.manager).getSaveOnChange()) {
|
||||
try {
|
||||
((RequestSessionManager) this.manager).save(this, true);
|
||||
} catch (Exception ex) {
|
||||
log.error("Error saving session on removeAttribute (triggered by saveOnChange=true): " + ex.getMessage());
|
||||
}
|
||||
} else {
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrincipal(Principal principal) {
|
||||
dirty = true;
|
||||
super.setPrincipal(principal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeObjectData(java.io.ObjectOutputStream out) throws IOException {
|
||||
super.writeObjectData(out);
|
||||
out.writeLong(this.getCreationTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readObjectData(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
super.readObjectData(in);
|
||||
this.setCreationTime(in.readLong());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.r.tomcat.session.management;
|
||||
|
||||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* This class is uses to store and retrieve the HTTP request session objects from catalina to data cache
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
public class DeserializedSessionContainer
|
||||
{
|
||||
public final CustomRequestSession session;
|
||||
|
||||
public final SessionSerializationMetadata metadata;
|
||||
|
||||
public DeserializedSessionContainer(CustomRequestSession session, SessionSerializationMetadata metadata) {
|
||||
this.session = session;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package com.r.tomcat.session.management;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.apache.catalina.connector.Response;
|
||||
import org.apache.catalina.valves.ValveBase;
|
||||
|
||||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* This class is uses to store and retrieve the HTTP request session objects from catalina to data cache
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
public class RequestSessionHandlerValve extends ValveBase
|
||||
{
|
||||
private RequestSessionManager manager;
|
||||
|
||||
public void setRedisSessionManager(RequestSessionManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(Request request, Response response) throws IOException, ServletException {
|
||||
try {
|
||||
getNext().invoke(request, response);
|
||||
} finally {
|
||||
manager.afterRequest(request);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,345 @@
|
|||
package com.r.tomcat.session.management;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.catalina.Lifecycle;
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.LifecycleListener;
|
||||
import org.apache.catalina.LifecycleState;
|
||||
import org.apache.catalina.Loader;
|
||||
import org.apache.catalina.Session;
|
||||
import org.apache.catalina.Valve;
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.apache.catalina.session.ManagerBase;
|
||||
import org.apache.catalina.util.LifecycleSupport;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import com.r.tomcat.session.data.cache.IRequestSessionCacheUtils;
|
||||
import com.r.tomcat.session.data.cache.RequestSessionCacheFactory;
|
||||
|
||||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* This class is uses to store and retrieve the HTTP request session objects from catalina to data cache
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
public class RequestSessionManager extends ManagerBase implements Lifecycle
|
||||
{
|
||||
private final Log log = LogFactory.getLog(RequestSessionManager.class);
|
||||
|
||||
private IRequestSessionCacheUtils requestSessionCacheUtils;
|
||||
|
||||
protected SessionDataSerializer serializer;
|
||||
|
||||
protected RequestSessionHandlerValve handlerValve;
|
||||
|
||||
protected byte[] NULL_SESSION = "null".getBytes();
|
||||
|
||||
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
|
||||
|
||||
protected ThreadLocal<String> currentSessionId = new ThreadLocal<>();
|
||||
|
||||
protected ThreadLocal<CustomRequestSession> currentSession = new ThreadLocal<>();
|
||||
|
||||
protected ThreadLocal<Boolean> currentSessionIsPersisted = new ThreadLocal<>();
|
||||
|
||||
protected EnumSet<SessionPersistPolicy> sessionPersistPoliciesSet = EnumSet.of(SessionPersistPolicy.DEFAULT);
|
||||
|
||||
protected ThreadLocal<SessionSerializationMetadata> currentSessionSerializationMetadata = new ThreadLocal<>();
|
||||
|
||||
enum SessionPersistPolicy {
|
||||
DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST;
|
||||
|
||||
static SessionPersistPolicy fromName(String name) {
|
||||
for (SessionPersistPolicy policy : SessionPersistPolicy.values()) {
|
||||
if (policy.name().equalsIgnoreCase(name)) {
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid session persist policy [" + name + "]. Must be one of " + Arrays.asList(SessionPersistPolicy.values()) + ".");
|
||||
}
|
||||
}
|
||||
|
||||
public String getSessionPersistPolicies() {
|
||||
StringBuilder policies = new StringBuilder();
|
||||
for (Iterator<SessionPersistPolicy> iter = this.sessionPersistPoliciesSet.iterator(); iter.hasNext();) {
|
||||
SessionPersistPolicy policy = iter.next();
|
||||
policies.append(policy.name());
|
||||
if (iter.hasNext()) {
|
||||
policies.append(",");
|
||||
}
|
||||
}
|
||||
return policies.toString();
|
||||
}
|
||||
|
||||
public void setSessionPersistPolicies(String sessionPersistPolicies) {
|
||||
String[] policyArray = sessionPersistPolicies.split(",");
|
||||
EnumSet<SessionPersistPolicy> policySet = EnumSet.of(SessionPersistPolicy.DEFAULT);
|
||||
for (String policyName : policyArray) {
|
||||
SessionPersistPolicy policy = SessionPersistPolicy.fromName(policyName);
|
||||
policySet.add(policy);
|
||||
}
|
||||
this.sessionPersistPoliciesSet = policySet;
|
||||
}
|
||||
|
||||
public boolean getSaveOnChange() {
|
||||
return this.sessionPersistPoliciesSet.contains(SessionPersistPolicy.SAVE_ON_CHANGE);
|
||||
}
|
||||
|
||||
public boolean getAlwaysSaveAfterRequest() {
|
||||
return this.sessionPersistPoliciesSet.contains(SessionPersistPolicy.ALWAYS_SAVE_AFTER_REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLifecycleListener(LifecycleListener listener) {
|
||||
lifecycle.addLifecycleListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LifecycleListener[] findLifecycleListeners() {
|
||||
return lifecycle.findLifecycleListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeLifecycleListener(LifecycleListener listener) {
|
||||
lifecycle.removeLifecycleListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void startInternal() throws LifecycleException {
|
||||
super.startInternal();
|
||||
setState(LifecycleState.STARTING);
|
||||
Boolean attachedToValve = false;
|
||||
for (Valve valve : getContainer().getPipeline().getValves()) {
|
||||
if (valve instanceof RequestSessionHandlerValve) {
|
||||
this.handlerValve = (RequestSessionHandlerValve) valve;
|
||||
this.handlerValve.setRedisSessionManager(this);
|
||||
attachedToValve = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!attachedToValve) {
|
||||
throw new LifecycleException("Unable to attach to session handling valve; sessions cannot be saved after the request without the valve starting properly.");
|
||||
}
|
||||
try {
|
||||
initializeSessionSerializer();
|
||||
requestSessionCacheUtils = RequestSessionCacheFactory.getInstance();
|
||||
} catch (Exception e) {
|
||||
log.error("Error while initializing serializer/rediscache", e);
|
||||
}
|
||||
log.info("The sessions will expire after " + getMaxInactiveInterval() + " seconds");
|
||||
setDistributable(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopInternal() throws LifecycleException {
|
||||
setState(LifecycleState.STOPPING);
|
||||
super.stopInternal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session createSession(String requestedSessionId) {
|
||||
CustomRequestSession customSession = null;
|
||||
String sessionId = null;
|
||||
if (requestedSessionId != null) {
|
||||
sessionId = requestedSessionId;
|
||||
if (requestSessionCacheUtils.setStringIfKeyNotExists(sessionId.getBytes(), NULL_SESSION) == 0L) {
|
||||
sessionId = null;
|
||||
}
|
||||
} else {
|
||||
do {
|
||||
sessionId = generateSessionId();
|
||||
} while (requestSessionCacheUtils.setStringIfKeyNotExists(sessionId.getBytes(), NULL_SESSION) == 0L); // 1 = key set; 0 = key already existed
|
||||
}
|
||||
if (sessionId != null) {
|
||||
customSession = (CustomRequestSession) createEmptySession();
|
||||
customSession.setNew(true);
|
||||
customSession.setValid(true);
|
||||
customSession.setCreationTime(System.currentTimeMillis());
|
||||
customSession.setMaxInactiveInterval(getMaxInactiveInterval());
|
||||
customSession.setId(sessionId);
|
||||
customSession.tellNew();
|
||||
}
|
||||
currentSession.set(customSession);
|
||||
currentSessionId.set(sessionId);
|
||||
currentSessionIsPersisted.set(false);
|
||||
currentSessionSerializationMetadata.set(new SessionSerializationMetadata());
|
||||
if (customSession != null) {
|
||||
try {
|
||||
save(customSession, true);
|
||||
} catch (Exception e) {
|
||||
log.error("Error saving newly created session", e);
|
||||
currentSession.set(null);
|
||||
currentSessionId.set(null);
|
||||
customSession = null;
|
||||
}
|
||||
}
|
||||
return customSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session createEmptySession() {
|
||||
return new CustomRequestSession(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Session session) {
|
||||
save(session, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Session findSession(String sessionId) throws IOException {
|
||||
CustomRequestSession customSession = null;
|
||||
if (sessionId == null) {
|
||||
currentSessionIsPersisted.set(false);
|
||||
currentSession.set(null);
|
||||
currentSessionSerializationMetadata.set(null);
|
||||
currentSessionId.set(null);
|
||||
} else if (sessionId.equals(currentSessionId.get())) {
|
||||
customSession = currentSession.get();
|
||||
} else {
|
||||
byte[] data = requestSessionCacheUtils.getByteArray(sessionId);
|
||||
if (data != null) {
|
||||
DeserializedSessionContainer container = deserializeSessionData(sessionId, data);
|
||||
customSession = (CustomRequestSession) container.session;
|
||||
currentSession.set(customSession);
|
||||
currentSessionSerializationMetadata.set(container.metadata);
|
||||
currentSessionIsPersisted.set(true);
|
||||
currentSessionId.set(sessionId);
|
||||
} else {
|
||||
currentSessionIsPersisted.set(false);
|
||||
currentSession.set(null);
|
||||
currentSessionSerializationMetadata.set(null);
|
||||
currentSessionId.set(null);
|
||||
}
|
||||
}
|
||||
return customSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Session session) {
|
||||
remove(session, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Session session, boolean update) {
|
||||
requestSessionCacheUtils.deleteKey(session.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() throws ClassNotFoundException, IOException {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() throws IOException {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
/**
|
||||
* method to deserialize session data
|
||||
*
|
||||
* @param id
|
||||
* @param data
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public DeserializedSessionContainer deserializeSessionData(String id, byte[] data) throws IOException {
|
||||
if (Arrays.equals(NULL_SESSION, data)) {
|
||||
throw new IOException("Serialized session data was equal to NULL_SESSION");
|
||||
}
|
||||
CustomRequestSession customSession = null;
|
||||
SessionSerializationMetadata metadata = null;
|
||||
try {
|
||||
metadata = new SessionSerializationMetadata();
|
||||
customSession = (CustomRequestSession) createEmptySession();
|
||||
serializer.deserializeSessionData(data, customSession, metadata);
|
||||
customSession.setId(id);
|
||||
customSession.setNew(false);
|
||||
customSession.setMaxInactiveInterval(getMaxInactiveInterval());
|
||||
customSession.access();
|
||||
customSession.setValid(true);
|
||||
customSession.resetDirtyTracking();
|
||||
} catch (Exception e) {
|
||||
log.error("Unable to deserialize into session", e);
|
||||
}
|
||||
return new DeserializedSessionContainer(customSession, metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* method to save session data to cache
|
||||
*
|
||||
* @param session
|
||||
* @param forceSave
|
||||
*/
|
||||
public void save(Session session, boolean forceSave) {
|
||||
Boolean isCurrentSessionPersisted;
|
||||
try {
|
||||
CustomRequestSession customSession = (CustomRequestSession) session;
|
||||
SessionSerializationMetadata sessionSerializationMetadata = currentSessionSerializationMetadata.get();
|
||||
byte[] originalSessionAttributesHash = sessionSerializationMetadata.getSessionAttributesHash();
|
||||
byte[] sessionAttributesHash = null;
|
||||
if (forceSave || customSession.isDirty() || (isCurrentSessionPersisted = this.currentSessionIsPersisted.get()) == null || !isCurrentSessionPersisted || !Arrays.equals(originalSessionAttributesHash, (sessionAttributesHash = serializer.getSessionAttributesHashCode(customSession)))) {
|
||||
if (sessionAttributesHash == null) {
|
||||
sessionAttributesHash = serializer.getSessionAttributesHashCode(customSession);
|
||||
}
|
||||
SessionSerializationMetadata updatedSerializationMetadata = new SessionSerializationMetadata();
|
||||
updatedSerializationMetadata.setSessionAttributesHash(sessionAttributesHash);
|
||||
requestSessionCacheUtils.setByteArray(customSession.getId(), serializer.serializeSessionData(customSession, updatedSerializationMetadata));
|
||||
customSession.resetDirtyTracking();
|
||||
currentSessionSerializationMetadata.set(updatedSerializationMetadata);
|
||||
currentSessionIsPersisted.set(true);
|
||||
}
|
||||
log.trace("Setting expire timeout on session [" + customSession.getId() + "] to " + getMaxInactiveInterval());
|
||||
requestSessionCacheUtils.expire(customSession.getId(), getMaxInactiveInterval());
|
||||
} catch (IOException e) {
|
||||
log.error("Error occured while storing the session object into redis", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void afterRequest(Request request) {
|
||||
CustomRequestSession customSession = currentSession.get();
|
||||
if (customSession != null) {
|
||||
try {
|
||||
if (customSession.isValid()) {
|
||||
log.trace("Request with session completed, saving session " + customSession.getId());
|
||||
save(customSession, getAlwaysSaveAfterRequest());
|
||||
} else {
|
||||
log.trace("HTTP Session has been invalidated, removing :" + customSession.getId());
|
||||
remove(customSession);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error storing/updating/removing session", e);
|
||||
} finally {
|
||||
currentSession.remove();
|
||||
currentSessionId.remove();
|
||||
currentSessionIsPersisted.remove();
|
||||
log.trace("Session removed from ThreadLocal :" + customSession.getIdInternal());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* method to initialize custom session serializer
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private void initializeSessionSerializer() throws Exception {
|
||||
serializer = new SessionDataSerializer();
|
||||
Loader loader = null;
|
||||
if (getContainer() != null) {
|
||||
loader = getContainer().getLoader();
|
||||
}
|
||||
ClassLoader classLoader = null;
|
||||
if (loader != null) {
|
||||
classLoader = loader.getClassLoader();
|
||||
}
|
||||
serializer.setClassLoader(classLoader);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package com.r.tomcat.session.management;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.apache.catalina.util.CustomObjectInputStream;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* This class is uses to store and retrieve the HTTP request session objects from catalina to data cache
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
public class SessionDataSerializer
|
||||
{
|
||||
private final Log log = LogFactory.getLog(SessionDataSerializer.class);
|
||||
|
||||
private ClassLoader loader;
|
||||
|
||||
public void setClassLoader(ClassLoader loader) {
|
||||
this.loader = loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* method to get session attributes hash code
|
||||
*
|
||||
* @param session
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public byte[] getSessionAttributesHashCode(CustomRequestSession session) throws IOException {
|
||||
byte[] serialized = null;
|
||||
HashMap<String, Object> attributes = new HashMap<String, Object>();
|
||||
for (Enumeration<String> enumerator = session.getAttributeNames(); enumerator.hasMoreElements();) {
|
||||
String key = enumerator.nextElement();
|
||||
attributes.put(key, session.getAttribute(key));
|
||||
}
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(bos));) {
|
||||
oos.writeUnshared(attributes);
|
||||
oos.flush();
|
||||
serialized = bos.toByteArray();
|
||||
}
|
||||
MessageDigest digester = null;
|
||||
try {
|
||||
digester = MessageDigest.getInstance("MD5");
|
||||
} catch (Exception e) {
|
||||
log.error("Unable to get MessageDigest instance for MD5");
|
||||
}
|
||||
return digester.digest(serialized);
|
||||
}
|
||||
|
||||
/**
|
||||
* method to serialize custom session data
|
||||
*
|
||||
* @param session
|
||||
* @param metadata
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public byte[] serializeSessionData(CustomRequestSession session, SessionSerializationMetadata metadata) throws IOException {
|
||||
byte[] serialized = null;
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(bos));) {
|
||||
oos.writeObject(metadata);
|
||||
session.writeObjectData(oos);
|
||||
oos.flush();
|
||||
serialized = bos.toByteArray();
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* method to deserialize custom session data
|
||||
*
|
||||
* @param data
|
||||
* @param session
|
||||
* @param metadata
|
||||
* @throws IOException
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
public void deserializeSessionData(byte[] data, CustomRequestSession session, SessionSerializationMetadata metadata) throws IOException, ClassNotFoundException {
|
||||
try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data)); ObjectInputStream ois = new CustomObjectInputStream(bis, loader);) {
|
||||
SessionSerializationMetadata serializedMetadata = (SessionSerializationMetadata) ois.readObject();
|
||||
metadata.copyFieldsFrom(serializedMetadata);
|
||||
session.readObjectData(ois);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package com.r.tomcat.session.management;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Tomcat clustering implementation
|
||||
*
|
||||
* This class is uses to store and retrieve the HTTP request session objects from catalina to data cache
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 1.0
|
||||
*/
|
||||
public class SessionSerializationMetadata implements Serializable
|
||||
{
|
||||
private static final long serialVersionUID = 124438185184833546L;
|
||||
|
||||
private byte[] sessionAttributesHash;
|
||||
|
||||
public SessionSerializationMetadata() {
|
||||
this.sessionAttributesHash = new byte[0];
|
||||
}
|
||||
|
||||
public byte[] getSessionAttributesHash() {
|
||||
return sessionAttributesHash;
|
||||
}
|
||||
|
||||
public void setSessionAttributesHash(byte[] sessionAttributesHash) {
|
||||
this.sessionAttributesHash = sessionAttributesHash;
|
||||
}
|
||||
|
||||
public void copyFieldsFrom(SessionSerializationMetadata metadata) {
|
||||
this.setSessionAttributesHash(metadata.getSessionAttributesHash());
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream out) throws IOException {
|
||||
out.writeInt(sessionAttributesHash.length);
|
||||
out.write(this.sessionAttributesHash);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
int hashLength = in.readInt();
|
||||
byte[] sessionAttributesHash = new byte[hashLength];
|
||||
in.read(sessionAttributesHash, 0, hashLength);
|
||||
this.sessionAttributesHash = sessionAttributesHash;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue