├── lib
├── catalina.jar
├── servlet-api.jar
├── commons-pool2-2.2.jar
├── commons-logging-1.1.jar
└── jedis-3.0.0-SNAPSHOT.jar
├── resources
├── redis.properties
└── ReadMe.txt
├── .gitattributes
├── .project
├── src
└── com
│ └── r
│ ├── data
│ └── cache
│ │ ├── ICacheUtils.java
│ │ ├── RedisCacheFactory.java
│ │ ├── constants
│ │ └── RedisConstants.java
│ │ ├── manager
│ │ ├── RedisClusterManager.java
│ │ └── RedisManager.java
│ │ ├── RedisCacheUtils.java
│ │ └── RedisClusterCacheUtils.java
│ └── tomcat
│ └── session
│ └── management
│ ├── commons
│ ├── DeserializedSessionContainer.java
│ ├── ISerializer.java
│ ├── SessionHandlerValve.java
│ ├── IRequestSession.java
│ ├── IRequestSessionManager.java
│ ├── SessionSerializationMetadata.java
│ └── JavaSerializer.java
│ └── redis
│ ├── RedisSession.java
│ └── RedisSessionManager.java
├── .classpath
├── .gitignore
└── README.md
/lib/catalina.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangwei5095/TomcatRedisClusterEnabledSessionManager/HEAD/lib/catalina.jar
--------------------------------------------------------------------------------
/lib/servlet-api.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangwei5095/TomcatRedisClusterEnabledSessionManager/HEAD/lib/servlet-api.jar
--------------------------------------------------------------------------------
/lib/commons-pool2-2.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangwei5095/TomcatRedisClusterEnabledSessionManager/HEAD/lib/commons-pool2-2.2.jar
--------------------------------------------------------------------------------
/lib/commons-logging-1.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangwei5095/TomcatRedisClusterEnabledSessionManager/HEAD/lib/commons-logging-1.1.jar
--------------------------------------------------------------------------------
/lib/jedis-3.0.0-SNAPSHOT.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangwei5095/TomcatRedisClusterEnabledSessionManager/HEAD/lib/jedis-3.0.0-SNAPSHOT.jar
--------------------------------------------------------------------------------
/resources/redis.properties:
--------------------------------------------------------------------------------
1 | # redis hosts ex: 127.0.0.1:6379, 127.0.0.2:6379, 127.0.0.2:6380, ....
2 | redis.hosts=127.0.0.1:6379
3 | # Redis Password
4 | # set true to enable redis cluster mode
5 | redis.cluster.enabled=false
6 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 |
7 | # Standard to msysgit
8 | *.doc diff=astextplain
9 | *.DOC diff=astextplain
10 | *.docx diff=astextplain
11 | *.DOCX diff=astextplain
12 | *.dot diff=astextplain
13 | *.DOT diff=astextplain
14 | *.pdf diff=astextplain
15 | *.PDF diff=astextplain
16 | *.rtf diff=astextplain
17 | *.RTF diff=astextplain
18 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | TomcatRedisSessionManager
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/com/r/data/cache/ICacheUtils.java:
--------------------------------------------------------------------------------
1 | package com.r.data.cache;
2 |
3 | /**
4 | * IRedis cache utils
5 | *
6 | * @author Ranjith
7 | * @since 1.0
8 | */
9 | public interface ICacheUtils
10 | {
11 | public boolean isAvailable();
12 |
13 | public void setByteArray(byte[] key, byte[] value);
14 |
15 | public byte[] getByteArray(String key);
16 |
17 | public void deleteKey(String key);
18 |
19 | public Long setStringIfKeyNotExists(byte[] key, byte[] value);
20 |
21 | public void expire(byte[] key, int ttl);
22 | }
--------------------------------------------------------------------------------
/src/com/r/tomcat/session/management/commons/DeserializedSessionContainer.java:
--------------------------------------------------------------------------------
1 | package com.r.tomcat.session.management.commons;
2 |
3 | /**
4 | * Tomcat session de-serialization container
5 | *
6 | * @author Ranjith
7 | * @since 1.0
8 | */
9 | public class DeserializedSessionContainer
10 | {
11 | public final IRequestSession session;
12 |
13 | public final SessionSerializationMetadata metadata;
14 |
15 | public DeserializedSessionContainer(IRequestSession session, SessionSerializationMetadata metadata) {
16 | this.session = session;
17 | this.metadata = metadata;
18 | }
19 | }
--------------------------------------------------------------------------------
/src/com/r/tomcat/session/management/commons/ISerializer.java:
--------------------------------------------------------------------------------
1 | package com.r.tomcat.session.management.commons;
2 |
3 | /**
4 | * ISerializer
5 | *
6 | * @author Ranjith
7 | * @since 4.15.9.4_Tomcat
8 | */
9 | public interface ISerializer
10 | {
11 | public void setClassLoader(ClassLoader loader);
12 |
13 | public byte[] attributesHashFrom(IRequestSession session) throws Exception;
14 |
15 | public byte[] serializeFrom(IRequestSession session, SessionSerializationMetadata metadata) throws Exception;
16 |
17 | public void deserializeInto(byte[] data, IRequestSession session, SessionSerializationMetadata metadata) throws Exception;
18 | }
--------------------------------------------------------------------------------
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # Windows shortcuts
18 | *.lnk
19 |
20 | # =========================
21 | # Operating System Files
22 | # =========================
23 |
24 | # OSX
25 | # =========================
26 |
27 | .DS_Store
28 | .AppleDouble
29 | .LSOverride
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear on external disk
35 | .Spotlight-V100
36 | .Trashes
37 |
38 | # Directories potentially created on remote AFP share
39 | .AppleDB
40 | .AppleDesktop
41 | Network Trash Folder
42 | Temporary Items
43 | .apdisk
44 |
--------------------------------------------------------------------------------
/src/com/r/tomcat/session/management/commons/SessionHandlerValve.java:
--------------------------------------------------------------------------------
1 | package com.r.tomcat.session.management.commons;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.ServletException;
6 |
7 | import org.apache.catalina.connector.Request;
8 | import org.apache.catalina.connector.Response;
9 | import org.apache.catalina.valves.ValveBase;
10 |
11 | /**
12 | * Tomcat session handler valve
13 | *
14 | * @author Ranjith
15 | * @since 1.0
16 | */
17 | public class SessionHandlerValve extends ValveBase
18 | {
19 | private IRequestSessionManager manager;
20 |
21 | public void setRedisSessionManager(IRequestSessionManager manager) {
22 | this.manager = manager;
23 | }
24 |
25 | @Override
26 | public void invoke(Request request, Response response) throws IOException, ServletException {
27 | try {
28 | getNext().invoke(request, response);
29 | } finally {
30 | manager.afterRequest();
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/com/r/tomcat/session/management/commons/IRequestSession.java:
--------------------------------------------------------------------------------
1 | package com.r.tomcat.session.management.commons;
2 |
3 | import java.io.IOException;
4 | import java.io.ObjectInputStream;
5 | import java.io.ObjectOutputStream;
6 | import java.security.Principal;
7 | import java.util.Enumeration;
8 | import java.util.HashMap;
9 |
10 | /**
11 | * IRequest session
12 | *
13 | * @author Ranjith
14 | * @since 1.0
15 | */
16 | public interface IRequestSession
17 | {
18 | public Boolean isDirty();
19 |
20 | public HashMap getChangedAttributes();
21 |
22 | public void resetDirtyTracking();
23 |
24 | public void setAttribute(String name, Object value);
25 |
26 | public Object getAttribute(String name);
27 |
28 | public Enumeration getAttributeNames();
29 |
30 | public void removeAttribute(String name);
31 |
32 | public void setId(String id);
33 |
34 | public void setPrincipal(Principal principal);
35 |
36 | public void writeObjectData(ObjectOutputStream out) throws IOException;
37 |
38 | public void readObjectData(ObjectInputStream in) throws IOException, ClassNotFoundException;
39 | }
--------------------------------------------------------------------------------
/src/com/r/data/cache/RedisCacheFactory.java:
--------------------------------------------------------------------------------
1 | package com.r.data.cache;
2 |
3 | import java.util.Properties;
4 |
5 | import org.apache.commons.logging.Log;
6 | import org.apache.commons.logging.LogFactory;
7 |
8 | import com.r.data.cache.constants.RedisConstants;
9 |
10 | /**
11 | * Get the active Redis cache.
12 | *
13 | * @author Ranjith
14 | * @since 1.0
15 | */
16 | public class RedisCacheFactory
17 | {
18 | private static Log log = LogFactory.getLog(RedisCacheFactory.class);
19 |
20 | private static Properties properties;
21 |
22 | protected static ICacheUtils cacheUtils;
23 |
24 | public static synchronized ICacheUtils createInstance(Properties props) throws Exception {
25 | try {
26 | if (props != null && !props.isEmpty()) {
27 | properties = props;
28 | if (!Boolean.valueOf(properties.getProperty(RedisConstants.IS_CLUSTER_ENABLED, RedisConstants.DEFAULT_IS_CLUSTER_ENABLED))) {
29 | cacheUtils = new RedisCacheUtils(properties);
30 | } else {
31 | cacheUtils = new RedisClusterCacheUtils(properties);
32 | }
33 | }
34 | } catch (Exception e) {
35 | log.error("Error creating redis instance", e);
36 | }
37 | return cacheUtils;
38 | }
39 |
40 | public static synchronized ICacheUtils getInstance() {
41 | return cacheUtils;
42 | }
43 | }
--------------------------------------------------------------------------------
/resources/ReadMe.txt:
--------------------------------------------------------------------------------
1 | /**
2 | * Redis Cluster Enabled Session Manager
3 | *
4 | * Redis session manager is the pluggable one. It uses to store sessions into Redis for easy distribution of HTTP Requests across a cluster of Tomcat servers.
5 | *
6 | * @author Ranjith
7 | * @since 1.0
8 | */
9 |
10 | Pre-requisite:
11 | --------------
12 | 1. jedis-3.0.0-SNAPSHOT.jar
13 | 2. commons-pool2-2.2.jar
14 | 3. commons-logging-1.1.jar
15 | Note: Download all the above three jars and move it into tomcat/lib directory
16 |
17 |
18 | Steps to be done,
19 | -----------------
20 | 1. Move the downloaded jar (RedisClusterEnabledSessionManager-1.0.jar) to tomcat/lib directory
21 | $catalina.home/lib/RedisSessionManager.jar
22 |
23 | 2. Add tomcat system property "catalina.home"
24 | catalina.home="TOMCAT_LOCATION"
25 |
26 | 3. Configure redis credentials in redis.properties file and move the file to tomcat/conf directory
27 | tomcat/conf/redis.properties
28 |
29 | 4. Add the below two lines in tomcat/conf/context.xml
30 |
31 |
32 |
33 | 5. Verify the session expiration time in tomcat/conf/web.xml
34 |
35 | 70
36 |
37 |
38 | Note:
39 | -----
40 | * The Redis session manager supports, both single redis master and redis cluster based on the redis.properties configuration.
--------------------------------------------------------------------------------
/src/com/r/tomcat/session/management/commons/IRequestSessionManager.java:
--------------------------------------------------------------------------------
1 | package com.r.tomcat.session.management.commons;
2 |
3 | import java.io.IOException;
4 |
5 | import org.apache.catalina.LifecycleListener;
6 | import org.apache.catalina.Session;
7 |
8 | /**
9 | * IRequest session manager
10 | *
11 | * @author Ranjith
12 | * @since 1.0
13 | */
14 | public interface IRequestSessionManager
15 | {
16 | public String getSessionPersistPolicies();
17 |
18 | public void setSessionPersistPolicies(String sessionPersistPolicies);
19 |
20 | public boolean getSaveOnChange();
21 |
22 | public boolean getAlwaysSaveAfterRequest();
23 |
24 | public void addLifecycleListener(LifecycleListener listener);
25 |
26 | public LifecycleListener[] findLifecycleListeners();
27 |
28 | public void removeLifecycleListener(LifecycleListener listener);
29 |
30 | public Session createSession(String requestedSessionId);
31 |
32 | public Session createEmptySession();
33 |
34 | public void add(Session session);
35 |
36 | public Session findSession(String id) throws IOException;
37 |
38 | public byte[] loadSessionDataFromRedis(String id) throws IOException;
39 |
40 | public DeserializedSessionContainer sessionFromSerializedData(String id, byte[] data) throws IOException;
41 |
42 | public void save(Session session) throws Exception;
43 |
44 | public void save(Session session, boolean forceSave) throws Exception;
45 |
46 | public void remove(Session session);
47 |
48 | public void remove(Session session, boolean update);
49 |
50 | public void afterRequest();
51 | }
--------------------------------------------------------------------------------
/src/com/r/tomcat/session/management/commons/SessionSerializationMetadata.java:
--------------------------------------------------------------------------------
1 | package com.r.tomcat.session.management.commons;
2 |
3 | import java.io.IOException;
4 | import java.io.ObjectInputStream;
5 | import java.io.ObjectOutputStream;
6 | import java.io.Serializable;
7 |
8 | /**
9 | * Tomcat session serialization object
10 | *
11 | * @author Ranjith
12 | * @since 1.0
13 | */
14 | public class SessionSerializationMetadata implements Serializable
15 | {
16 | private static final long serialVersionUID = 1L;
17 |
18 | private byte[] sessionAttributesHash;
19 |
20 | public SessionSerializationMetadata() {
21 | this.sessionAttributesHash = new byte[0];
22 | }
23 |
24 | public byte[] getSessionAttributesHash() {
25 | return sessionAttributesHash;
26 | }
27 |
28 | public void setSessionAttributesHash(byte[] sessionAttributesHash) {
29 | this.sessionAttributesHash = sessionAttributesHash;
30 | }
31 |
32 | public void copyFieldsFrom(SessionSerializationMetadata metadata) {
33 | this.setSessionAttributesHash(metadata.getSessionAttributesHash());
34 | }
35 |
36 | private void writeObject(ObjectOutputStream out) throws IOException {
37 | out.writeInt(sessionAttributesHash.length);
38 | out.write(this.sessionAttributesHash);
39 | }
40 |
41 | private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
42 | int hashLength = in.readInt();
43 | byte[] sessionAttributesHash = new byte[hashLength];
44 | in.read(sessionAttributesHash, 0, hashLength);
45 | this.sessionAttributesHash = sessionAttributesHash;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/com/r/data/cache/constants/RedisConstants.java:
--------------------------------------------------------------------------------
1 | package com.r.data.cache.constants;
2 |
3 | /**
4 | * Redis constants
5 | *
6 | * @author Ranjith
7 | * @since 1.0
8 | */
9 | public class RedisConstants
10 | {
11 | public static final String REDIS_PROPERTIES_FILE = "redis.properties";
12 |
13 | // Redis properties
14 | public static final String MAX_ACTIVE = "redis.max.active";
15 | public static final String TEST_ONBORROW = "redis.test.onBorrow";
16 | public static final String TEST_ONRETURN = "redis.test.onReturn";
17 | public static final String MAX_IDLE = "redis.max.idle";
18 | public static final String MIN_IDLE = "redis.min.idle";
19 | public static final String TEST_WHILEIDLE = "redis.test.whileIdle";
20 | public static final String TEST_NUMPEREVICTION = "redis.test.numPerEviction";
21 | public static final String TIME_BETWEENEVICTION = "redis.time.betweenEviction";
22 | public static final String HOSTS = "redis.hosts";
23 | public static final String PASSWORD = "redis.password";
24 | public static final String IS_CLUSTER_ENABLED = "redis.cluster.enabled";
25 |
26 | // Redis property default values
27 | public static final String DEFAULT_MAX_ACTIVE_VALUE = "10";
28 | public static final String DEFAULT_TEST_ONBORROW_VALUE = "true";
29 | public static final String DEFAULT_TEST_ONRETURN_VALUE = "true";
30 | public static final String DEFAULT_MAX_IDLE_VALUE = "5";
31 | public static final String DEFAULT_MIN_IDLE_VALUE = "1";
32 | public static final String DEFAULT_TEST_WHILEIDLE_VALUE = "true";
33 | public static final String DEFAULT_TEST_NUMPEREVICTION_VALUE = "10";
34 | public static final String DEFAULT_TIME_BETWEENEVICTION_VALUE = "60000";
35 | public static final String DEFAULT_IS_CLUSTER_ENABLED = "false";
36 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tomcat-Redis-Cluster-Enabled-Session-Manager
2 |
3 | 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.
4 |
5 | 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.
6 |
7 | It supports, both single redis master and redis cluster based on the redis.properties configuration.
8 |
9 | Going forward, we no need to enable sticky session (JSESSIONID) in Load balancer.
10 |
11 | ## Supports:
12 | * Apache Tomcat 7
13 |
14 | ## Downloads:
15 |
16 | **Tomcat Redis Cluster Enabled Session Manager is available**
17 |
18 | https://github.com/ran-jit/TomcatRedisClusterEnabledSessionManager/releases/download/1.0/TomcatRedisSessionManager-1.0.zip
19 |
20 |
21 | ## Pre-requisite:
22 | 1. jedis-3.0.0-SNAPSHOT.jar
23 | 2. commons-pool2-2.2.jar
24 | 3. commons-logging-1.1.jar
25 |
26 | ##### Note: Download all the above three jars and move it into tomcat/lib directory
27 |
28 |
29 | ####Steps to be done,
30 | 1. Move the downloaded jar (RedisSessionManager.jar) to tomcat/lib directory
31 | * **$catalina.home/lib/RedisSessionManager.jar**
32 |
33 | 2. Add tomcat system property "catalina.home"
34 | * **catalina.home="TOMCAT_LOCATION"**
35 |
36 | 3. Extract downloaded jar (RedisSessionManager.jar) to configure redis credentials in redis.properties file and move the file to tomcat/conf directory
37 | * **tomcat/conf/redis.properties**
38 |
39 | 4. Add the below two lines in tomcat/conf/context.xml
40 | * **<Valve className="com.r.tomcat.session.management.commons.SessionHandlerValve" />**
41 | * **<Manager className="com.r.tomcat.session.management.redis.RedisSessionManager" />**
42 |
43 | 5. Verify the session expiration time in tomcat/conf/web.xml
44 | * **<session-config>**
45 | * **<session-timeout>70</session-timeout>**
46 | * **</session-config>**
47 |
48 | ###Note:
49 | * The Redis session manager supports, both single redis master and redis cluster based on the redis.properties configuration.
50 |
--------------------------------------------------------------------------------
/src/com/r/tomcat/session/management/commons/JavaSerializer.java:
--------------------------------------------------------------------------------
1 | package com.r.tomcat.session.management.commons;
2 |
3 | import java.io.BufferedInputStream;
4 | import java.io.BufferedOutputStream;
5 | import java.io.ByteArrayInputStream;
6 | import java.io.ByteArrayOutputStream;
7 | import java.io.ObjectInputStream;
8 | import java.io.ObjectOutputStream;
9 | import java.security.MessageDigest;
10 | import java.util.Enumeration;
11 | import java.util.HashMap;
12 |
13 | import org.apache.catalina.util.CustomObjectInputStream;
14 |
15 | /**
16 | * Java serializer
17 | *
18 | * @author Ranjith
19 | * @since 1.0
20 | */
21 | public class JavaSerializer implements ISerializer
22 | {
23 | private ClassLoader loader;
24 |
25 | @Override
26 | public void setClassLoader(ClassLoader loader) {
27 | this.loader = loader;
28 | }
29 |
30 | public byte[] attributesHashFrom(IRequestSession session) throws Exception {
31 | byte[] serialized = null;
32 | HashMap attributes = new HashMap();
33 | for (Enumeration enumerator = session.getAttributeNames(); enumerator.hasMoreElements();) {
34 | String key = enumerator.nextElement();
35 | attributes.put(key, session.getAttribute(key));
36 | }
37 | try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(bos));) {
38 | oos.writeUnshared(attributes);
39 | oos.flush();
40 | serialized = bos.toByteArray();
41 | }
42 | MessageDigest digester = null;
43 | try {
44 | digester = MessageDigest.getInstance("MD5");
45 | } catch (Exception e) {
46 | throw new Exception("Unable to get MessageDigest instance for MD5");
47 | }
48 | return digester.digest(serialized);
49 | }
50 |
51 | @Override
52 | public byte[] serializeFrom(IRequestSession session, SessionSerializationMetadata metadata) throws Exception {
53 | byte[] serialized = null;
54 | try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(bos));) {
55 | oos.writeObject(metadata);
56 | session.writeObjectData(oos);
57 | oos.flush();
58 | serialized = bos.toByteArray();
59 | }
60 | return serialized;
61 | }
62 |
63 | @Override
64 | public void deserializeInto(byte[] data, IRequestSession session, SessionSerializationMetadata metadata) throws Exception {
65 | try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data)); ObjectInputStream ois = new CustomObjectInputStream(bis, loader);) {
66 | SessionSerializationMetadata serializedMetadata = (SessionSerializationMetadata) ois.readObject();
67 | metadata.copyFieldsFrom(serializedMetadata);
68 | session.readObjectData(ois);
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/src/com/r/data/cache/manager/RedisClusterManager.java:
--------------------------------------------------------------------------------
1 | package com.r.data.cache.manager;
2 |
3 | import java.util.HashSet;
4 | import java.util.Properties;
5 | import java.util.Set;
6 |
7 | import redis.clients.jedis.HostAndPort;
8 | import redis.clients.jedis.JedisCluster;
9 | import redis.clients.jedis.JedisPoolConfig;
10 | import redis.clients.jedis.Protocol;
11 |
12 | import com.r.data.cache.constants.RedisConstants;
13 |
14 | /**
15 | * Redis cluster manager
16 | *
17 | * @author Ranjith
18 | * @since 1.0
19 | */
20 | public class RedisClusterManager
21 | {
22 | private Properties properties;
23 |
24 | private static JedisCluster jedisCluster;
25 |
26 | private static RedisClusterManager instance;
27 |
28 | private RedisClusterManager(Properties properties) {
29 | this.properties = properties;
30 | }
31 |
32 | public static RedisClusterManager createInstance(Properties properties) {
33 | instance = new RedisClusterManager(properties);
34 | instance.connect();
35 | return instance;
36 | }
37 |
38 | public final static RedisClusterManager getInstance() throws Exception {
39 | return instance;
40 | }
41 |
42 | public void connect() {
43 | JedisPoolConfig poolConfig = new JedisPoolConfig();
44 | int maxActive = Integer.parseInt(properties.getProperty(RedisConstants.MAX_ACTIVE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE));
45 | poolConfig.setMaxTotal(maxActive);
46 | boolean testOnBorrow = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_ONBORROW, RedisConstants.DEFAULT_TEST_ONBORROW_VALUE));
47 | poolConfig.setTestOnBorrow(testOnBorrow);
48 | boolean testOnReturn = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_ONRETURN, RedisConstants.DEFAULT_TEST_ONRETURN_VALUE));
49 | poolConfig.setTestOnReturn(testOnReturn);
50 | int maxIdle = Integer.parseInt(properties.getProperty(RedisConstants.MAX_IDLE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE));
51 | poolConfig.setMaxIdle(maxIdle);
52 | int minIdle = Integer.parseInt(properties.getProperty(RedisConstants.MIN_IDLE, RedisConstants.DEFAULT_MIN_IDLE_VALUE));
53 | poolConfig.setMinIdle(minIdle);
54 | boolean testWhileIdle = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_WHILEIDLE, RedisConstants.DEFAULT_TEST_WHILEIDLE_VALUE));
55 | poolConfig.setTestWhileIdle(testWhileIdle);
56 | int testNumPerEviction = Integer.parseInt(properties.getProperty(RedisConstants.TEST_NUMPEREVICTION, RedisConstants.DEFAULT_TEST_NUMPEREVICTION_VALUE));
57 | poolConfig.setNumTestsPerEvictionRun(testNumPerEviction);
58 | long timeBetweenEviction = Long.parseLong(properties.getProperty(RedisConstants.TIME_BETWEENEVICTION, RedisConstants.DEFAULT_TIME_BETWEENEVICTION_VALUE));
59 | poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEviction);
60 | jedisCluster = new JedisCluster(getJedisClusterNodesSet(properties.getProperty(RedisConstants.HOSTS, Protocol.DEFAULT_HOST.concat(":").concat(String.valueOf(Protocol.DEFAULT_PORT)))), poolConfig);
61 | }
62 |
63 | /*
64 | * method to get the cluster nodes
65 | */
66 | private Set getJedisClusterNodesSet(String hosts) {
67 | Set nodes = new HashSet();
68 | hosts = hosts.replaceAll("\\s", "");
69 | String[] hostPorts = hosts.split(",");
70 | for (String hostPort : hostPorts) {
71 | String[] hostPortArr = hostPort.split(":");
72 | nodes.add(new HostAndPort(hostPortArr[0], Integer.valueOf(hostPortArr[1])));
73 | }
74 | return nodes;
75 | }
76 |
77 | public JedisCluster getJedis() {
78 | return jedisCluster;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/com/r/data/cache/manager/RedisManager.java:
--------------------------------------------------------------------------------
1 | package com.r.data.cache.manager;
2 |
3 | import java.util.Properties;
4 |
5 | import redis.clients.jedis.Jedis;
6 | import redis.clients.jedis.JedisPool;
7 | import redis.clients.jedis.JedisPoolConfig;
8 | import redis.clients.jedis.Protocol;
9 |
10 | import com.r.data.cache.constants.RedisConstants;
11 |
12 | /**
13 | * Redis manager
14 | *
15 | * @author Ranjith
16 | * @since 1.0
17 | */
18 | public class RedisManager
19 | {
20 | private static RedisManager instance;
21 |
22 | private static JedisPool pool;
23 |
24 | private Properties properties;
25 |
26 | private RedisManager(Properties properties) {
27 | this.properties = properties;
28 | }
29 |
30 | public static RedisManager createInstance(Properties properties) {
31 | instance = new RedisManager(properties);
32 | instance.connect();
33 | return instance;
34 | }
35 |
36 | public final static RedisManager getInstance() throws Exception {
37 | return instance;
38 | }
39 |
40 | public void connect() {
41 | JedisPoolConfig poolConfig = new JedisPoolConfig();
42 | int maxActive = Integer.parseInt(properties.getProperty(RedisConstants.MAX_ACTIVE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE));
43 | poolConfig.setMaxTotal(maxActive);
44 | boolean testOnBorrow = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_ONBORROW, RedisConstants.DEFAULT_TEST_ONBORROW_VALUE));
45 | poolConfig.setTestOnBorrow(testOnBorrow);
46 | boolean testOnReturn = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_ONRETURN, RedisConstants.DEFAULT_TEST_ONRETURN_VALUE));
47 | poolConfig.setTestOnReturn(testOnReturn);
48 | int maxIdle = Integer.parseInt(properties.getProperty(RedisConstants.MAX_ACTIVE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE));
49 | poolConfig.setMaxIdle(maxIdle);
50 | int minIdle = Integer.parseInt(properties.getProperty(RedisConstants.MIN_IDLE, RedisConstants.DEFAULT_MIN_IDLE_VALUE));
51 | poolConfig.setMinIdle(minIdle);
52 | boolean testWhileIdle = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_WHILEIDLE, RedisConstants.DEFAULT_TEST_WHILEIDLE_VALUE));
53 | poolConfig.setTestWhileIdle(testWhileIdle);
54 | int testNumPerEviction = Integer.parseInt(properties.getProperty(RedisConstants.TEST_NUMPEREVICTION, RedisConstants.DEFAULT_TEST_NUMPEREVICTION_VALUE));
55 | poolConfig.setNumTestsPerEvictionRun(testNumPerEviction);
56 | long timeBetweenEviction = Long.parseLong(properties.getProperty(RedisConstants.TIME_BETWEENEVICTION, RedisConstants.DEFAULT_TIME_BETWEENEVICTION_VALUE));
57 | poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEviction);
58 | String hosts = properties.getProperty(RedisConstants.HOSTS, Protocol.DEFAULT_HOST.concat(":").concat(String.valueOf(Protocol.DEFAULT_PORT)));
59 | String host = null;
60 | int port = 0;
61 | hosts = hosts.replaceAll("\\s", "");
62 | String[] hostPorts = hosts.split(",");
63 | for (String hostPort : hostPorts) {
64 | String[] hostPortArr = hostPort.split(":");
65 | host = hostPortArr[0];
66 | port = Integer.valueOf(hostPortArr[1]);
67 | if (!host.isEmpty() && port != 0) {
68 | break;
69 | }
70 | }
71 | String password = properties.getProperty(RedisConstants.PASSWORD);
72 | if (password == null || password == "" || password.isEmpty()) {
73 | pool = new JedisPool(poolConfig, host, port);
74 | } else {
75 | pool = new JedisPool(poolConfig, host, port, Protocol.DEFAULT_TIMEOUT, password);
76 | }
77 | }
78 |
79 | public void release() {
80 | pool.destroy();
81 | }
82 |
83 | public Jedis getJedis() {
84 | return pool.getResource();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/com/r/data/cache/RedisCacheUtils.java:
--------------------------------------------------------------------------------
1 | package com.r.data.cache;
2 |
3 | import java.util.Properties;
4 |
5 | import org.apache.commons.logging.Log;
6 | import org.apache.commons.logging.LogFactory;
7 |
8 | import redis.clients.jedis.Jedis;
9 | import redis.clients.jedis.exceptions.JedisConnectionException;
10 |
11 | import com.r.data.cache.manager.RedisManager;
12 |
13 | /**
14 | * Redis cache utils
15 | *
16 | * @author Ranjith
17 | * @since 1.0
18 | */
19 | public class RedisCacheUtils implements ICacheUtils
20 | {
21 | private Log log = LogFactory.getLog(RedisCacheUtils.class);
22 |
23 | public boolean available = false;
24 |
25 | private static int numRetries = 3;
26 |
27 | private RedisManager manager = null;
28 |
29 | RedisCacheUtils(Properties properties) throws Exception {
30 | try {
31 | manager = RedisManager.createInstance(properties);
32 | } catch (Exception e) {
33 | this.available = false;
34 | log.error("Exception initializing Redis: ", e);
35 | }
36 | this.available = true;
37 | }
38 |
39 | @Override
40 | public boolean isAvailable() {
41 | return available;
42 | }
43 |
44 | @Override
45 | public void setByteArray(byte[] key, byte[] value) {
46 | int tries = 0;
47 | boolean sucess = false;
48 | do {
49 | tries++;
50 | try {
51 | if (key != null && value != null) {
52 | Jedis jedis = manager.getJedis();
53 | jedis.set(key, value);
54 | jedis.close();
55 | }
56 | sucess = true;
57 | } catch (JedisConnectionException e) {
58 | log.error("Jedis connection failed, retrying..." + tries);
59 | }
60 | } while (!sucess && tries <= numRetries);
61 | }
62 |
63 | @Override
64 | public Long setStringIfKeyNotExists(byte[] key, byte[] value) {
65 | int tries = 0;
66 | Long retVal = null;
67 | boolean sucess = false;
68 | do {
69 | tries++;
70 | try {
71 | if (key != null && value != null) {
72 | Jedis jedis = manager.getJedis();
73 | retVal = jedis.setnx(key, value);
74 | jedis.close();
75 | }
76 | sucess = true;
77 | } catch (JedisConnectionException e) {
78 | log.error("Jedis connection failed, retrying..." + tries);
79 | }
80 | } while (!sucess && tries <= numRetries);
81 | return retVal;
82 | }
83 |
84 | @Override
85 | public void expire(byte[] key, int ttl) {
86 | int tries = 0;
87 | boolean sucess = false;
88 | do {
89 | tries++;
90 | try {
91 | Jedis jedis = manager.getJedis();
92 | jedis.expire(key, ttl);
93 | jedis.close();
94 | sucess = true;
95 | } catch (JedisConnectionException e) {
96 | log.error("Jedis connection failed, retrying..." + tries);
97 | }
98 | } while (!sucess && tries <= numRetries);
99 | }
100 |
101 | @Override
102 | public byte[] getByteArray(String key) {
103 | int tries = 0;
104 | boolean sucess = false;
105 | byte[] array = new byte[1];
106 | do {
107 | tries++;
108 | try {
109 | if (key != null) {
110 | Jedis jedis = manager.getJedis();
111 | array = jedis.get(key.getBytes());
112 | jedis.close();
113 | }
114 | sucess = true;
115 | } catch (JedisConnectionException e) {
116 | log.error("Jedis connection failed, retrying..." + tries);
117 | }
118 | } while (!sucess && tries <= numRetries);
119 | return array;
120 | }
121 |
122 | @Override
123 | public void deleteKey(String key) {
124 | int tries = 0;
125 | boolean sucess = false;
126 | do {
127 | tries++;
128 | try {
129 | if (key != null) {
130 | Jedis jedis = manager.getJedis();
131 | jedis.del(key);
132 | jedis.close();
133 | }
134 | sucess = true;
135 | } catch (JedisConnectionException e) {
136 | log.error("Jedis connection failed, retrying..." + tries);
137 | }
138 | } while (!sucess && tries <= numRetries);
139 | }
140 | }
--------------------------------------------------------------------------------
/src/com/r/tomcat/session/management/redis/RedisSession.java:
--------------------------------------------------------------------------------
1 | package com.r.tomcat.session.management.redis;
2 |
3 | import java.io.IOException;
4 | import java.security.Principal;
5 | import java.util.Enumeration;
6 | import java.util.HashMap;
7 |
8 | import org.apache.catalina.Manager;
9 | import org.apache.catalina.session.StandardSession;
10 | import org.apache.commons.logging.Log;
11 | import org.apache.commons.logging.LogFactory;
12 |
13 | import com.r.tomcat.session.management.commons.IRequestSession;
14 |
15 | /**
16 | * Tomcat redis session
17 | *
18 | * @author Ranjith
19 | * @since 1.0
20 | */
21 | public class RedisSession extends StandardSession implements IRequestSession
22 | {
23 | private Log log = LogFactory.getLog(RedisSession.class);
24 |
25 | private static final long serialVersionUID = 2L;
26 |
27 | protected Boolean dirty;
28 |
29 | protected HashMap changedAttributes;
30 |
31 | protected static Boolean manualDirtyTrackingSupportEnabled = false;
32 |
33 | protected static String manualDirtyTrackingAttributeKey = "__changed__";
34 |
35 | public static void setManualDirtyTrackingSupportEnabled(Boolean enabled) {
36 | manualDirtyTrackingSupportEnabled = enabled;
37 | }
38 |
39 | public static void setManualDirtyTrackingAttributeKey(String key) {
40 | manualDirtyTrackingAttributeKey = key;
41 | }
42 |
43 | public RedisSession(Manager manager) {
44 | super(manager);
45 | resetDirtyTracking();
46 | }
47 |
48 | public Boolean isDirty() {
49 | return dirty || !changedAttributes.isEmpty();
50 | }
51 |
52 | public HashMap getChangedAttributes() {
53 | return changedAttributes;
54 | }
55 |
56 | public void resetDirtyTracking() {
57 | changedAttributes = new HashMap<>();
58 | dirty = false;
59 | }
60 |
61 | @Override
62 | public void setAttribute(String key, Object value) {
63 | if (manualDirtyTrackingSupportEnabled && manualDirtyTrackingAttributeKey.equals(key)) {
64 | dirty = true;
65 | return;
66 | }
67 | Object oldValue = getAttribute(key);
68 | super.setAttribute(key, value);
69 |
70 | if ((value != null || oldValue != null) && (value == null && oldValue != null || oldValue == null && value != null || !value.getClass().isInstance(oldValue) || !value.equals(oldValue))) {
71 | if (this.manager instanceof RedisSessionManager && ((RedisSessionManager) this.manager).getSaveOnChange()) {
72 | try {
73 | ((RedisSessionManager) this.manager).save(this, true);
74 | } catch (Exception e) {
75 | log.error("Error saving session on setAttribute (triggered by saveOnChange=true):", e);
76 | }
77 | } else {
78 | changedAttributes.put(key, value);
79 | }
80 | }
81 | }
82 |
83 | @Override
84 | public Object getAttribute(String name) {
85 | return super.getAttribute(name);
86 | }
87 |
88 | @Override
89 | public Enumeration getAttributeNames() {
90 | return super.getAttributeNames();
91 | }
92 |
93 | @Override
94 | public void removeAttribute(String name) {
95 | super.removeAttribute(name);
96 | if (this.manager instanceof RedisSessionManager && ((RedisSessionManager) this.manager).getSaveOnChange()) {
97 | try {
98 | ((RedisSessionManager) this.manager).save(this, true);
99 | } catch (Exception e) {
100 | log.error("Error saving session on setAttribute (triggered by saveOnChange=true): ", e);
101 | }
102 | } else {
103 | dirty = true;
104 | }
105 | }
106 |
107 | @Override
108 | public void setId(String id) {
109 | this.id = id;
110 | }
111 |
112 | @Override
113 | public void setPrincipal(Principal principal) {
114 | dirty = true;
115 | super.setPrincipal(principal);
116 | }
117 |
118 | @Override
119 | public void writeObjectData(java.io.ObjectOutputStream out) throws IOException {
120 | super.writeObjectData(out);
121 | out.writeLong(this.getCreationTime());
122 | }
123 |
124 | @Override
125 | public void readObjectData(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
126 | super.readObjectData(in);
127 | this.setCreationTime(in.readLong());
128 | }
129 | }
--------------------------------------------------------------------------------
/src/com/r/data/cache/RedisClusterCacheUtils.java:
--------------------------------------------------------------------------------
1 | package com.r.data.cache;
2 |
3 | import java.util.Properties;
4 |
5 | import org.apache.commons.logging.Log;
6 | import org.apache.commons.logging.LogFactory;
7 |
8 | import redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException;
9 | import redis.clients.jedis.exceptions.JedisConnectionException;
10 |
11 | import com.r.data.cache.manager.RedisClusterManager;
12 |
13 | /**
14 | * Redis cluster cache utils
15 | *
16 | * @author Ranjith
17 | * @since 1.0
18 | */
19 | public class RedisClusterCacheUtils implements ICacheUtils
20 | {
21 | private Log log = LogFactory.getLog(RedisClusterCacheUtils.class);
22 |
23 | public boolean available = false;
24 |
25 | private static int numRetries = 30;
26 |
27 | private RedisClusterManager clusterManager = null;
28 |
29 | RedisClusterCacheUtils(Properties properties) throws Exception {
30 | try {
31 | clusterManager = RedisClusterManager.createInstance(properties);
32 | } catch (Exception e) {
33 | this.available = false;
34 | log.error("Exception initializing Redis cluster: " + e);
35 | }
36 | this.available = true;
37 | }
38 |
39 | @Override
40 | public boolean isAvailable() {
41 | return available;
42 | }
43 |
44 | @Override
45 | public void setByteArray(byte[] key, byte[] value) {
46 | int tries = 0;
47 | boolean sucess = false;
48 | do {
49 | tries++;
50 | try {
51 | if (key != null && value != null) {
52 | clusterManager.getJedis().set(key, value);
53 | }
54 | sucess = true;
55 | } catch (JedisClusterMaxRedirectionsException | JedisConnectionException e) {
56 | log.error("Jedis connection failed, retrying..." + tries);
57 | waitforFailover();
58 | }
59 | } while (!sucess && tries <= numRetries);
60 | }
61 |
62 | @Override
63 | public Long setStringIfKeyNotExists(byte[] key, byte[] value) {
64 | int tries = 0;
65 | Long retVal = null;
66 | boolean sucess = false;
67 | do {
68 | tries++;
69 | try {
70 | if (key != null && value != null) {
71 | retVal = clusterManager.getJedis().setnx(key, value);
72 | }
73 | sucess = true;
74 | } catch (JedisClusterMaxRedirectionsException | JedisConnectionException e) {
75 | log.error("Jedis connection failed, retrying..." + tries);
76 | waitforFailover();
77 | }
78 | } while (!sucess && tries <= numRetries);
79 | return retVal;
80 | }
81 |
82 | @Override
83 | public void expire(byte[] key, int ttl) {
84 | int tries = 0;
85 | boolean sucess = false;
86 | do {
87 | tries++;
88 | try {
89 | clusterManager.getJedis().expire(key, ttl);
90 | sucess = true;
91 | } catch (JedisClusterMaxRedirectionsException | JedisConnectionException e) {
92 | log.error("Jedis connection failed, retrying..." + tries);
93 | waitforFailover();
94 | }
95 | } while (!sucess && tries <= numRetries);
96 | }
97 |
98 | @Override
99 | public byte[] getByteArray(String key) {
100 | int tries = 0;
101 | boolean sucess = false;
102 | byte[] array = new byte[1];
103 | do {
104 | tries++;
105 | try {
106 | if (key != null) {
107 | array = clusterManager.getJedis().get(key.getBytes());
108 | }
109 | sucess = true;
110 | } catch (JedisClusterMaxRedirectionsException | JedisConnectionException e) {
111 | log.error("Jedis connection failed, retrying..." + tries);
112 | waitforFailover();
113 | }
114 | } while (!sucess && tries <= numRetries);
115 | return array;
116 | }
117 |
118 | @Override
119 | public void deleteKey(String key) {
120 | int tries = 0;
121 | boolean sucess = false;
122 | do {
123 | tries++;
124 | try {
125 | if (key != null) {
126 | clusterManager.getJedis().del(key);
127 | }
128 | sucess = true;
129 | } catch (JedisClusterMaxRedirectionsException | JedisConnectionException e) {
130 | log.error("Jedis connection failed, retrying..." + tries);
131 | waitforFailover();
132 | }
133 | } while (!sucess && tries <= numRetries);
134 | }
135 |
136 | /*
137 | * method to wait for cluster fails
138 | */
139 | private void waitforFailover() {
140 | try {
141 | Thread.sleep(4000);
142 | } catch (InterruptedException ex) {
143 | Thread.currentThread().interrupt();
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/src/com/r/tomcat/session/management/redis/RedisSessionManager.java:
--------------------------------------------------------------------------------
1 | package com.r.tomcat.session.management.redis;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.util.Arrays;
8 | import java.util.EnumSet;
9 | import java.util.Iterator;
10 | import java.util.Properties;
11 |
12 | import org.apache.catalina.Lifecycle;
13 | import org.apache.catalina.LifecycleException;
14 | import org.apache.catalina.LifecycleListener;
15 | import org.apache.catalina.LifecycleState;
16 | import org.apache.catalina.Loader;
17 | import org.apache.catalina.Session;
18 | import org.apache.catalina.Valve;
19 | import org.apache.catalina.session.ManagerBase;
20 | import org.apache.catalina.util.LifecycleSupport;
21 | import org.apache.commons.logging.Log;
22 | import org.apache.commons.logging.LogFactory;
23 |
24 | import com.r.data.cache.ICacheUtils;
25 | import com.r.data.cache.RedisCacheFactory;
26 | import com.r.data.cache.constants.RedisConstants;
27 | import com.r.tomcat.session.management.commons.DeserializedSessionContainer;
28 | import com.r.tomcat.session.management.commons.IRequestSessionManager;
29 | import com.r.tomcat.session.management.commons.ISerializer;
30 | import com.r.tomcat.session.management.commons.JavaSerializer;
31 | import com.r.tomcat.session.management.commons.SessionHandlerValve;
32 | import com.r.tomcat.session.management.commons.SessionSerializationMetadata;
33 |
34 | /**
35 | * Tomcat redis session manager
36 | *
37 | * @author Ranjith
38 | * @since 1.0
39 | */
40 | public class RedisSessionManager extends ManagerBase implements IRequestSessionManager, Lifecycle
41 | {
42 | private Log log = LogFactory.getLog(RedisSessionManager.class);
43 |
44 | private ICacheUtils cache;
45 |
46 | protected ISerializer serializer;
47 |
48 | protected SessionHandlerValve handlerValve;
49 |
50 | protected byte[] NULL_SESSION = "null".getBytes();
51 |
52 | protected LifecycleSupport lifecycle = new LifecycleSupport(this);
53 |
54 | protected ThreadLocal currentSessionId = new ThreadLocal<>();
55 |
56 | protected ThreadLocal currentSession = new ThreadLocal<>();
57 |
58 | protected ThreadLocal currentSessionIsPersisted = new ThreadLocal<>();
59 |
60 | protected EnumSet sessionPersistPoliciesSet = EnumSet.of(SessionPersistPolicy.DEFAULT);
61 |
62 | protected ThreadLocal currentSessionSerializationMetadata = new ThreadLocal<>();
63 |
64 | enum SessionPersistPolicy {
65 | DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST;
66 |
67 | static SessionPersistPolicy fromName(String name) {
68 | for (SessionPersistPolicy policy : SessionPersistPolicy.values()) {
69 | if (policy.name().equalsIgnoreCase(name)) {
70 | return policy;
71 | }
72 | }
73 | throw new IllegalArgumentException("Invalid session persist policy [" + name + "]. Must be one of " + Arrays.asList(SessionPersistPolicy.values()) + ".");
74 | }
75 | }
76 |
77 | public String getSessionPersistPolicies() {
78 | StringBuilder policies = new StringBuilder();
79 | for (Iterator iter = this.sessionPersistPoliciesSet.iterator(); iter.hasNext();) {
80 | SessionPersistPolicy policy = iter.next();
81 | policies.append(policy.name());
82 | if (iter.hasNext()) {
83 | policies.append(",");
84 | }
85 | }
86 | return policies.toString();
87 | }
88 |
89 | public void setSessionPersistPolicies(String sessionPersistPolicies) {
90 | String[] policyArray = sessionPersistPolicies.split(",");
91 | EnumSet policySet = EnumSet.of(SessionPersistPolicy.DEFAULT);
92 | for (String policyName : policyArray) {
93 | SessionPersistPolicy policy = SessionPersistPolicy.fromName(policyName);
94 | policySet.add(policy);
95 | }
96 | this.sessionPersistPoliciesSet = policySet;
97 | }
98 |
99 | public boolean getSaveOnChange() {
100 | return this.sessionPersistPoliciesSet.contains(SessionPersistPolicy.SAVE_ON_CHANGE);
101 | }
102 |
103 | public boolean getAlwaysSaveAfterRequest() {
104 | return this.sessionPersistPoliciesSet.contains(SessionPersistPolicy.ALWAYS_SAVE_AFTER_REQUEST);
105 | }
106 |
107 | /**
108 | * Add a lifecycle event listener to this component.
109 | *
110 | * @param listener
111 | * - The listener to add
112 | */
113 | @Override
114 | public void addLifecycleListener(LifecycleListener listener) {
115 | lifecycle.addLifecycleListener(listener);
116 | }
117 |
118 | /**
119 | * Get the lifecycle listeners associated with this lifecycle. If this Lifecycle has no listeners registered, a zero-length array is returned.
120 | */
121 | @Override
122 | public LifecycleListener[] findLifecycleListeners() {
123 | return lifecycle.findLifecycleListeners();
124 | }
125 |
126 | /**
127 | * Remove a lifecycle event listener from this component.
128 | *
129 | * @param listener
130 | * - The listener to remove
131 | */
132 | @Override
133 | public void removeLifecycleListener(LifecycleListener listener) {
134 | lifecycle.removeLifecycleListener(listener);
135 | }
136 |
137 | /**
138 | * Start this component and implement the requirements of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
139 | *
140 | * @exception LifecycleException
141 | * - if this component detects a fatal error that prevents this component from being used
142 | */
143 | @Override
144 | protected synchronized void startInternal() throws LifecycleException {
145 | super.startInternal();
146 | setState(LifecycleState.STARTING);
147 | Boolean attachedToValve = false;
148 | for (Valve valve : getContainer().getPipeline().getValves()) {
149 | if (valve instanceof SessionHandlerValve) {
150 | this.handlerValve = (SessionHandlerValve) valve;
151 | this.handlerValve.setRedisSessionManager(this);
152 | attachedToValve = true;
153 | break;
154 | }
155 | }
156 | if (!attachedToValve) {
157 | throw new LifecycleException("Unable to attach to session handling valve; sessions cannot be saved after the request without the valve starting properly.");
158 | }
159 | try {
160 | initializeSerializer();
161 | cache = RedisCacheFactory.createInstance(getRedisProperties());
162 | } catch (Exception e) {
163 | log.error("Error while initializing serializer/rediscache", e);
164 | }
165 | log.info("The sessions will expire after " + getMaxInactiveInterval() + " seconds");
166 | setDistributable(true);
167 | }
168 |
169 | /**
170 | * Stop this component and implement the requirements of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
171 | *
172 | * @exception LifecycleException
173 | * - if this component detects a fatal error that prevents this component from being used
174 | */
175 | @Override
176 | protected synchronized void stopInternal() throws LifecycleException {
177 | setState(LifecycleState.STOPPING);
178 | super.stopInternal();
179 | }
180 |
181 | @Override
182 | public Session createSession(String requestedSessionId) {
183 | RedisSession redisSession = null;
184 | String sessionId = null;
185 | String jvmRoute = getJvmRoute();
186 | if (null != requestedSessionId) {
187 | sessionId = requestedSessionId;
188 | if (jvmRoute != null) {
189 | sessionId += '.' + jvmRoute;
190 | }
191 | if (cache.setStringIfKeyNotExists(sessionId.getBytes(), NULL_SESSION) == 0L) {
192 | sessionId = null;
193 | }
194 | } else {
195 | do {
196 | sessionId = generateSessionId();
197 | if (jvmRoute != null) {
198 | sessionId += '.' + jvmRoute;
199 | }
200 | } while (cache.setStringIfKeyNotExists(sessionId.getBytes(), NULL_SESSION) == 0L); // 1 = key set; 0 = key already existed
201 | }
202 | if (null != sessionId) {
203 | redisSession = (RedisSession) createEmptySession();
204 | redisSession.setNew(true);
205 | redisSession.setValid(true);
206 | redisSession.setCreationTime(System.currentTimeMillis());
207 | redisSession.setMaxInactiveInterval(getMaxInactiveInterval());
208 | redisSession.setId(sessionId);
209 | redisSession.tellNew();
210 | }
211 | currentSession.set(redisSession);
212 | currentSessionId.set(sessionId);
213 | currentSessionIsPersisted.set(false);
214 | currentSessionSerializationMetadata.set(new SessionSerializationMetadata());
215 | if (null != redisSession) {
216 | try {
217 | save(redisSession, true);
218 | } catch (Exception e) {
219 | currentSession.set(null);
220 | currentSessionId.set(null);
221 | redisSession = null;
222 | }
223 | }
224 | return redisSession;
225 | }
226 |
227 | @Override
228 | public Session createEmptySession() {
229 | return new RedisSession(this);
230 | }
231 |
232 | @Override
233 | public void add(Session session) {
234 | try {
235 | save(session);
236 | } catch (Exception e) {
237 | log.error("Error occured while adding session", e);
238 | }
239 | }
240 |
241 | @Override
242 | public Session findSession(String id) throws IOException {
243 | RedisSession redisSession = null;
244 | if (id == null) {
245 | currentSessionIsPersisted.set(false);
246 | currentSession.set(null);
247 | currentSessionSerializationMetadata.set(null);
248 | currentSessionId.set(null);
249 | } else if (id.equals(currentSessionId.get())) {
250 | redisSession = currentSession.get();
251 | } else {
252 | byte[] data = loadSessionDataFromRedis(id);
253 | if (data != null) {
254 | DeserializedSessionContainer container = sessionFromSerializedData(id, data);
255 | redisSession = (RedisSession) container.session;
256 | currentSession.set(redisSession);
257 | currentSessionSerializationMetadata.set(container.metadata);
258 | currentSessionIsPersisted.set(true);
259 | currentSessionId.set(id);
260 | } else {
261 | currentSessionIsPersisted.set(false);
262 | currentSession.set(null);
263 | currentSessionSerializationMetadata.set(null);
264 | currentSessionId.set(null);
265 | }
266 | }
267 | return redisSession;
268 | }
269 |
270 | public byte[] loadSessionDataFromRedis(String id) throws IOException {
271 | return cache.getByteArray(id);
272 | }
273 |
274 | public DeserializedSessionContainer sessionFromSerializedData(String id, byte[] data) throws IOException {
275 | if (Arrays.equals(NULL_SESSION, data)) {
276 | throw new IOException("Serialized session data was equal to NULL_SESSION");
277 | }
278 | RedisSession requestSession = null;
279 | SessionSerializationMetadata metadata = null;
280 | try {
281 | metadata = new SessionSerializationMetadata();
282 | requestSession = (RedisSession) createEmptySession();
283 | serializer.deserializeInto(data, requestSession, metadata);
284 | requestSession.setId(id);
285 | requestSession.setNew(false);
286 | requestSession.setMaxInactiveInterval(getMaxInactiveInterval());
287 | requestSession.access();
288 | requestSession.setValid(true);
289 | requestSession.resetDirtyTracking();
290 | } catch (Exception e) {
291 | log.error("Unable to deserialize into session", e);
292 | }
293 | return new DeserializedSessionContainer(requestSession, metadata);
294 | }
295 |
296 | public void save(Session session) throws Exception {
297 | save(session, false);
298 | }
299 |
300 | public void save(Session session, boolean forceSave) throws Exception {
301 | Boolean isCurrentSessionPersisted;
302 | try {
303 | RedisSession redisSession = (RedisSession) session;
304 | byte[] binaryId = redisSession.getId().getBytes();
305 | SessionSerializationMetadata sessionSerializationMetadata = currentSessionSerializationMetadata.get();
306 | byte[] originalSessionAttributesHash = sessionSerializationMetadata.getSessionAttributesHash();
307 | byte[] sessionAttributesHash = null;
308 | if (forceSave || redisSession.isDirty() || null == (isCurrentSessionPersisted = this.currentSessionIsPersisted.get()) || !isCurrentSessionPersisted || !Arrays.equals(originalSessionAttributesHash, (sessionAttributesHash = serializer.attributesHashFrom(redisSession)))) {
309 | if (null == sessionAttributesHash) {
310 | sessionAttributesHash = serializer.attributesHashFrom(redisSession);
311 | }
312 | SessionSerializationMetadata updatedSerializationMetadata = new SessionSerializationMetadata();
313 | updatedSerializationMetadata.setSessionAttributesHash(sessionAttributesHash);
314 | cache.setByteArray(binaryId, serializer.serializeFrom(redisSession, updatedSerializationMetadata));
315 | redisSession.resetDirtyTracking();
316 | currentSessionSerializationMetadata.set(updatedSerializationMetadata);
317 | currentSessionIsPersisted.set(true);
318 | }
319 | cache.expire(binaryId, getMaxInactiveInterval());
320 | } catch (Exception e) {
321 | throw e;
322 | }
323 | }
324 |
325 | @Override
326 | public void remove(Session session) {
327 | remove(session, false);
328 | }
329 |
330 | @Override
331 | public void remove(Session session, boolean update) {
332 | if (cache.isAvailable()) {
333 | cache.deleteKey(session.getId());
334 | }
335 | }
336 |
337 | public void afterRequest() {
338 | RedisSession redisSession = currentSession.get();
339 | if (redisSession != null) {
340 | try {
341 | if (redisSession.isValid()) {
342 | log.debug("Request with session completed, saving session " + redisSession.getId());
343 | save(redisSession, getAlwaysSaveAfterRequest());
344 | } else {
345 | log.debug("HTTP Session has been invalidated, removing :" + redisSession.getId());
346 | remove(redisSession);
347 | }
348 | } catch (Exception e) {
349 | log.error("Error storing/removing session", e);
350 | } finally {
351 | currentSession.remove();
352 | currentSessionId.remove();
353 | currentSessionIsPersisted.remove();
354 | }
355 | }
356 | }
357 |
358 | private void initializeSerializer() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
359 | serializer = new JavaSerializer();
360 | Loader loader = null;
361 | if (getContainer() != null) {
362 | loader = getContainer().getLoader();
363 | }
364 | ClassLoader classLoader = null;
365 | if (loader != null) {
366 | classLoader = loader.getClassLoader();
367 | }
368 | serializer.setClassLoader(classLoader);
369 | }
370 |
371 | @Override
372 | public void load() throws ClassNotFoundException, IOException {
373 | // TODO Auto-generated method stub
374 | }
375 |
376 | @Override
377 | public void unload() throws IOException {
378 | // TODO Auto-generated method stub
379 | }
380 |
381 | private Properties getRedisProperties() throws Exception {
382 | Properties properties = null;
383 | try {
384 | if (properties == null || properties.isEmpty()) {
385 | InputStream resourceStream = null;
386 | try {
387 | resourceStream = null;
388 | properties = new Properties();
389 | File file = new File(System.getProperty("catalina.home").concat(File.separator).concat("conf").concat(File.separator).concat(RedisConstants.REDIS_PROPERTIES_FILE));
390 | if (file.exists()) {
391 | resourceStream = new FileInputStream(file);
392 | }
393 | if (resourceStream == null) {
394 | ClassLoader loader = Thread.currentThread().getContextClassLoader();
395 | resourceStream = loader.getResourceAsStream(RedisConstants.REDIS_PROPERTIES_FILE);
396 | }
397 | properties.load(resourceStream);
398 | } finally {
399 | resourceStream.close();
400 | }
401 | }
402 | } catch (IOException e) {
403 | log.error("Error occurred fetching redis properties", e);
404 | }
405 | return properties;
406 | }
407 | }
--------------------------------------------------------------------------------