├── 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 | } --------------------------------------------------------------------------------