Bug fixes and added redis sentinel supportability changes.
parent
9069315d96
commit
d562a03d63
40
pom.xml
40
pom.xml
|
@ -14,42 +14,56 @@
|
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>1.7</java.version>
|
||||
|
||||
<jedis.version>2.9.0</jedis.version>
|
||||
<commons-pool2.version>2.4.2</commons-pool2.version>
|
||||
<commons-logging.version>1.2</commons-logging.version>
|
||||
|
||||
<maven-compiler.version>3.6.1</maven-compiler.version>
|
||||
<source-java.version>1.7</source-java.version>
|
||||
<target-java.version>${source-java.version}</target-java.version>
|
||||
|
||||
<!-- For local development properties begins.. -->
|
||||
<tomcat-catalina.version>apache-tomcat-8.5.32</tomcat-catalina.version>
|
||||
<tomcat-servlet-api.version>apache-tomcat-8.5.32</tomcat-servlet-api.version>
|
||||
<tomcat-api.version>apache-tomcat-8.5.32</tomcat-api.version>
|
||||
<!-- For local development properties end.. -->
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>2.9.0</version>
|
||||
<version>${jedis.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
<version>2.4.2</version>
|
||||
<version>${commons-pool2.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
<version>1.2</version>
|
||||
<version>${commons-logging.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- For local development -->
|
||||
<!-- <dependency>
|
||||
<!-- For local development dependency begins.. -->
|
||||
<dependency>
|
||||
<groupId>apache-tomcat</groupId>
|
||||
<artifactId>catalina</artifactId>
|
||||
<version>apache-tomcat-8.5.20</version>
|
||||
<version>${tomcat-catalina.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>apache-tomcat</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>apache-tomcat-8.5.20</version>
|
||||
<version>${tomcat-servlet-api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>apache-tomcat</groupId>
|
||||
<artifactId>tomcat-api</artifactId>
|
||||
<version>apache-tomcat-8.5.20</version>
|
||||
</dependency> -->
|
||||
<!-- For local development end.. -->
|
||||
<version>${tomcat-api.version}</version>
|
||||
</dependency>
|
||||
<!-- For local development dependency end.. -->
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -57,10 +71,10 @@
|
|||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.6.1</version>
|
||||
<version>${maven-compiler.version}</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
<source>${source-java.version}</source>
|
||||
<target>${target-java.version}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package tomcat.request.session;
|
||||
|
||||
import org.apache.catalina.util.CustomObjectInputStream;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
@ -11,30 +13,18 @@ import java.security.MessageDigest;
|
|||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.apache.catalina.util.CustomObjectInputStream;
|
||||
|
||||
/**
|
||||
* Tomcat clustering with Redis data-cache implementation.
|
||||
*
|
||||
* Session serialization utility.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 2.0
|
||||
*/
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
public class SerializationUtil {
|
||||
|
||||
private ClassLoader loader;
|
||||
|
||||
/**
|
||||
* To set class loader
|
||||
*/
|
||||
/** To set class loader. */
|
||||
public void setClassLoader(ClassLoader loader) {
|
||||
this.loader = loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* To get session attributes hash code
|
||||
*/
|
||||
/** To get session attributes hash code. */
|
||||
public byte[] getSessionAttributesHashCode(Session session) throws IOException {
|
||||
byte[] serialized;
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
|
@ -61,9 +51,7 @@ public class SerializationUtil {
|
|||
return digester.digest(serialized);
|
||||
}
|
||||
|
||||
/**
|
||||
* To serialize session object
|
||||
*/
|
||||
/** To serialize session object. */
|
||||
public byte[] serializeSessionData(Session session, SessionMetadata metadata) throws IOException {
|
||||
byte[] serialized;
|
||||
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
|
@ -76,9 +64,7 @@ public class SerializationUtil {
|
|||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* To de-serialize session object
|
||||
*/
|
||||
/** To de-serialize session object. */
|
||||
public void deserializeSessionData(byte[] data, Session session, SessionMetadata metadata)
|
||||
throws IOException, ClassNotFoundException {
|
||||
try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data));
|
||||
|
@ -88,4 +74,5 @@ public class SerializationUtil {
|
|||
session.readObjectData(ois);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -13,24 +13,15 @@ import org.apache.catalina.session.StandardSession;
|
|||
|
||||
import tomcat.request.session.redis.SessionManager;
|
||||
|
||||
/**
|
||||
* Tomcat clustering with Redis data-cache implementation.
|
||||
*
|
||||
* This class is uses to store and retrieve the HTTP request session objects.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 2.0
|
||||
*/
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
public class Session extends StandardSession {
|
||||
|
||||
private static final long serialVersionUID = -6056744304016869278L;
|
||||
|
||||
protected Boolean dirty;
|
||||
|
||||
protected Map<String, Object> changedAttributes;
|
||||
|
||||
protected static Boolean manualDirtyTrackingSupportEnabled = false;
|
||||
|
||||
protected static String manualDirtyTrackingAttributeKey = "__changed__";
|
||||
|
||||
public Session(Manager manager) {
|
||||
|
@ -38,38 +29,39 @@ public class Session extends StandardSession {
|
|||
resetDirtyTracking();
|
||||
}
|
||||
|
||||
/** To reset dirty tracking. */
|
||||
public void resetDirtyTracking() {
|
||||
this.changedAttributes = new HashMap<>();
|
||||
this.dirty = false;
|
||||
}
|
||||
|
||||
/** To set manual dirty tracking support enabled. */
|
||||
public static void setManualDirtyTrackingSupportEnabled(boolean enabled) {
|
||||
manualDirtyTrackingSupportEnabled = enabled;
|
||||
}
|
||||
|
||||
/** To set manual dirty tracking attribute key. */
|
||||
public static void setManualDirtyTrackingAttributeKey(String key) {
|
||||
manualDirtyTrackingAttributeKey = key;
|
||||
}
|
||||
|
||||
/** Returns dirty check. */
|
||||
public Boolean isDirty() {
|
||||
return this.dirty || !this.changedAttributes.isEmpty();
|
||||
}
|
||||
|
||||
/** To get changed attributes. */
|
||||
public Map<String, Object> getChangedAttributes() {
|
||||
return this.changedAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void setAttribute(String key, Object value) {
|
||||
if (manualDirtyTrackingSupportEnabled && manualDirtyTrackingAttributeKey.equals(key)) {
|
||||
|
@ -92,25 +84,19 @@ public class Session extends StandardSession {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Object getAttribute(String name) {
|
||||
return super.getAttribute(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Enumeration<String> getAttributeNames() {
|
||||
return super.getAttributeNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
super.removeAttribute(name);
|
||||
|
@ -122,38 +108,31 @@ public class Session extends StandardSession {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void setPrincipal(Principal principal) {
|
||||
super.setPrincipal(principal);
|
||||
this.dirty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void writeObjectData(ObjectOutputStream out) throws IOException {
|
||||
super.writeObjectData(out);
|
||||
out.writeLong(this.getCreationTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void readObjectData(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
super.readObjectData(in);
|
||||
this.setCreationTime(in.readLong());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void invalidate() {
|
||||
super.invalidate();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,18 +1,22 @@
|
|||
package tomcat.request.session;
|
||||
|
||||
/**
|
||||
* Tomcat clustering with Redis data-cache implementation.
|
||||
*
|
||||
* Session constants.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 2.0
|
||||
*/
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
public interface SessionConstants {
|
||||
|
||||
byte[] NULL_SESSION = "null".getBytes();
|
||||
|
||||
String CATALINA_BASE = "catalina.base";
|
||||
|
||||
String CONF = "conf";
|
||||
|
||||
enum SessionPolicy {
|
||||
DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST;
|
||||
|
||||
public static SessionPolicy fromName(String name) {
|
||||
for (SessionPolicy policy : SessionPolicy.values()) {
|
||||
if (policy.name().equalsIgnoreCase(name)) {
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid session policy [" + name + "]");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,82 +1,54 @@
|
|||
package tomcat.request.session;
|
||||
|
||||
/**
|
||||
* Tomcat clustering with Redis data-cache implementation.
|
||||
*
|
||||
* Session context uses to manage current session data.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 2.0
|
||||
*/
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
public class SessionContext {
|
||||
|
||||
private String id;
|
||||
|
||||
private Session session;
|
||||
|
||||
private boolean persisted;
|
||||
|
||||
private SessionMetadata metadata;
|
||||
|
||||
/**
|
||||
* To get session id
|
||||
*/
|
||||
/** To get session id. */
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* To set session id
|
||||
*/
|
||||
/** To set session id. */
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* To get session
|
||||
*/
|
||||
/** To get session. */
|
||||
public Session getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* To set session
|
||||
*/
|
||||
/** To set session. */
|
||||
public void setSession(Session session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/**
|
||||
* To check session is persisted
|
||||
*/
|
||||
/** To check session is persisted. */
|
||||
public boolean isPersisted() {
|
||||
return persisted;
|
||||
}
|
||||
|
||||
/**
|
||||
* To set session persisted
|
||||
*/
|
||||
/** To set session persisted. */
|
||||
public void setPersisted(boolean persisted) {
|
||||
this.persisted = persisted;
|
||||
}
|
||||
|
||||
/**
|
||||
* To get session meta-data
|
||||
*/
|
||||
/** To get session meta-data. */
|
||||
public SessionMetadata getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* To set session meta-data
|
||||
*/
|
||||
/** To set session meta-data. */
|
||||
public void setMetadata(SessionMetadata metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SessionContext [id=" + id + "]";
|
||||
|
|
|
@ -5,14 +5,7 @@ import java.io.ObjectInputStream;
|
|||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Tomcat clustering with Redis data-cache implementation.
|
||||
*
|
||||
* This class is uses to store and retrieve the HTTP request session object meta-data.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 2.0
|
||||
*/
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
public class SessionMetadata implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 124438185184833546L;
|
||||
|
@ -23,42 +16,33 @@ public class SessionMetadata implements Serializable {
|
|||
this.attributesHash = new byte[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* To get session meta-data hash
|
||||
*/
|
||||
/** To get session meta-data hash. */
|
||||
public byte[] getAttributesHash() {
|
||||
return this.attributesHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* To set session meta-data hash
|
||||
*/
|
||||
/** To set session meta-data hash. */
|
||||
public void setAttributesHash(byte[] attributesHash) {
|
||||
this.attributesHash = attributesHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* To copy session meta-data
|
||||
*/
|
||||
/** To copy session meta-data. */
|
||||
public void copyFieldsFrom(SessionMetadata metadata) {
|
||||
this.setAttributesHash(metadata.getAttributesHash());
|
||||
}
|
||||
|
||||
/**
|
||||
* To write session meta-data to output stream
|
||||
*/
|
||||
/** To write session meta-data to output stream. */
|
||||
private void writeObject(ObjectOutputStream out) throws IOException {
|
||||
out.writeInt(this.attributesHash.length);
|
||||
out.write(this.attributesHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* To read session meta-data from input stream
|
||||
*/
|
||||
/** To read session meta-data from input stream. */
|
||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
int hashLength = in.readInt();
|
||||
byte[] attributesHash = new byte[hashLength];
|
||||
in.read(attributesHash, 0, hashLength);
|
||||
this.attributesHash = attributesHash;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,39 +1,48 @@
|
|||
package tomcat.request.session.data.cache;
|
||||
|
||||
/**
|
||||
* Tomcat clustering with Redis data-cache implementation.
|
||||
*
|
||||
* API for Data cache.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 2.0
|
||||
*/
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
public interface DataCache {
|
||||
|
||||
/**
|
||||
* To set value in data-cache
|
||||
* Set value in data-cache.
|
||||
*
|
||||
* @param key - key with which the specified value is to be associated.
|
||||
* @param value - value to be associated with the specified key.
|
||||
* @return - Returns the value.
|
||||
*/
|
||||
byte[] set(String key, byte[] value);
|
||||
|
||||
/**
|
||||
* To set value if key not exists in data-cache
|
||||
* Set value if key not exists in data-cache.
|
||||
*
|
||||
* Returns If key exists = 0 else 1
|
||||
* @param key - key with which the specified value is to be associated.
|
||||
* @param value - value to be associated with the specified key.
|
||||
* @return - Returns '0' if key already exists else '1'.
|
||||
*/
|
||||
Long setnx(String key, byte[] value);
|
||||
|
||||
/**
|
||||
* To expire the value based on key in data-cache
|
||||
* Set expiry in data-cache.
|
||||
*
|
||||
* @param key - key with which the specified value is to be associated.
|
||||
* @param seconds - expiration time in seconds.
|
||||
* @return - Returns the expiration time in seconds.
|
||||
*/
|
||||
Long expire(String key, int seconds);
|
||||
|
||||
/**
|
||||
* To get the value based on key from data-cache
|
||||
* Get value from data-cache.
|
||||
*
|
||||
* @param key - key with which the specified value is to be associated.
|
||||
* @return - Returns the value.
|
||||
*/
|
||||
byte[] get(String key);
|
||||
|
||||
/**
|
||||
* To delete the value based on key from data-cache
|
||||
* Delete value from data-cache.
|
||||
*
|
||||
* @param key - key with which the specified value is to be associated.
|
||||
* @return - Returns the number of keys that were removed.
|
||||
*/
|
||||
Long delete(String key);
|
||||
|
||||
|
|
|
@ -1,147 +0,0 @@
|
|||
package tomcat.request.session.data.cache.impl;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
import tomcat.request.session.data.cache.DataCache;
|
||||
|
||||
/**
|
||||
* Tomcat clustering with Redis data-cache implementation.
|
||||
*
|
||||
* Redis stand-alone mode data-cache implementation.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 2.0
|
||||
*/
|
||||
class RedisCacheUtil implements DataCache {
|
||||
|
||||
private JedisPool pool;
|
||||
|
||||
private static final int NUM_RETRIES = 3;
|
||||
|
||||
private Log log = LogFactory.getLog(RedisCacheUtil.class);
|
||||
|
||||
RedisCacheUtil(String host, int port, String password, int database, int timeout,
|
||||
JedisPoolConfig poolConfig) {
|
||||
pool = new JedisPool(poolConfig, host, port, timeout, password, database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public byte[] set(String key, byte[] value) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
String retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try (Jedis jedis = pool.getResource()) {
|
||||
retVal = jedis.set(key.getBytes(), value);
|
||||
sucess = true;
|
||||
} catch (JedisConnectionException ex) {
|
||||
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
|
||||
if (tries == NUM_RETRIES) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
} while (!sucess && tries <= NUM_RETRIES);
|
||||
return (retVal != null) ? retVal.getBytes() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Long setnx(String key, byte[] value) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
Long retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try (Jedis jedis = pool.getResource()) {
|
||||
retVal = jedis.setnx(key.getBytes(), value);
|
||||
sucess = true;
|
||||
} catch (JedisConnectionException ex) {
|
||||
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
|
||||
if (tries == NUM_RETRIES) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
} while (!sucess && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Long expire(String key, int seconds) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
Long retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try (Jedis jedis = pool.getResource()) {
|
||||
retVal = jedis.expire(key, seconds);
|
||||
sucess = true;
|
||||
} catch (JedisConnectionException ex) {
|
||||
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
|
||||
if (tries == NUM_RETRIES) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
} while (!sucess && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public byte[] get(String key) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
byte[] retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try (Jedis jedis = pool.getResource()) {
|
||||
retVal = jedis.get(key.getBytes());
|
||||
sucess = true;
|
||||
} catch (JedisConnectionException ex) {
|
||||
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
|
||||
if (tries == NUM_RETRIES) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
} while (!sucess && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Long delete(String key) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
Long retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try (Jedis jedis = pool.getResource()) {
|
||||
retVal = jedis.del(key);
|
||||
sucess = true;
|
||||
} catch (JedisConnectionException ex) {
|
||||
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
|
||||
if (tries == NUM_RETRIES) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
} while (!sucess && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
}
|
|
@ -1,169 +0,0 @@
|
|||
package tomcat.request.session.data.cache.impl;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import redis.clients.jedis.HostAndPort;
|
||||
import redis.clients.jedis.JedisCluster;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.Protocol;
|
||||
import redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
import tomcat.request.session.data.cache.DataCache;
|
||||
|
||||
/**
|
||||
* Tomcat clustering with Redis data-cache implementation.
|
||||
*
|
||||
* Redis multiple node cluster data-cache implementation.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 2.0
|
||||
*/
|
||||
class RedisClusterCacheUtil implements DataCache {
|
||||
|
||||
private JedisCluster cluster;
|
||||
|
||||
private static final int NUM_RETRIES = 30;
|
||||
private static final int DEFAULT_MAX_REDIRECTIONS = 5;
|
||||
|
||||
private Log log = LogFactory.getLog(RedisClusterCacheUtil.class);
|
||||
|
||||
RedisClusterCacheUtil(Set<HostAndPort> nodes, String password, int timeout,
|
||||
JedisPoolConfig poolConfig) {
|
||||
cluster = new JedisCluster(nodes, timeout, Protocol.DEFAULT_TIMEOUT, DEFAULT_MAX_REDIRECTIONS,
|
||||
password, poolConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public byte[] set(String key, byte[] value) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
String retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
retVal = cluster.set(key.getBytes(), value);
|
||||
sucess = true;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
|
||||
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
|
||||
if (tries == NUM_RETRIES) {
|
||||
throw ex;
|
||||
}
|
||||
waitforFailover();
|
||||
}
|
||||
} while (!sucess && tries <= NUM_RETRIES);
|
||||
return (retVal != null) ? retVal.getBytes() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Long setnx(String key, byte[] value) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
Long retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
retVal = cluster.setnx(key.getBytes(), value);
|
||||
sucess = true;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
|
||||
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
|
||||
if (tries == NUM_RETRIES) {
|
||||
throw ex;
|
||||
}
|
||||
waitforFailover();
|
||||
}
|
||||
} while (!sucess && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Long expire(String key, int seconds) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
Long retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
retVal = cluster.expire(key, seconds);
|
||||
sucess = true;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
|
||||
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
|
||||
if (tries == NUM_RETRIES) {
|
||||
throw ex;
|
||||
}
|
||||
waitforFailover();
|
||||
}
|
||||
} while (!sucess && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public byte[] get(String key) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
byte[] retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
retVal = cluster.get(key.getBytes());
|
||||
sucess = true;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
|
||||
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
|
||||
if (tries == NUM_RETRIES) {
|
||||
throw ex;
|
||||
}
|
||||
waitforFailover();
|
||||
}
|
||||
} while (!sucess && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Long delete(String key) {
|
||||
int tries = 0;
|
||||
boolean sucess = false;
|
||||
Long retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
retVal = cluster.del(key);
|
||||
sucess = true;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
|
||||
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
|
||||
if (tries == NUM_RETRIES) {
|
||||
throw ex;
|
||||
}
|
||||
waitforFailover();
|
||||
}
|
||||
} while (!sucess && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* To wait for handling redis fail-over
|
||||
*/
|
||||
private void waitforFailover() {
|
||||
try {
|
||||
Thread.sleep(4000);
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package tomcat.request.session.data.cache.impl;
|
||||
|
||||
/**
|
||||
* Tomcat clustering with Redis data-cache implementation.
|
||||
*
|
||||
* Redis data-cache constants.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 2.0
|
||||
*/
|
||||
interface RedisConstants {
|
||||
|
||||
// redis properties file name
|
||||
String PROPERTIES_FILE = "redis-data-cache.properties";
|
||||
|
||||
// redis properties
|
||||
String HOSTS = "redis.hosts";
|
||||
String CLUSTER_ENABLED = "redis.cluster.enabled";
|
||||
|
||||
String MAX_ACTIVE = "redis.max.active";
|
||||
String TEST_ONBORROW = "redis.test.onBorrow";
|
||||
String TEST_ONRETURN = "redis.test.onReturn";
|
||||
String MAX_IDLE = "redis.max.idle";
|
||||
String MIN_IDLE = "redis.min.idle";
|
||||
String TEST_WHILEIDLE = "redis.test.whileIdle";
|
||||
String TEST_NUMPEREVICTION = "redis.test.numPerEviction";
|
||||
String TIME_BETWEENEVICTION = "redis.time.betweenEviction";
|
||||
|
||||
String PASSWORD = "redis.password";
|
||||
String DATABASE = "redis.database";
|
||||
String TIMEOUT = "redis.timeout";
|
||||
|
||||
// redis property default values
|
||||
String DEFAULT_MAX_ACTIVE_VALUE = "10";
|
||||
String DEFAULT_TEST_ONBORROW_VALUE = "true";
|
||||
String DEFAULT_TEST_ONRETURN_VALUE = "true";
|
||||
String DEFAULT_MAX_IDLE_VALUE = "5";
|
||||
String DEFAULT_MIN_IDLE_VALUE = "1";
|
||||
String DEFAULT_TEST_WHILEIDLE_VALUE = "true";
|
||||
String DEFAULT_TEST_NUMPEREVICTION_VALUE = "10";
|
||||
String DEFAULT_TIME_BETWEENEVICTION_VALUE = "60000";
|
||||
String DEFAULT_CLUSTER_ENABLED = "false";
|
||||
|
||||
String CONN_FAILED_RETRY_MSG = "Jedis connection failed, retrying...";
|
||||
}
|
|
@ -1,226 +0,0 @@
|
|||
package tomcat.request.session.data.cache.impl;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import redis.clients.jedis.HostAndPort;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.Protocol;
|
||||
import tomcat.request.session.SessionConstants;
|
||||
import tomcat.request.session.data.cache.DataCache;
|
||||
|
||||
/**
|
||||
* Tomcat clustering with Redis data-cache implementation.
|
||||
*
|
||||
* Redis data-cache implementation to store/retrieve session objects.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 2.0
|
||||
*/
|
||||
public class RedisDataCache implements DataCache {
|
||||
|
||||
private static DataCache dataCache;
|
||||
|
||||
private Log log = LogFactory.getLog(RedisDataCache.class);
|
||||
|
||||
public RedisDataCache() {
|
||||
initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public byte[] set(String key, byte[] value) {
|
||||
return dataCache.set(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Long setnx(String key, byte[] value) {
|
||||
return dataCache.setnx(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Long expire(String key, int seconds) {
|
||||
return dataCache.expire(key, seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public byte[] get(String key) {
|
||||
return (key != null) ? dataCache.get(key) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Long delete(String key) {
|
||||
return dataCache.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* To parse data-cache key
|
||||
*/
|
||||
public static String parseDataCacheKey(String key) {
|
||||
return key.replaceAll("\\s", "_");
|
||||
}
|
||||
|
||||
/**
|
||||
* To initialize the data-cache
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void initialize() {
|
||||
if (dataCache != null) {
|
||||
return;
|
||||
}
|
||||
Properties properties = loadProperties();
|
||||
|
||||
boolean clusterEnabled = Boolean.valueOf(properties
|
||||
.getProperty(RedisConstants.CLUSTER_ENABLED, RedisConstants.DEFAULT_CLUSTER_ENABLED));
|
||||
|
||||
String hosts = properties.getProperty(RedisConstants.HOSTS,
|
||||
Protocol.DEFAULT_HOST.concat(":").concat(String.valueOf(Protocol.DEFAULT_PORT)));
|
||||
Collection<? extends Serializable> nodes = getJedisNodes(hosts, clusterEnabled);
|
||||
|
||||
String password = properties.getProperty(RedisConstants.PASSWORD);
|
||||
password = (password != null && !password.isEmpty()) ? password : null;
|
||||
|
||||
int database = Integer.parseInt(
|
||||
properties.getProperty(RedisConstants.DATABASE, String.valueOf(Protocol.DEFAULT_DATABASE)));
|
||||
|
||||
int timeout = Integer.parseInt(
|
||||
properties.getProperty(RedisConstants.TIMEOUT, String.valueOf(Protocol.DEFAULT_TIMEOUT)));
|
||||
timeout = (timeout < Protocol.DEFAULT_TIMEOUT) ? Protocol.DEFAULT_TIMEOUT : timeout;
|
||||
|
||||
if (clusterEnabled) {
|
||||
dataCache = new RedisClusterCacheUtil((Set<HostAndPort>) nodes, password, timeout,
|
||||
getPoolConfig(properties));
|
||||
} else {
|
||||
dataCache = new RedisCacheUtil(((List<String>) nodes).get(0),
|
||||
Integer.parseInt(((List<String>) nodes).get(1)), password, database, timeout,
|
||||
getPoolConfig(properties));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To get jedis pool configuration
|
||||
*/
|
||||
private JedisPoolConfig getPoolConfig(Properties properties) {
|
||||
JedisPoolConfig poolConfig = new JedisPoolConfig();
|
||||
int maxActive = Integer.parseInt(
|
||||
properties.getProperty(RedisConstants.MAX_ACTIVE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE));
|
||||
poolConfig.setMaxTotal(maxActive);
|
||||
|
||||
boolean testOnBorrow = Boolean.parseBoolean(properties
|
||||
.getProperty(RedisConstants.TEST_ONBORROW, RedisConstants.DEFAULT_TEST_ONBORROW_VALUE));
|
||||
poolConfig.setTestOnBorrow(testOnBorrow);
|
||||
|
||||
boolean testOnReturn = Boolean.parseBoolean(properties
|
||||
.getProperty(RedisConstants.TEST_ONRETURN, RedisConstants.DEFAULT_TEST_ONRETURN_VALUE));
|
||||
poolConfig.setTestOnReturn(testOnReturn);
|
||||
|
||||
int maxIdle = Integer.parseInt(
|
||||
properties.getProperty(RedisConstants.MAX_ACTIVE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE));
|
||||
poolConfig.setMaxIdle(maxIdle);
|
||||
|
||||
int minIdle = Integer.parseInt(
|
||||
properties.getProperty(RedisConstants.MIN_IDLE, RedisConstants.DEFAULT_MIN_IDLE_VALUE));
|
||||
poolConfig.setMinIdle(minIdle);
|
||||
|
||||
boolean testWhileIdle = Boolean.parseBoolean(properties
|
||||
.getProperty(RedisConstants.TEST_WHILEIDLE, RedisConstants.DEFAULT_TEST_WHILEIDLE_VALUE));
|
||||
poolConfig.setTestWhileIdle(testWhileIdle);
|
||||
|
||||
int testNumPerEviction = Integer.parseInt(properties
|
||||
.getProperty(RedisConstants.TEST_NUMPEREVICTION,
|
||||
RedisConstants.DEFAULT_TEST_NUMPEREVICTION_VALUE));
|
||||
poolConfig.setNumTestsPerEvictionRun(testNumPerEviction);
|
||||
|
||||
long timeBetweenEviction = Long.parseLong(properties
|
||||
.getProperty(RedisConstants.TIME_BETWEENEVICTION,
|
||||
RedisConstants.DEFAULT_TIME_BETWEENEVICTION_VALUE));
|
||||
poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEviction);
|
||||
return poolConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* To get jedis nodes
|
||||
*/
|
||||
private Collection<? extends Serializable> getJedisNodes(String hosts, boolean clusterEnabled) {
|
||||
hosts = hosts.replaceAll("\\s", "");
|
||||
String[] hostPorts = hosts.split(",");
|
||||
|
||||
List<String> node = null;
|
||||
Set<HostAndPort> nodes = null;
|
||||
|
||||
for (String hostPort : hostPorts) {
|
||||
String[] hostPortArr = hostPort.split(":");
|
||||
|
||||
if (clusterEnabled) {
|
||||
nodes = (nodes == null) ? new HashSet<HostAndPort>() : nodes;
|
||||
nodes.add(new HostAndPort(hostPortArr[0], Integer.valueOf(hostPortArr[1])));
|
||||
} else {
|
||||
int port = Integer.valueOf(hostPortArr[1]);
|
||||
if (!hostPortArr[0].isEmpty() && port > 0) {
|
||||
node = (node == null) ? new ArrayList<String>() : node;
|
||||
node.add(hostPortArr[0]);
|
||||
node.add(String.valueOf(port));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return clusterEnabled ? nodes : node;
|
||||
}
|
||||
|
||||
/**
|
||||
* To load data-cache properties
|
||||
*/
|
||||
private Properties loadProperties() {
|
||||
Properties properties = new Properties();
|
||||
try {
|
||||
String filePath = System.getProperty(SessionConstants.CATALINA_BASE).concat(File.separator)
|
||||
.concat(SessionConstants.CONF).concat(File.separator)
|
||||
.concat(RedisConstants.PROPERTIES_FILE);
|
||||
|
||||
InputStream resourceStream = null;
|
||||
try {
|
||||
resourceStream = (filePath != null && !filePath.isEmpty() && new File(filePath).exists())
|
||||
? new FileInputStream(filePath) : null;
|
||||
|
||||
if (resourceStream == null) {
|
||||
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
resourceStream = loader.getResourceAsStream(RedisConstants.PROPERTIES_FILE);
|
||||
}
|
||||
properties.load(resourceStream);
|
||||
} finally {
|
||||
if (resourceStream != null) {
|
||||
resourceStream.close();
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
log.error("Error while loading task scheduler properties", ex);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
}
|
132
src/main/java/tomcat/request/session/data/cache/impl/redis/AbstractRedisUtil.java
vendored
Normal file
132
src/main/java/tomcat/request/session/data/cache/impl/redis/AbstractRedisUtil.java
vendored
Normal file
|
@ -0,0 +1,132 @@
|
|||
package tomcat.request.session.data.cache.impl.redis;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
import redis.clients.util.Pool;
|
||||
import tomcat.request.session.data.cache.DataCache;
|
||||
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
public abstract class AbstractRedisUtil implements DataCache {
|
||||
|
||||
private static final int NUM_RETRIES = 3;
|
||||
private static final Log LOGGER = LogFactory.getLog(RedisUtil.class);
|
||||
|
||||
private final Pool<Jedis> pool;
|
||||
private final long failiureWaitTime;
|
||||
|
||||
AbstractRedisUtil(Pool<Jedis> pool, long failiureWaitTime) {
|
||||
this.pool = pool;
|
||||
this.failiureWaitTime = failiureWaitTime;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public byte[] set(String key, byte[] value) {
|
||||
int tries = 0;
|
||||
boolean retry = true;
|
||||
String retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try (Jedis jedis = this.pool.getResource()) {
|
||||
retVal = jedis.set(key.getBytes(), value);
|
||||
retry = false;
|
||||
} catch (JedisConnectionException ex) {
|
||||
handleException(tries, ex);
|
||||
}
|
||||
} while (retry && tries <= NUM_RETRIES);
|
||||
return (retVal != null) ? retVal.getBytes() : null;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Long setnx(String key, byte[] value) {
|
||||
int tries = 0;
|
||||
boolean retry = true;
|
||||
Long retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try (Jedis jedis = this.pool.getResource()) {
|
||||
retVal = jedis.setnx(key.getBytes(), value);
|
||||
retry = false;
|
||||
} catch (JedisConnectionException ex) {
|
||||
handleException(tries, ex);
|
||||
}
|
||||
} while (retry && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Long expire(String key, int seconds) {
|
||||
int tries = 0;
|
||||
boolean retry = true;
|
||||
Long retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try (Jedis jedis = this.pool.getResource()) {
|
||||
retVal = jedis.expire(key, seconds);
|
||||
retry = false;
|
||||
} catch (JedisConnectionException ex) {
|
||||
handleException(tries, ex);
|
||||
}
|
||||
} while (retry && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public byte[] get(String key) {
|
||||
int tries = 0;
|
||||
boolean retry = true;
|
||||
byte[] retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try (Jedis jedis = this.pool.getResource()) {
|
||||
retVal = jedis.get(key.getBytes());
|
||||
retry = false;
|
||||
} catch (JedisConnectionException ex) {
|
||||
handleException(tries, ex);
|
||||
}
|
||||
} while (retry && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Long delete(String key) {
|
||||
int tries = 0;
|
||||
boolean retry = true;
|
||||
Long retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try (Jedis jedis = this.pool.getResource()) {
|
||||
retVal = jedis.del(key);
|
||||
retry = false;
|
||||
} catch (JedisConnectionException ex) {
|
||||
handleException(tries, ex);
|
||||
}
|
||||
} while (retry && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* To handle jedis exception.
|
||||
*
|
||||
* @param tries - exception occurred in tries.
|
||||
* @param ex - jedis exception.
|
||||
*/
|
||||
void handleException(int tries, RuntimeException ex) {
|
||||
LOGGER.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
|
||||
if (tries == NUM_RETRIES) {
|
||||
throw ex;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(this.failiureWaitTime);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
package tomcat.request.session.data.cache.impl.redis;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import redis.clients.jedis.HostAndPort;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.Protocol;
|
||||
import tomcat.request.session.SessionConstants;
|
||||
import tomcat.request.session.data.cache.DataCache;
|
||||
import tomcat.request.session.data.cache.impl.redis.RedisConstants.RedisConfigType;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
public class RedisCache implements DataCache {
|
||||
|
||||
private static DataCache dataCache;
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(RedisCache.class);
|
||||
|
||||
public RedisCache() {
|
||||
initialize();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public byte[] set(String key, byte[] value) {
|
||||
return dataCache.set(key, value);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Long setnx(String key, byte[] value) {
|
||||
return dataCache.setnx(key, value);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Long expire(String key, int seconds) {
|
||||
return dataCache.expire(key, seconds);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public byte[] get(String key) {
|
||||
return dataCache.get(key);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Long delete(String key) {
|
||||
return dataCache.delete(key);
|
||||
}
|
||||
|
||||
/** To initialize the redis data cache. */
|
||||
private void initialize() {
|
||||
if (dataCache != null) {
|
||||
return;
|
||||
}
|
||||
Properties properties = getProperties();
|
||||
|
||||
RedisConfigType configType;
|
||||
if (Boolean.valueOf(properties.getProperty(RedisConstants.CLUSTER_ENABLED))) {
|
||||
configType = RedisConfigType.CLUSTER;
|
||||
} else if (Boolean.valueOf(properties.getProperty(RedisConstants.SENTINEL_ENABLED))) {
|
||||
configType = RedisConfigType.SENTINEL;
|
||||
} else {
|
||||
configType = RedisConfigType.DEFAULT;
|
||||
}
|
||||
|
||||
String hosts = properties.getProperty(RedisConstants.HOSTS, Protocol.DEFAULT_HOST.concat(":").concat(String.valueOf(Protocol.DEFAULT_PORT)));
|
||||
Collection<?> nodes = getJedisNodes(hosts, configType);
|
||||
|
||||
String password = properties.getProperty(RedisConstants.PASSWORD);
|
||||
password = (password != null && !password.isEmpty()) ? password : null;
|
||||
|
||||
int database = Integer.parseInt(properties.getProperty(RedisConstants.DATABASE, String.valueOf(Protocol.DEFAULT_DATABASE)));
|
||||
|
||||
int timeout = Integer.parseInt(properties.getProperty(RedisConstants.TIMEOUT, String.valueOf(Protocol.DEFAULT_TIMEOUT)));
|
||||
timeout = (timeout < Protocol.DEFAULT_TIMEOUT) ? Protocol.DEFAULT_TIMEOUT : timeout;
|
||||
|
||||
JedisPoolConfig poolConfig = getPoolConfig(properties);
|
||||
switch (configType) {
|
||||
case CLUSTER:
|
||||
dataCache = new RedisClusterUtil((Set<HostAndPort>) nodes, password, timeout, poolConfig);
|
||||
break;
|
||||
case SENTINEL:
|
||||
String masterName = String.valueOf(properties.getProperty(RedisConstants.SENTINEL_MASTER, RedisConstants.DEFAULT_SENTINEL_MASTER));
|
||||
dataCache = new RedisSentinelUtil((Set<String>) nodes, masterName, password, database, timeout, poolConfig);
|
||||
break;
|
||||
default:
|
||||
dataCache = new RedisUtil(((List<String>) nodes).get(0), Integer.parseInt(((List<String>) nodes).get(1)), password, database, timeout, poolConfig);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To get redis pool config.
|
||||
*
|
||||
* @param properties - Redis data cache properties.
|
||||
* @return - Returns the redis pool config.
|
||||
*/
|
||||
private JedisPoolConfig getPoolConfig(Properties properties) {
|
||||
JedisPoolConfig poolConfig = new JedisPoolConfig();
|
||||
int maxActive = Integer.parseInt(properties.getProperty(RedisConstants.MAX_ACTIVE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE));
|
||||
poolConfig.setMaxTotal(maxActive);
|
||||
|
||||
boolean testOnBorrow = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_ONBORROW, RedisConstants.DEFAULT_TEST_ONBORROW_VALUE));
|
||||
poolConfig.setTestOnBorrow(testOnBorrow);
|
||||
|
||||
boolean testOnReturn = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_ONRETURN, RedisConstants.DEFAULT_TEST_ONRETURN_VALUE));
|
||||
poolConfig.setTestOnReturn(testOnReturn);
|
||||
|
||||
int maxIdle = Integer.parseInt(properties.getProperty(RedisConstants.MAX_ACTIVE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE));
|
||||
poolConfig.setMaxIdle(maxIdle);
|
||||
|
||||
int minIdle = Integer.parseInt(properties.getProperty(RedisConstants.MIN_IDLE, RedisConstants.DEFAULT_MIN_IDLE_VALUE));
|
||||
poolConfig.setMinIdle(minIdle);
|
||||
|
||||
boolean testWhileIdle = Boolean.parseBoolean(properties.getProperty(RedisConstants.TEST_WHILEIDLE, RedisConstants.DEFAULT_TEST_WHILEIDLE_VALUE));
|
||||
poolConfig.setTestWhileIdle(testWhileIdle);
|
||||
|
||||
int testNumPerEviction = Integer.parseInt(properties.getProperty(RedisConstants.TEST_NUMPEREVICTION, RedisConstants.DEFAULT_TEST_NUMPEREVICTION_VALUE));
|
||||
poolConfig.setNumTestsPerEvictionRun(testNumPerEviction);
|
||||
|
||||
long timeBetweenEviction = Long.parseLong(properties.getProperty(RedisConstants.TIME_BETWEENEVICTION, RedisConstants.DEFAULT_TIME_BETWEENEVICTION_VALUE));
|
||||
poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEviction);
|
||||
return poolConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* To get redis data cache nodes.
|
||||
*
|
||||
* @param hosts - redis server hosts.
|
||||
* @param configType - redis data config type.
|
||||
* @return - Returns the redis nodes.
|
||||
*/
|
||||
private Collection<?> getJedisNodes(String hosts, RedisConfigType configType) {
|
||||
hosts = hosts.replaceAll("\\s", "");
|
||||
String[] hostPorts = hosts.split(",");
|
||||
|
||||
Set<Object> nodes = null;
|
||||
|
||||
for (String hostPort : hostPorts) {
|
||||
String[] hostPortArr = hostPort.split(":");
|
||||
|
||||
switch (configType) {
|
||||
case CLUSTER:
|
||||
nodes = (nodes == null) ? new HashSet<>() : nodes;
|
||||
nodes.add(new HostAndPort(hostPortArr[0], Integer.valueOf(hostPortArr[1])));
|
||||
break;
|
||||
case SENTINEL:
|
||||
nodes = (nodes == null) ? new HashSet<>() : nodes;
|
||||
nodes.add(new HostAndPort(hostPortArr[0], Integer.valueOf(hostPortArr[1])).toString());
|
||||
break;
|
||||
default:
|
||||
int port = Integer.valueOf(hostPortArr[1]);
|
||||
if (!hostPortArr[0].isEmpty() && port > 0) {
|
||||
List<String> node = new ArrayList<>();
|
||||
node.add(hostPortArr[0]);
|
||||
node.add(String.valueOf(port));
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/** To get redis data cache properties. */
|
||||
private Properties getProperties() {
|
||||
Properties properties = new Properties();
|
||||
try {
|
||||
String filePath = System.getProperty(SessionConstants.CATALINA_BASE).concat(File.separator)
|
||||
.concat(SessionConstants.CONF).concat(File.separator)
|
||||
.concat(RedisConstants.PROPERTIES_FILE);
|
||||
|
||||
InputStream resourceStream = null;
|
||||
try {
|
||||
resourceStream = (!filePath.isEmpty() && new File(filePath).exists()) ? new FileInputStream(filePath) : null;
|
||||
|
||||
if (resourceStream == null) {
|
||||
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
resourceStream = loader.getResourceAsStream(RedisConstants.PROPERTIES_FILE);
|
||||
}
|
||||
properties.load(resourceStream);
|
||||
} finally {
|
||||
if (resourceStream != null) {
|
||||
resourceStream.close();
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
LOGGER.error("Error while loading task scheduler properties", ex);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
}
|
120
src/main/java/tomcat/request/session/data/cache/impl/redis/RedisClusterUtil.java
vendored
Normal file
120
src/main/java/tomcat/request/session/data/cache/impl/redis/RedisClusterUtil.java
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
package tomcat.request.session.data.cache.impl.redis;
|
||||
|
||||
import redis.clients.jedis.HostAndPort;
|
||||
import redis.clients.jedis.JedisCluster;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.Protocol;
|
||||
import redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException;
|
||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
public class RedisClusterUtil extends AbstractRedisUtil {
|
||||
|
||||
private final JedisCluster cluster;
|
||||
|
||||
private static final int NUM_RETRIES = 30;
|
||||
private static final int DEFAULT_MAX_REDIRECTIONS = 5;
|
||||
private static final long FAILIURE_WAIT_TIME = 4000L;
|
||||
|
||||
RedisClusterUtil(Set<HostAndPort> nodes,
|
||||
String password,
|
||||
int timeout,
|
||||
JedisPoolConfig poolConfig) {
|
||||
super(null, FAILIURE_WAIT_TIME);
|
||||
this.cluster = new JedisCluster(nodes, timeout, Protocol.DEFAULT_TIMEOUT, DEFAULT_MAX_REDIRECTIONS,
|
||||
password, poolConfig);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public byte[] set(String key, byte[] value) {
|
||||
int tries = 0;
|
||||
boolean retry = true;
|
||||
String retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
retVal = this.cluster.set(key.getBytes(), value);
|
||||
retry = false;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
|
||||
handleException(tries, ex);
|
||||
}
|
||||
} while (retry && tries <= NUM_RETRIES);
|
||||
return (retVal != null) ? retVal.getBytes() : null;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Long setnx(String key, byte[] value) {
|
||||
int tries = 0;
|
||||
boolean retry = true;
|
||||
Long retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
retVal = this.cluster.setnx(key.getBytes(), value);
|
||||
retry = false;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
|
||||
handleException(tries, ex);
|
||||
}
|
||||
} while (retry && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Long expire(String key, int seconds) {
|
||||
int tries = 0;
|
||||
boolean retry = true;
|
||||
Long retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
retVal = this.cluster.expire(key, seconds);
|
||||
retry = false;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
|
||||
handleException(tries, ex);
|
||||
}
|
||||
} while (retry && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public byte[] get(String key) {
|
||||
int tries = 0;
|
||||
boolean retry = true;
|
||||
byte[] retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
retVal = this.cluster.get(key.getBytes());
|
||||
retry = false;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
|
||||
handleException(tries, ex);
|
||||
}
|
||||
} while (retry && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Long delete(String key) {
|
||||
int tries = 0;
|
||||
boolean retry = true;
|
||||
Long retVal = null;
|
||||
do {
|
||||
tries++;
|
||||
try {
|
||||
retVal = this.cluster.del(key);
|
||||
retry = false;
|
||||
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
|
||||
handleException(tries, ex);
|
||||
}
|
||||
} while (retry && tries <= NUM_RETRIES);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
48
src/main/java/tomcat/request/session/data/cache/impl/redis/RedisConstants.java
vendored
Normal file
48
src/main/java/tomcat/request/session/data/cache/impl/redis/RedisConstants.java
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
package tomcat.request.session.data.cache.impl.redis;
|
||||
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
interface RedisConstants {
|
||||
|
||||
// redis properties file name
|
||||
String PROPERTIES_FILE = "redis-data-cache.properties";
|
||||
|
||||
// redis properties
|
||||
String HOSTS = "redis.hosts";
|
||||
String CLUSTER_ENABLED = "redis.cluster.enabled";
|
||||
String SENTINEL_ENABLED = "redis.sentinel.enabled";
|
||||
|
||||
String MAX_ACTIVE = "redis.max.active";
|
||||
String TEST_ONBORROW = "redis.test.onBorrow";
|
||||
String TEST_ONRETURN = "redis.test.onReturn";
|
||||
String MAX_IDLE = "redis.max.idle";
|
||||
String MIN_IDLE = "redis.min.idle";
|
||||
String TEST_WHILEIDLE = "redis.test.whileIdle";
|
||||
String TEST_NUMPEREVICTION = "redis.test.numPerEviction";
|
||||
String TIME_BETWEENEVICTION = "redis.time.betweenEviction";
|
||||
|
||||
String PASSWORD = "redis.password";
|
||||
String DATABASE = "redis.database";
|
||||
String TIMEOUT = "redis.timeout";
|
||||
|
||||
String SENTINEL_MASTER = "redis.sentinel.master";
|
||||
String DEFAULT_SENTINEL_MASTER = "mymaster";
|
||||
|
||||
// redis property default values
|
||||
String DEFAULT_MAX_ACTIVE_VALUE = "10";
|
||||
String DEFAULT_TEST_ONBORROW_VALUE = "true";
|
||||
String DEFAULT_TEST_ONRETURN_VALUE = "true";
|
||||
String DEFAULT_MAX_IDLE_VALUE = "5";
|
||||
String DEFAULT_MIN_IDLE_VALUE = "1";
|
||||
String DEFAULT_TEST_WHILEIDLE_VALUE = "true";
|
||||
String DEFAULT_TEST_NUMPEREVICTION_VALUE = "10";
|
||||
String DEFAULT_TIME_BETWEENEVICTION_VALUE = "60000";
|
||||
|
||||
String CONN_FAILED_RETRY_MSG = "Jedis connection failed, retrying...";
|
||||
|
||||
enum RedisConfigType {
|
||||
DEFAULT,
|
||||
SENTINEL,
|
||||
CLUSTER
|
||||
}
|
||||
|
||||
}
|
22
src/main/java/tomcat/request/session/data/cache/impl/redis/RedisSentinelUtil.java
vendored
Normal file
22
src/main/java/tomcat/request/session/data/cache/impl/redis/RedisSentinelUtil.java
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
package tomcat.request.session.data.cache.impl.redis;
|
||||
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
import redis.clients.jedis.JedisSentinelPool;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
class RedisSentinelUtil extends AbstractRedisUtil {
|
||||
|
||||
private static final long FAILIURE_WAIT_TIME = 2000L;
|
||||
|
||||
RedisSentinelUtil(Set<String> nodes,
|
||||
String masterName,
|
||||
String password,
|
||||
int database,
|
||||
int timeout,
|
||||
JedisPoolConfig poolConfig) {
|
||||
super(new JedisSentinelPool(masterName, nodes, poolConfig, timeout, password, database), FAILIURE_WAIT_TIME);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package tomcat.request.session.data.cache.impl.redis;
|
||||
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
class RedisUtil extends AbstractRedisUtil {
|
||||
|
||||
private static final long FAILIURE_WAIT_TIME = 2000L;
|
||||
|
||||
RedisUtil(String host,
|
||||
int port,
|
||||
String password,
|
||||
int database,
|
||||
int timeout,
|
||||
JedisPoolConfig poolConfig) {
|
||||
super(new JedisPool(poolConfig, host, port, timeout, password, database), FAILIURE_WAIT_TIME);
|
||||
}
|
||||
|
||||
}
|
|
@ -8,29 +8,17 @@ import org.apache.catalina.connector.Request;
|
|||
import org.apache.catalina.connector.Response;
|
||||
import org.apache.catalina.valves.ValveBase;
|
||||
|
||||
/**
|
||||
* Tomcat clustering with Redis data-cache implementation.
|
||||
*
|
||||
* Valve that implements per-request session persistence. It is intended to be used with non-sticky
|
||||
* load-balancers.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 2.0
|
||||
*/
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
public class SessionHandlerValve extends ValveBase {
|
||||
|
||||
private SessionManager manager;
|
||||
|
||||
/**
|
||||
* To set session manager
|
||||
*/
|
||||
/** To set session manager */
|
||||
public void setSessionManager(SessionManager manager) {
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void invoke(Request request, Response response) throws IOException, ServletException {
|
||||
try {
|
||||
|
@ -39,4 +27,5 @@ public class SessionHandlerValve extends ValveBase {
|
|||
manager.afterRequest(request);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +1,5 @@
|
|||
package tomcat.request.session.redis;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Lifecycle;
|
||||
import org.apache.catalina.LifecycleException;
|
||||
|
@ -16,54 +10,34 @@ import org.apache.catalina.connector.Request;
|
|||
import org.apache.catalina.session.ManagerBase;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import tomcat.request.session.SerializationUtil;
|
||||
import tomcat.request.session.Session;
|
||||
import tomcat.request.session.SessionConstants;
|
||||
import tomcat.request.session.SessionConstants.SessionPolicy;
|
||||
import tomcat.request.session.SessionContext;
|
||||
import tomcat.request.session.SessionMetadata;
|
||||
import tomcat.request.session.data.cache.DataCache;
|
||||
import tomcat.request.session.data.cache.impl.RedisDataCache;
|
||||
import tomcat.request.session.data.cache.impl.redis.RedisCache;
|
||||
|
||||
/**
|
||||
* Tomcat clustering with Redis data-cache implementation.
|
||||
*
|
||||
* Manager that implements per-request session persistence. It is intended to be used with
|
||||
* non-sticky load-balancers.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 2.0
|
||||
*/
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
|
||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||
public class SessionManager extends ManagerBase implements Lifecycle {
|
||||
|
||||
private DataCache dataCache;
|
||||
|
||||
protected SerializationUtil serializer;
|
||||
|
||||
protected ThreadLocal<SessionContext> sessionContext = new ThreadLocal<>();
|
||||
|
||||
protected SessionHandlerValve handlerValve;
|
||||
|
||||
protected ThreadLocal<SessionContext> sessionContext = new ThreadLocal<>();
|
||||
protected Set<SessionPolicy> sessionPolicy = EnumSet.of(SessionPolicy.DEFAULT);
|
||||
|
||||
private Log log = LogFactory.getLog(SessionManager.class);
|
||||
private static final Log LOGGER = LogFactory.getLog(SessionManager.class);
|
||||
|
||||
enum SessionPolicy {
|
||||
DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST;
|
||||
|
||||
static SessionPolicy fromName(String name) {
|
||||
for (SessionPolicy policy : SessionPolicy.values()) {
|
||||
if (policy.name().equalsIgnoreCase(name)) {
|
||||
return policy;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid session policy [" + name + "]");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To get session persist policies
|
||||
*/
|
||||
/** To get session persist policies */
|
||||
public String getSessionPersistPolicies() {
|
||||
String policyStr = null;
|
||||
for (SessionPolicy policy : this.sessionPolicy) {
|
||||
|
@ -72,9 +46,7 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
|||
return policyStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* To set session persist policies
|
||||
*/
|
||||
/** To set session persist policies */
|
||||
public void setSessionPersistPolicies(String policyStr) {
|
||||
Set<SessionPolicy> policySet = EnumSet.of(SessionPolicy.DEFAULT);
|
||||
String[] policyArray = policyStr.split(",");
|
||||
|
@ -93,33 +65,25 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
|||
return this.sessionPolicy.contains(SessionPolicy.ALWAYS_SAVE_AFTER_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void addLifecycleListener(LifecycleListener listener) {
|
||||
super.addLifecycleListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public LifecycleListener[] findLifecycleListeners() {
|
||||
return super.findLifecycleListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void removeLifecycleListener(LifecycleListener listener) {
|
||||
super.removeLifecycleListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
protected synchronized void startInternal() throws LifecycleException {
|
||||
super.startInternal();
|
||||
|
@ -142,22 +106,18 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
|||
|
||||
initialize();
|
||||
|
||||
log.info("The sessions will expire after " + (getSessionTimeout(null)) + " seconds.");
|
||||
LOGGER.info("The sessions will expire after " + (getSessionTimeout(null)) + " seconds.");
|
||||
context.setDistributable(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
protected synchronized void stopInternal() throws LifecycleException {
|
||||
super.setState(LifecycleState.STOPPING);
|
||||
super.stopInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Session createSession(String sessionId) {
|
||||
if (sessionId != null) {
|
||||
|
@ -184,7 +144,7 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
|||
try {
|
||||
save(session, true);
|
||||
} catch (Exception ex) {
|
||||
log.error("Error occurred while creating session..", ex);
|
||||
LOGGER.error("Error occurred while creating session..", ex);
|
||||
setValues(null, null);
|
||||
session = null;
|
||||
}
|
||||
|
@ -192,25 +152,19 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
|||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Session createEmptySession() {
|
||||
return new Session(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void add(org.apache.catalina.Session session) {
|
||||
save(session, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public Session findSession(String sessionId) throws IOException {
|
||||
Session session = null;
|
||||
|
@ -223,8 +177,6 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
|||
boolean isPersisted = false;
|
||||
SessionMetadata metadata = null;
|
||||
if (data == null) {
|
||||
session = null;
|
||||
metadata = null;
|
||||
sessionId = null;
|
||||
isPersisted = false;
|
||||
} else {
|
||||
|
@ -246,7 +198,7 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
|||
session = newSession;
|
||||
isPersisted = true;
|
||||
} catch (Exception ex) {
|
||||
log.error("Error occurred while de-serializing the session object..", ex);
|
||||
LOGGER.error("Error occurred while de-serializing the session object..", ex);
|
||||
}
|
||||
}
|
||||
setValues(sessionId, session, isPersisted, metadata);
|
||||
|
@ -254,66 +206,51 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
|||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void remove(org.apache.catalina.Session session) {
|
||||
remove(session, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void remove(org.apache.catalina.Session session, boolean update) {
|
||||
this.dataCache.expire(session.getId(), 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void load() throws ClassNotFoundException, IOException {
|
||||
// Auto-generated method stub
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public void unload() throws IOException {
|
||||
// Auto-generated method stub
|
||||
}
|
||||
|
||||
/**
|
||||
* To initialize the session manager
|
||||
*/
|
||||
/** To initialize the session manager. */
|
||||
private void initialize() {
|
||||
try {
|
||||
this.dataCache = new RedisDataCache();
|
||||
this.dataCache = new RedisCache();
|
||||
|
||||
this.serializer = new SerializationUtil();
|
||||
Context context = getContextIns();
|
||||
ClassLoader loader =
|
||||
(context != null && context.getLoader() != null) ? context.getLoader().getClassLoader()
|
||||
: null;
|
||||
ClassLoader loader = (context != null && context.getLoader() != null) ? context.getLoader().getClassLoader() : null;
|
||||
this.serializer.setClassLoader(loader);
|
||||
} catch (Exception ex) {
|
||||
log.error("Error occurred while initializing the session manager..", ex);
|
||||
LOGGER.error("Error occurred while initializing the session manager..", ex);
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To save session object to data-cache
|
||||
*/
|
||||
/** To save session object to data cache. */
|
||||
public void save(org.apache.catalina.Session session, boolean forceSave) {
|
||||
try {
|
||||
Boolean isPersisted;
|
||||
Session newSession = (Session) session;
|
||||
byte[] hash =
|
||||
(this.sessionContext.get() != null && this.sessionContext.get().getMetadata() != null)
|
||||
byte[] hash = (this.sessionContext.get() != null && this.sessionContext.get().getMetadata() != null)
|
||||
? this.sessionContext.get().getMetadata().getAttributesHash() : null;
|
||||
byte[] currentHash = serializer.getSessionAttributesHashCode(newSession);
|
||||
|
||||
|
@ -334,16 +271,14 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
|||
|
||||
int timeout = getSessionTimeout(newSession);
|
||||
this.dataCache.expire(newSession.getId(), timeout);
|
||||
log.trace("Session [" + newSession.getId() + "] expire in [" + timeout + "] seconds.");
|
||||
LOGGER.trace("Session [" + newSession.getId() + "] expire in [" + timeout + "] seconds.");
|
||||
|
||||
} catch (IOException ex) {
|
||||
log.error("Error occurred while saving the session object in data cache..", ex);
|
||||
LOGGER.error("Error occurred while saving the session object in data cache..", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To process post request process
|
||||
*/
|
||||
/** To process post request process. */
|
||||
public void afterRequest(Request request) {
|
||||
Session session = null;
|
||||
try {
|
||||
|
@ -354,24 +289,26 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
|||
} else {
|
||||
remove(session);
|
||||
}
|
||||
log.trace(
|
||||
LOGGER.trace(
|
||||
"Session object " + (session.isValid() ? "saved: " : "removed: ") + session.getId());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.error("Error occurred while processing post request process..", ex);
|
||||
LOGGER.error("Error occurred while processing post request process..", ex);
|
||||
} finally {
|
||||
this.sessionContext.remove();
|
||||
log.trace(
|
||||
LOGGER.trace(
|
||||
"Session removed from ThreadLocal:" + ((session != null) ? session.getIdInternal() : ""));
|
||||
}
|
||||
}
|
||||
|
||||
/** To get session timeout. */
|
||||
private int getSessionTimeout(Session session) {
|
||||
int timeout = getContextIns().getSessionTimeout() * 60;
|
||||
int sessionTimeout = (session == null) ? 0 : session.getMaxInactiveInterval();
|
||||
return (sessionTimeout < timeout) ? ((timeout < 1800) ? 1800 : timeout) : sessionTimeout;
|
||||
}
|
||||
|
||||
/** To set values to session context. */
|
||||
private void setValues(String sessionId, Session session) {
|
||||
if (this.sessionContext.get() == null) {
|
||||
this.sessionContext.set(new SessionContext());
|
||||
|
@ -380,6 +317,7 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
|||
this.sessionContext.get().setSession(session);
|
||||
}
|
||||
|
||||
/** To set values to session context. */
|
||||
private void setValues(boolean isPersisted, SessionMetadata metadata) {
|
||||
if (this.sessionContext.get() == null) {
|
||||
this.sessionContext.set(new SessionContext());
|
||||
|
@ -388,12 +326,14 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
|||
this.sessionContext.get().setPersisted(isPersisted);
|
||||
}
|
||||
|
||||
/** To set values to session context. */
|
||||
private void setValues(String sessionId, Session session, boolean isPersisted,
|
||||
SessionMetadata metadata) {
|
||||
setValues(sessionId, session);
|
||||
setValues(isPersisted, metadata);
|
||||
}
|
||||
|
||||
/** To get catalina context instance. */
|
||||
private Context getContextIns() {
|
||||
try {
|
||||
Method method = this.getClass().getSuperclass().getDeclaredMethod("getContext");
|
||||
|
@ -403,9 +343,10 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
|||
Method method = this.getClass().getSuperclass().getDeclaredMethod("getContainer");
|
||||
return (Context) method.invoke(this);
|
||||
} catch (Exception ex2) {
|
||||
log.error("Error in getContext", ex2);
|
||||
// skip
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Error occurred while creating container instance");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
/**
|
||||
* Tomcat clustering with Redis data-cache implementation.
|
||||
*
|
||||
* Tomcat clustering with Redis is the plugable one. It uses to store session objects to Redis data cache.
|
||||
*
|
||||
* @author Ranjith Manickam
|
||||
* @since 2.0
|
||||
* author: Ranjith Manickam @ 12 Jul' 2018
|
||||
*/
|
||||
|
||||
Supports:
|
||||
* Apache Tomcat 7
|
||||
* Apache Tomcat 8
|
||||
- Apache Tomcat 7
|
||||
- Apache Tomcat 8
|
||||
- Apache Tomcat 9
|
||||
|
||||
Pre-requisite:
|
||||
1. jedis.jar
|
||||
|
@ -20,13 +18,13 @@ more details.. https://github.com/ran-jit/tomcat-cluster-redis-session-manager/w
|
|||
|
||||
Steps to be done,
|
||||
1. Move the downloaded jars to tomcat/lib directory
|
||||
* tomcat/lib/
|
||||
- tomcat/lib/
|
||||
|
||||
2. Add tomcat system property "catalina.base"
|
||||
* catalina.base="TOMCAT_LOCATION"
|
||||
- catalina.base="TOMCAT_LOCATION"
|
||||
|
||||
3. Extract downloaded package (tomcat-cluster-redis-session-manager.zip) to configure Redis credentials in redis-data-cache.properties file and move the file to tomcat/conf directory
|
||||
* tomcat/conf/redis-data-cache.properties
|
||||
- tomcat/conf/redis-data-cache.properties
|
||||
|
||||
4. Add the below two lines in tomcat/conf/context.xml
|
||||
<Valve className="tomcat.request.session.redis.SessionHandlerValve" />
|
||||
|
@ -38,5 +36,5 @@ Steps to be done,
|
|||
<session-config>
|
||||
|
||||
Note:
|
||||
* All your session attribute values must implement java.io.Serializable.
|
||||
* This supports, both redis stand-alone and multiple node cluster based on the redis-data-cache.properties configuration.
|
||||
- All your session attribute values must implement java.io.Serializable.
|
||||
- Supports redis default, sentinel and cluster based on the redis-data-cache.properties configuration.
|
||||
|
|
|
@ -3,14 +3,19 @@
|
|||
#- redis hosts ex: 127.0.0.1:6379, 127.0.0.2:6379, 127.0.0.2:6380, ....
|
||||
redis.hosts=127.0.0.1:6379
|
||||
|
||||
#- redis password (for stand-alone mode)
|
||||
#- redis password (used for default and sentinel mode)
|
||||
#redis.password=
|
||||
|
||||
#- set true to enable redis cluster mode
|
||||
redis.cluster.enabled=false
|
||||
|
||||
#- redis database (default 0)
|
||||
#- set true to enable redis sentinel mode
|
||||
redis.sentinel.enabled=false
|
||||
# redis sentinel master name (default value: mymaster)
|
||||
redis.sentinel.master=mymaster
|
||||
|
||||
#- redis database (default value: 0)
|
||||
#redis.database=0
|
||||
|
||||
#- redis connection timeout (default 2000)
|
||||
#- redis connection timeout (default value: 2000)
|
||||
#redis.timeout=2000
|
Loading…
Reference in New Issue