Code cleanup

pull/15/head
Ranjith Manickam 2018-07-09 18:12:27 +05:30
parent 6a65b99e24
commit 6aff279fbd
12 changed files with 1109 additions and 1112 deletions

View File

@ -11,14 +11,11 @@ import java.security.MessageDigest;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.apache.catalina.util.CustomObjectInputStream; import org.apache.catalina.util.CustomObjectInputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/** /**
* Tomcat clustering with Redis data-cache implementation. * Tomcat clustering with Redis data-cache implementation.
* *
* Session serialization utility. * Session serialization utility.
* *
* @author Ranjith Manickam * @author Ranjith Manickam
@ -26,87 +23,69 @@ import org.apache.commons.logging.LogFactory;
*/ */
public class SerializationUtil { public class SerializationUtil {
private ClassLoader loader; private ClassLoader loader;
private Log log = LogFactory.getLog(SerializationUtil.class); /**
* To set class loader
*/
public void setClassLoader(ClassLoader loader) {
this.loader = loader;
}
/** /**
* To set class loader * To get session attributes hash code
* */
* @param loader public byte[] getSessionAttributesHashCode(Session session) throws IOException {
*/ byte[] serialized;
public void setClassLoader(ClassLoader loader) { Map<String, Object> attributes = new HashMap<>();
this.loader = loader;
}
/** for (Enumeration<String> enumerator = session.getAttributeNames();
* To get session attributes hash code enumerator.hasMoreElements(); ) {
* String key = enumerator.nextElement();
* @param session attributes.put(key, session.getAttribute(key));
* @return }
* @throws IOException
*/
public byte[] getSessionAttributesHashCode(Session session) throws IOException {
byte[] serialized = null;
Map<String, Object> attributes = new HashMap<String, Object>();
for (Enumeration<String> enumerator = session.getAttributeNames(); enumerator.hasMoreElements();) { try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
String key = enumerator.nextElement(); ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(bos))) {
attributes.put(key, session.getAttribute(key)); oos.writeUnshared(attributes);
} oos.flush();
serialized = bos.toByteArray();
}
try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); MessageDigest digester;
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(bos));) { try {
oos.writeUnshared(attributes); digester = MessageDigest.getInstance("MD5");
oos.flush(); } catch (Exception ex) {
serialized = bos.toByteArray(); throw new RuntimeException("Unable to get MessageDigest instance for MD5", ex);
} }
return digester.digest(serialized);
}
MessageDigest digester = null; /**
try { * To serialize session object
digester = MessageDigest.getInstance("MD5"); */
} catch (Exception ex) { public byte[] serializeSessionData(Session session, SessionMetadata metadata) throws IOException {
log.error("Unable to get MessageDigest instance for MD5", ex); byte[] serialized;
} try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
return digester.digest(serialized); ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(bos))) {
} oos.writeObject(metadata);
session.writeObjectData(oos);
oos.flush();
serialized = bos.toByteArray();
}
return serialized;
}
/** /**
* To serialize session object * To de-serialize session object
* */
* @param session public void deserializeSessionData(byte[] data, Session session, SessionMetadata metadata)
* @param metadata throws IOException, ClassNotFoundException {
* @return try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data));
* @throws IOException ObjectInputStream ois = new CustomObjectInputStream(bis, this.loader)) {
*/ SessionMetadata serializedMetadata = (SessionMetadata) ois.readObject();
public byte[] serializeSessionData(Session session, SessionMetadata metadata) throws IOException { metadata.copyFieldsFrom(serializedMetadata);
byte[] serialized = null; session.readObjectData(ois);
try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); }
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(bos));) { }
oos.writeObject(metadata);
session.writeObjectData(oos);
oos.flush();
serialized = bos.toByteArray();
}
return serialized;
}
/**
* To de-serialize session object
*
* @param data
* @param session
* @param metadata
* @throws IOException
* @throws ClassNotFoundException
*/
public void deserializeSessionData(byte[] data, Session session, SessionMetadata metadata)
throws IOException, ClassNotFoundException {
try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data));
ObjectInputStream ois = new CustomObjectInputStream(bis, this.loader);) {
SessionMetadata serializedMetadata = (SessionMetadata) ois.readObject();
metadata.copyFieldsFrom(serializedMetadata);
session.readObjectData(ois);
}
}
} }

View File

@ -15,7 +15,7 @@ import tomcat.request.session.redis.SessionManager;
/** /**
* Tomcat clustering with Redis data-cache implementation. * Tomcat clustering with Redis data-cache implementation.
* *
* This class is uses to store and retrieve the HTTP request session objects. * This class is uses to store and retrieve the HTTP request session objects.
* *
* @author Ranjith Manickam * @author Ranjith Manickam
@ -23,117 +23,137 @@ import tomcat.request.session.redis.SessionManager;
*/ */
public class Session extends StandardSession { public class Session extends StandardSession {
private static final long serialVersionUID = -6056744304016869278L; private static final long serialVersionUID = -6056744304016869278L;
protected Boolean dirty; protected Boolean dirty;
protected Map<String, Object> changedAttributes; protected Map<String, Object> changedAttributes;
protected static Boolean manualDirtyTrackingSupportEnabled = false; protected static Boolean manualDirtyTrackingSupportEnabled = false;
protected static String manualDirtyTrackingAttributeKey = "__changed__"; protected static String manualDirtyTrackingAttributeKey = "__changed__";
public Session(Manager manager) { public Session(Manager manager) {
super(manager); super(manager);
resetDirtyTracking(); resetDirtyTracking();
} }
public void resetDirtyTracking() { public void resetDirtyTracking() {
this.changedAttributes = new HashMap<>(); this.changedAttributes = new HashMap<>();
this.dirty = false; this.dirty = false;
} }
public static void setManualDirtyTrackingSupportEnabled(boolean enabled) { public static void setManualDirtyTrackingSupportEnabled(boolean enabled) {
manualDirtyTrackingSupportEnabled = enabled; manualDirtyTrackingSupportEnabled = enabled;
} }
public static void setManualDirtyTrackingAttributeKey(String key) { public static void setManualDirtyTrackingAttributeKey(String key) {
manualDirtyTrackingAttributeKey = key; manualDirtyTrackingAttributeKey = key;
} }
public Boolean isDirty() { public Boolean isDirty() {
return this.dirty || !this.changedAttributes.isEmpty(); return this.dirty || !this.changedAttributes.isEmpty();
} }
public Map<String, Object> getChangedAttributes() { public Map<String, Object> getChangedAttributes() {
return this.changedAttributes; return this.changedAttributes;
} }
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void setId(String id) { */
this.id = id; @Override
} public void setId(String id) {
this.id = id;
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void setAttribute(String key, Object value) { */
if (manualDirtyTrackingSupportEnabled && manualDirtyTrackingAttributeKey.equals(key)) { @Override
this.dirty = true; public void setAttribute(String key, Object value) {
return; if (manualDirtyTrackingSupportEnabled && manualDirtyTrackingAttributeKey.equals(key)) {
} this.dirty = true;
return;
}
Object oldValue = getAttribute(key); Object oldValue = getAttribute(key);
super.setAttribute(key, value); super.setAttribute(key, value);
if ((value != null || oldValue != null) if ((value != null || oldValue != null)
&& (value == null && oldValue != null || oldValue == null && value != null && (value == null && oldValue != null || oldValue == null && value != null
|| !value.getClass().isInstance(oldValue) || !value.equals(oldValue))) { || !value.getClass().isInstance(oldValue) || !value.equals(oldValue))) {
if (this.manager instanceof SessionManager && ((SessionManager) this.manager).getSaveOnChange()) { if (this.manager instanceof SessionManager && ((SessionManager) this.manager)
((SessionManager) this.manager).save(this, true); .getSaveOnChange()) {
} else { ((SessionManager) this.manager).save(this, true);
this.changedAttributes.put(key, value); } else {
} this.changedAttributes.put(key, value);
} }
} }
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Object getAttribute(String name) { */
return super.getAttribute(name); @Override
} public Object getAttribute(String name) {
return super.getAttribute(name);
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Enumeration<String> getAttributeNames() { */
return super.getAttributeNames(); @Override
} public Enumeration<String> getAttributeNames() {
return super.getAttributeNames();
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void removeAttribute(String name) { */
super.removeAttribute(name); @Override
if (this.manager instanceof SessionManager && ((SessionManager) this.manager).getSaveOnChange()) { public void removeAttribute(String name) {
((SessionManager) this.manager).save(this, true); super.removeAttribute(name);
} else { if (this.manager instanceof SessionManager && ((SessionManager) this.manager)
this.dirty = true; .getSaveOnChange()) {
} ((SessionManager) this.manager).save(this, true);
} } else {
this.dirty = true;
}
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void setPrincipal(Principal principal) { */
super.setPrincipal(principal); @Override
this.dirty = true; public void setPrincipal(Principal principal) {
} super.setPrincipal(principal);
this.dirty = true;
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void writeObjectData(ObjectOutputStream out) throws IOException { */
super.writeObjectData(out); @Override
out.writeLong(this.getCreationTime()); public void writeObjectData(ObjectOutputStream out) throws IOException {
} super.writeObjectData(out);
out.writeLong(this.getCreationTime());
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void readObjectData(ObjectInputStream in) throws IOException, ClassNotFoundException { */
super.readObjectData(in); @Override
this.setCreationTime(in.readLong()); public void readObjectData(ObjectInputStream in) throws IOException, ClassNotFoundException {
} super.readObjectData(in);
this.setCreationTime(in.readLong());
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void invalidate() { */
super.invalidate(); @Override
} public void invalidate() {
super.invalidate();
}
} }

View File

@ -2,17 +2,17 @@ package tomcat.request.session;
/** /**
* Tomcat clustering with Redis data-cache implementation. * Tomcat clustering with Redis data-cache implementation.
* *
* Session constants. * Session constants.
* *
* @author Ranjith Manickam * @author Ranjith Manickam
* @since 2.0 * @since 2.0
*/ */
public class SessionConstants { public interface SessionConstants {
public static final byte[] NULL_SESSION = "null".getBytes(); byte[] NULL_SESSION = "null".getBytes();
public static final String CATALINA_BASE = "catalina.base"; String CATALINA_BASE = "catalina.base";
public static final String CONF = "conf"; String CONF = "conf";
} }

View File

@ -2,98 +2,84 @@ package tomcat.request.session;
/** /**
* Tomcat clustering with Redis data-cache implementation. * Tomcat clustering with Redis data-cache implementation.
* *
* Session context uses to manage current session data. * Session context uses to manage current session data.
* *
* @author Ranjith Manickam * @author Ranjith Manickam
* @since 2.0 * @since 2.0
*/ */
public class SessionContext { public class SessionContext {
private String id; private String id;
private Session session; private Session session;
private boolean persisted; private boolean persisted;
private SessionMetadata metadata; private SessionMetadata metadata;
/** /**
* To get session id * To get session id
* */
* @return public String getId() {
*/ return id;
public String getId() { }
return id;
}
/** /**
* To set session id * To set session id
* */
* @param id public void setId(String id) {
*/ this.id = id;
public void setId(String id) { }
this.id = id;
}
/** /**
* To get session * To get session
* */
* @return public Session getSession() {
*/ return session;
public Session getSession() { }
return session;
}
/** /**
* To set session * To set session
* */
* @param session public void setSession(Session session) {
*/ this.session = session;
public void setSession(Session session) { }
this.session = session;
}
/** /**
* To check session is persisted * To check session is persisted
* */
* @return public boolean isPersisted() {
*/ return persisted;
public boolean isPersisted() { }
return persisted;
}
/** /**
* To set session persisted * To set session persisted
* */
* @param persisted public void setPersisted(boolean persisted) {
*/ this.persisted = persisted;
public void setPersisted(boolean persisted) { }
this.persisted = persisted;
}
/** /**
* To get session meta-data * To get session meta-data
* */
* @return public SessionMetadata getMetadata() {
*/ return metadata;
public SessionMetadata getMetadata() { }
return metadata;
}
/** /**
* To set session meta-data * To set session meta-data
* */
* @param metadata public void setMetadata(SessionMetadata metadata) {
*/ this.metadata = metadata;
public void setMetadata(SessionMetadata metadata) { }
this.metadata = metadata;
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public String toString() { */
return "SessionContext [id=" + id + "]"; @Override
} public String toString() {
return "SessionContext [id=" + id + "]";
}
} }

View File

@ -7,72 +7,58 @@ import java.io.Serializable;
/** /**
* Tomcat clustering with Redis data-cache implementation. * Tomcat clustering with Redis data-cache implementation.
* *
* This class is uses to store and retrieve the HTTP request session object * This class is uses to store and retrieve the HTTP request session object meta-data.
* meta-data.
* *
* @author Ranjith Manickam * @author Ranjith Manickam
* @since 2.0 * @since 2.0
*/ */
public class SessionMetadata implements Serializable { public class SessionMetadata implements Serializable {
private static final long serialVersionUID = 124438185184833546L; private static final long serialVersionUID = 124438185184833546L;
private byte[] attributesHash; private byte[] attributesHash;
public SessionMetadata() { public SessionMetadata() {
this.attributesHash = new byte[0]; this.attributesHash = new byte[0];
} }
/** /**
* To get session meta-data hash * To get session meta-data hash
* */
* @return public byte[] getAttributesHash() {
*/ return this.attributesHash;
public byte[] getAttributesHash() { }
return this.attributesHash;
}
/** /**
* To set session meta-data hash * To set session meta-data hash
* */
* @param attributesHash public void setAttributesHash(byte[] attributesHash) {
*/ this.attributesHash = attributesHash;
public void setAttributesHash(byte[] attributesHash) { }
this.attributesHash = attributesHash;
}
/** /**
* To copy session meta-data * To copy session meta-data
* */
* @param metadata public void copyFieldsFrom(SessionMetadata metadata) {
*/ this.setAttributesHash(metadata.getAttributesHash());
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
* */
* @param out private void writeObject(ObjectOutputStream out) throws IOException {
* @throws IOException out.writeInt(this.attributesHash.length);
*/ out.write(this.attributesHash);
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
* */
* @param in private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
* @throws IOException int hashLength = in.readInt();
* @throws ClassNotFoundException byte[] attributesHash = new byte[hashLength];
*/ in.read(attributesHash, 0, hashLength);
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { this.attributesHash = attributesHash;
int hashLength = in.readInt(); }
byte[] attributesHash = new byte[hashLength];
in.read(attributesHash, 0, hashLength);
this.attributesHash = attributesHash;
}
} }

View File

@ -2,7 +2,7 @@ package tomcat.request.session.data.cache;
/** /**
* Tomcat clustering with Redis data-cache implementation. * Tomcat clustering with Redis data-cache implementation.
* *
* API for Data cache. * API for Data cache.
* *
* @author Ranjith Manickam * @author Ranjith Manickam
@ -10,49 +10,31 @@ package tomcat.request.session.data.cache;
*/ */
public interface DataCache { public interface DataCache {
/** /**
* To set value in data-cache * To set value in data-cache
* */
* @param key byte[] set(String key, byte[] value);
* @param value
* @return
*/
byte[] set(String key, byte[] value);
/** /**
* To set value if key not exists in data-cache * To set value if key not exists in data-cache
* *
* Returns If key exists = 0 else 1 * Returns If key exists = 0 else 1
* */
* @param key Long setnx(String key, byte[] value);
* @param value
* @return
*/
Long setnx(String key, byte[] value);
/** /**
* To expire the value based on key in data-cache * To expire the value based on key in data-cache
* */
* @param key Long expire(String key, int seconds);
* @param seconds
* @return
*/
Long expire(String key, int seconds);
/** /**
* To get the value based on key from data-cache * To get the value based on key from data-cache
* */
* @param key byte[] get(String key);
* @return
*/
byte[] get(String key);
/** /**
* To delete the value based on key from data-cache * To delete the value based on key from data-cache
* */
* @param key Long delete(String key);
* @return
*/
Long delete(String key);
} }

View File

@ -11,132 +11,147 @@ import tomcat.request.session.data.cache.DataCache;
/** /**
* Tomcat clustering with Redis data-cache implementation. * Tomcat clustering with Redis data-cache implementation.
* *
* Redis stand-alone mode data-cache implementation. * Redis stand-alone mode data-cache implementation.
* *
* @author Ranjith Manickam * @author Ranjith Manickam
* @since 2.0 * @since 2.0
*/ */
class RedisCacheUtil implements DataCache { class RedisCacheUtil implements DataCache {
private JedisPool pool; private JedisPool pool;
private static final int NUM_RETRIES = 3; private static final int NUM_RETRIES = 3;
private Log log = LogFactory.getLog(RedisCacheUtil.class); private Log log = LogFactory.getLog(RedisCacheUtil.class);
public RedisCacheUtil(String host, int port, String password, int database, int timeout, RedisCacheUtil(String host, int port, String password, int database, int timeout,
JedisPoolConfig poolConfig) { JedisPoolConfig poolConfig) {
pool = new JedisPool(poolConfig, host, port, timeout, password, database); pool = new JedisPool(poolConfig, host, port, timeout, password, database);
} }
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public byte[] set(String key, byte[] value) { */
int tries = 0; @Override
boolean sucess = false; public byte[] set(String key, byte[] value) {
String retVal = null; int tries = 0;
do { boolean sucess = false;
tries++; String retVal = null;
try { do {
Jedis jedis = pool.getResource(); tries++;
retVal = jedis.set(key.getBytes(), value); try {
jedis.close(); Jedis jedis = pool.getResource();
sucess = true; retVal = jedis.set(key.getBytes(), value);
} catch (JedisConnectionException ex) { jedis.close();
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries); sucess = true;
if (tries == NUM_RETRIES) } catch (JedisConnectionException ex) {
throw ex; log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
} if (tries == NUM_RETRIES) {
} while (!sucess && tries <= NUM_RETRIES); throw ex;
return (retVal != null) ? retVal.getBytes() : null; }
} }
} while (!sucess && tries <= NUM_RETRIES);
return (retVal != null) ? retVal.getBytes() : null;
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Long setnx(String key, byte[] value) { */
int tries = 0; @Override
boolean sucess = false; public Long setnx(String key, byte[] value) {
Long retVal = null; int tries = 0;
do { boolean sucess = false;
tries++; Long retVal = null;
try { do {
Jedis jedis = pool.getResource(); tries++;
retVal = jedis.setnx(key.getBytes(), value); try {
jedis.close(); Jedis jedis = pool.getResource();
sucess = true; retVal = jedis.setnx(key.getBytes(), value);
} catch (JedisConnectionException ex) { jedis.close();
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries); sucess = true;
if (tries == NUM_RETRIES) } catch (JedisConnectionException ex) {
throw ex; log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
} if (tries == NUM_RETRIES) {
} while (!sucess && tries <= NUM_RETRIES); throw ex;
return retVal; }
} }
} while (!sucess && tries <= NUM_RETRIES);
return retVal;
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Long expire(String key, int seconds) { */
int tries = 0; @Override
boolean sucess = false; public Long expire(String key, int seconds) {
Long retVal = null; int tries = 0;
do { boolean sucess = false;
tries++; Long retVal = null;
try { do {
Jedis jedis = pool.getResource(); tries++;
retVal = jedis.expire(key, seconds); try {
jedis.close(); Jedis jedis = pool.getResource();
sucess = true; retVal = jedis.expire(key, seconds);
} catch (JedisConnectionException ex) { jedis.close();
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries); sucess = true;
if (tries == NUM_RETRIES) } catch (JedisConnectionException ex) {
throw ex; log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
} if (tries == NUM_RETRIES) {
} while (!sucess && tries <= NUM_RETRIES); throw ex;
return retVal; }
} }
} while (!sucess && tries <= NUM_RETRIES);
return retVal;
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public byte[] get(String key) { */
int tries = 0; @Override
boolean sucess = false; public byte[] get(String key) {
byte[] retVal = null; int tries = 0;
do { boolean sucess = false;
tries++; byte[] retVal = null;
try { do {
Jedis jedis = pool.getResource(); tries++;
retVal = jedis.get(key.getBytes()); try {
jedis.close(); Jedis jedis = pool.getResource();
sucess = true; retVal = jedis.get(key.getBytes());
} catch (JedisConnectionException ex) { jedis.close();
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries); sucess = true;
if (tries == NUM_RETRIES) } catch (JedisConnectionException ex) {
throw ex; log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
} if (tries == NUM_RETRIES) {
} while (!sucess && tries <= NUM_RETRIES); throw ex;
return retVal; }
} }
} while (!sucess && tries <= NUM_RETRIES);
return retVal;
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Long delete(String key) { */
int tries = 0; @Override
boolean sucess = false; public Long delete(String key) {
Long retVal = null; int tries = 0;
do { boolean sucess = false;
tries++; Long retVal = null;
try { do {
Jedis jedis = pool.getResource(); tries++;
retVal = jedis.del(key); try {
jedis.close(); Jedis jedis = pool.getResource();
sucess = true; retVal = jedis.del(key);
} catch (JedisConnectionException ex) { jedis.close();
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries); sucess = true;
if (tries == NUM_RETRIES) } catch (JedisConnectionException ex) {
throw ex; log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
} if (tries == NUM_RETRIES) {
} while (!sucess && tries <= NUM_RETRIES); throw ex;
return retVal; }
} }
} while (!sucess && tries <= NUM_RETRIES);
return retVal;
}
} }

View File

@ -15,143 +15,155 @@ import tomcat.request.session.data.cache.DataCache;
/** /**
* Tomcat clustering with Redis data-cache implementation. * Tomcat clustering with Redis data-cache implementation.
* *
* Redis multiple node cluster data-cache implementation. * Redis multiple node cluster data-cache implementation.
* *
* @author Ranjith Manickam * @author Ranjith Manickam
* @since 2.0 * @since 2.0
*/ */
class RedisClusterCacheUtil implements DataCache { class RedisClusterCacheUtil implements DataCache {
private JedisCluster cluster; private JedisCluster cluster;
private static final int NUM_RETRIES = 30; private static final int NUM_RETRIES = 30;
private static final int DEFAULT_MAX_REDIRECTIONS = 5; private static final int DEFAULT_MAX_REDIRECTIONS = 5;
private Log log = LogFactory.getLog(RedisClusterCacheUtil.class); private Log log = LogFactory.getLog(RedisClusterCacheUtil.class);
public RedisClusterCacheUtil(Set<HostAndPort> nodes, String password, int timeout, JedisPoolConfig poolConfig) { RedisClusterCacheUtil(Set<HostAndPort> nodes, String password, int timeout,
cluster = new JedisCluster(nodes, timeout, Protocol.DEFAULT_TIMEOUT, DEFAULT_MAX_REDIRECTIONS, password, poolConfig); JedisPoolConfig poolConfig) {
} cluster = new JedisCluster(nodes, timeout, Protocol.DEFAULT_TIMEOUT, DEFAULT_MAX_REDIRECTIONS,
password, poolConfig);
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public byte[] set(String key, byte[] value) { */
int tries = 0; @Override
boolean sucess = false; public byte[] set(String key, byte[] value) {
String retVal = null; int tries = 0;
do { boolean sucess = false;
tries++; String retVal = null;
try { do {
retVal = cluster.set(key.getBytes(), value); tries++;
sucess = true; try {
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) { retVal = cluster.set(key.getBytes(), value);
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries); sucess = true;
if (tries == NUM_RETRIES) { } catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
throw ex; log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
} if (tries == NUM_RETRIES) {
waitforFailover(); throw ex;
} }
} while (!sucess && tries <= NUM_RETRIES); waitforFailover();
return (retVal != null) ? retVal.getBytes() : null; }
} } while (!sucess && tries <= NUM_RETRIES);
return (retVal != null) ? retVal.getBytes() : null;
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Long setnx(String key, byte[] value) { */
int tries = 0; @Override
boolean sucess = false; public Long setnx(String key, byte[] value) {
Long retVal = null; int tries = 0;
do { boolean sucess = false;
tries++; Long retVal = null;
try { do {
retVal = cluster.setnx(key.getBytes(), value); tries++;
sucess = true; try {
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) { retVal = cluster.setnx(key.getBytes(), value);
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries); sucess = true;
if (tries == NUM_RETRIES) { } catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
throw ex; log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
} if (tries == NUM_RETRIES) {
waitforFailover(); throw ex;
} }
} while (!sucess && tries <= NUM_RETRIES); waitforFailover();
return retVal; }
} } while (!sucess && tries <= NUM_RETRIES);
return retVal;
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Long expire(String key, int seconds) { */
int tries = 0; @Override
boolean sucess = false; public Long expire(String key, int seconds) {
Long retVal = null; int tries = 0;
do { boolean sucess = false;
tries++; Long retVal = null;
try { do {
retVal = cluster.expire(key, seconds); tries++;
sucess = true; try {
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) { retVal = cluster.expire(key, seconds);
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries); sucess = true;
if (tries == NUM_RETRIES) { } catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
throw ex; log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
} if (tries == NUM_RETRIES) {
waitforFailover(); throw ex;
} }
} while (!sucess && tries <= NUM_RETRIES); waitforFailover();
return retVal; }
} } while (!sucess && tries <= NUM_RETRIES);
return retVal;
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public byte[] get(String key) { */
int tries = 0; @Override
boolean sucess = false; public byte[] get(String key) {
byte[] retVal = null; int tries = 0;
do { boolean sucess = false;
tries++; byte[] retVal = null;
try { do {
retVal = cluster.get(key.getBytes()); tries++;
sucess = true; try {
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) { retVal = cluster.get(key.getBytes());
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries); sucess = true;
if (tries == NUM_RETRIES) { } catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
throw ex; log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
} if (tries == NUM_RETRIES) {
waitforFailover(); throw ex;
} }
} while (!sucess && tries <= NUM_RETRIES); waitforFailover();
return retVal; }
} } while (!sucess && tries <= NUM_RETRIES);
return retVal;
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Long delete(String key) { */
int tries = 0; @Override
boolean sucess = false; public Long delete(String key) {
Long retVal = null; int tries = 0;
do { boolean sucess = false;
tries++; Long retVal = null;
try { do {
retVal = cluster.del(key); tries++;
sucess = true; try {
} catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) { retVal = cluster.del(key);
log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries); sucess = true;
if (tries == NUM_RETRIES) { } catch (JedisClusterMaxRedirectionsException | JedisConnectionException ex) {
throw ex; log.error(RedisConstants.CONN_FAILED_RETRY_MSG + tries);
} if (tries == NUM_RETRIES) {
waitforFailover(); throw ex;
} }
} while (!sucess && tries <= NUM_RETRIES); waitforFailover();
return retVal; }
} } while (!sucess && tries <= NUM_RETRIES);
return retVal;
}
/** /**
* To wait for handling redis fail-over * To wait for handling redis fail-over
*/ */
private void waitforFailover() { private void waitforFailover() {
try { try {
Thread.sleep(4000); Thread.sleep(4000);
} catch (InterruptedException ex) { } catch (InterruptedException ex) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }
} }

View File

@ -2,44 +2,44 @@ package tomcat.request.session.data.cache.impl;
/** /**
* Tomcat clustering with Redis data-cache implementation. * Tomcat clustering with Redis data-cache implementation.
* *
* Redis data-cache constants. * Redis data-cache constants.
* *
* @author Ranjith Manickam * @author Ranjith Manickam
* @since 2.0 * @since 2.0
*/ */
interface RedisConstants { interface RedisConstants {
// redis properties file name // redis properties file name
final String PROPERTIES_FILE = "redis-data-cache.properties"; String PROPERTIES_FILE = "redis-data-cache.properties";
// redis properties // redis properties
final String HOSTS = "redis.hosts"; String HOSTS = "redis.hosts";
final String CLUSTER_ENABLED = "redis.cluster.enabled"; String CLUSTER_ENABLED = "redis.cluster.enabled";
final String MAX_ACTIVE = "redis.max.active"; String MAX_ACTIVE = "redis.max.active";
final String TEST_ONBORROW = "redis.test.onBorrow"; String TEST_ONBORROW = "redis.test.onBorrow";
final String TEST_ONRETURN = "redis.test.onReturn"; String TEST_ONRETURN = "redis.test.onReturn";
final String MAX_IDLE = "redis.max.idle"; String MAX_IDLE = "redis.max.idle";
final String MIN_IDLE = "redis.min.idle"; String MIN_IDLE = "redis.min.idle";
final String TEST_WHILEIDLE = "redis.test.whileIdle"; String TEST_WHILEIDLE = "redis.test.whileIdle";
final String TEST_NUMPEREVICTION = "redis.test.numPerEviction"; String TEST_NUMPEREVICTION = "redis.test.numPerEviction";
final String TIME_BETWEENEVICTION = "redis.time.betweenEviction"; String TIME_BETWEENEVICTION = "redis.time.betweenEviction";
final String PASSWORD = "redis.password"; String PASSWORD = "redis.password";
final String DATABASE = "redis.database"; String DATABASE = "redis.database";
final String TIMEOUT = "redis.timeout"; String TIMEOUT = "redis.timeout";
// redis property default values // redis property default values
final String DEFAULT_MAX_ACTIVE_VALUE = "10"; String DEFAULT_MAX_ACTIVE_VALUE = "10";
final String DEFAULT_TEST_ONBORROW_VALUE = "true"; String DEFAULT_TEST_ONBORROW_VALUE = "true";
final String DEFAULT_TEST_ONRETURN_VALUE = "true"; String DEFAULT_TEST_ONRETURN_VALUE = "true";
final String DEFAULT_MAX_IDLE_VALUE = "5"; String DEFAULT_MAX_IDLE_VALUE = "5";
final String DEFAULT_MIN_IDLE_VALUE = "1"; String DEFAULT_MIN_IDLE_VALUE = "1";
final String DEFAULT_TEST_WHILEIDLE_VALUE = "true"; String DEFAULT_TEST_WHILEIDLE_VALUE = "true";
final String DEFAULT_TEST_NUMPEREVICTION_VALUE = "10"; String DEFAULT_TEST_NUMPEREVICTION_VALUE = "10";
final String DEFAULT_TIME_BETWEENEVICTION_VALUE = "60000"; String DEFAULT_TIME_BETWEENEVICTION_VALUE = "60000";
final String DEFAULT_CLUSTER_ENABLED = "false"; String DEFAULT_CLUSTER_ENABLED = "false";
final String CONN_FAILED_RETRY_MSG = "Jedis connection failed, retrying..."; String CONN_FAILED_RETRY_MSG = "Jedis connection failed, retrying...";
} }

View File

@ -23,191 +23,204 @@ import tomcat.request.session.data.cache.DataCache;
/** /**
* Tomcat clustering with Redis data-cache implementation. * Tomcat clustering with Redis data-cache implementation.
* *
* Redis data-cache implementation to store/retrieve session objects. * Redis data-cache implementation to store/retrieve session objects.
* *
* @author Ranjith Manickam * @author Ranjith Manickam
* @since 2.0 * @since 2.0
*/ */
public class RedisDataCache implements DataCache { public class RedisDataCache implements DataCache {
private static DataCache dataCache; private static DataCache dataCache;
private Log log = LogFactory.getLog(RedisDataCache.class); private Log log = LogFactory.getLog(RedisDataCache.class);
public RedisDataCache() { public RedisDataCache() {
initialize(); initialize();
} }
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public byte[] set(String key, byte[] value) { */
return dataCache.set(key, value); @Override
} public byte[] set(String key, byte[] value) {
return dataCache.set(key, value);
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Long setnx(String key, byte[] value) { */
return dataCache.setnx(key, value); @Override
} public Long setnx(String key, byte[] value) {
return dataCache.setnx(key, value);
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Long expire(String key, int seconds) { */
return dataCache.expire(key, seconds); @Override
} public Long expire(String key, int seconds) {
return dataCache.expire(key, seconds);
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public byte[] get(String key) { */
return (key != null) ? dataCache.get(key) : null; @Override
} public byte[] get(String key) {
return (key != null) ? dataCache.get(key) : null;
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Long delete(String key) { */
return dataCache.delete(key); @Override
} public Long delete(String key) {
return dataCache.delete(key);
}
/** /**
* To parse data-cache key * To parse data-cache key
* */
* @param key public static String parseDataCacheKey(String key) {
* @return return key.replaceAll("\\s", "_");
*/ }
public static String parseDataCacheKey(String key) {
return key.replaceAll("\\s", "_");
}
/** /**
* To initialize the data-cache * To initialize the data-cache
* */
* @param properties @SuppressWarnings("unchecked")
* @param filePath private void initialize() {
*/ if (dataCache != null) {
@SuppressWarnings("unchecked") return;
private void initialize() { }
if (dataCache != null) { Properties properties = loadProperties();
return;
}
Properties properties = loadProperties();
boolean clusterEnabled = Boolean.valueOf(properties.getProperty(RedisConstants.CLUSTER_ENABLED, RedisConstants.DEFAULT_CLUSTER_ENABLED)); 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))); String hosts = properties.getProperty(RedisConstants.HOSTS,
Collection<? extends Serializable> nodes = getJedisNodes(hosts, clusterEnabled); Protocol.DEFAULT_HOST.concat(":").concat(String.valueOf(Protocol.DEFAULT_PORT)));
Collection<? extends Serializable> nodes = getJedisNodes(hosts, clusterEnabled);
String password = properties.getProperty(RedisConstants.PASSWORD); String password = properties.getProperty(RedisConstants.PASSWORD);
password = (password != null && !password.isEmpty()) ? password : null; password = (password != null && !password.isEmpty()) ? password : null;
int database = Integer.parseInt(properties.getProperty(RedisConstants.DATABASE, String.valueOf(Protocol.DEFAULT_DATABASE))); 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))); int timeout = Integer.parseInt(
timeout = (timeout < Protocol.DEFAULT_TIMEOUT) ? Protocol.DEFAULT_TIMEOUT : timeout; properties.getProperty(RedisConstants.TIMEOUT, String.valueOf(Protocol.DEFAULT_TIMEOUT)));
timeout = (timeout < Protocol.DEFAULT_TIMEOUT) ? Protocol.DEFAULT_TIMEOUT : timeout;
if (clusterEnabled) { if (clusterEnabled) {
dataCache = new RedisClusterCacheUtil((Set<HostAndPort>) nodes, password, timeout, getPoolConfig(properties)); dataCache = new RedisClusterCacheUtil((Set<HostAndPort>) nodes, password, timeout,
} else { getPoolConfig(properties));
dataCache = new RedisCacheUtil(((List<String>) nodes).get(0), } else {
Integer.parseInt(((List<String>) nodes).get(1)), password, database, timeout, getPoolConfig(properties)); 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 * To get jedis pool configuration
* */
* @param properties private JedisPoolConfig getPoolConfig(Properties properties) {
* @return JedisPoolConfig poolConfig = new JedisPoolConfig();
*/ int maxActive = Integer.parseInt(
private JedisPoolConfig getPoolConfig(Properties properties) { properties.getProperty(RedisConstants.MAX_ACTIVE, RedisConstants.DEFAULT_MAX_ACTIVE_VALUE));
JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(maxActive);
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)); boolean testOnBorrow = Boolean.parseBoolean(properties
poolConfig.setTestOnBorrow(testOnBorrow); .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)); boolean testOnReturn = Boolean.parseBoolean(properties
poolConfig.setTestOnReturn(testOnReturn); .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)); int maxIdle = Integer.parseInt(
poolConfig.setMaxIdle(maxIdle); 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)); int minIdle = Integer.parseInt(
poolConfig.setMinIdle(minIdle); 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)); boolean testWhileIdle = Boolean.parseBoolean(properties
poolConfig.setTestWhileIdle(testWhileIdle); .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)); int testNumPerEviction = Integer.parseInt(properties
poolConfig.setNumTestsPerEvictionRun(testNumPerEviction); .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)); long timeBetweenEviction = Long.parseLong(properties
poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEviction); .getProperty(RedisConstants.TIME_BETWEENEVICTION,
return poolConfig; RedisConstants.DEFAULT_TIME_BETWEENEVICTION_VALUE));
} poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEviction);
return poolConfig;
}
/** /**
* To get jedis nodes * To get jedis nodes
* */
* @param hosts private Collection<? extends Serializable> getJedisNodes(String hosts, boolean clusterEnabled) {
* @param clusterEnabled hosts = hosts.replaceAll("\\s", "");
* @return String[] hostPorts = hosts.split(",");
*/
private Collection<? extends Serializable> getJedisNodes(String hosts, boolean clusterEnabled) {
hosts = hosts.replaceAll("\\s", "");
String[] hostPorts = hosts.split(",");
List<String> node = null; List<String> node = null;
Set<HostAndPort> nodes = null; Set<HostAndPort> nodes = null;
for (String hostPort : hostPorts) { for (String hostPort : hostPorts) {
String[] hostPortArr = hostPort.split(":"); String[] hostPortArr = hostPort.split(":");
if (clusterEnabled) { if (clusterEnabled) {
nodes = (nodes == null) ? new HashSet<HostAndPort>() : nodes; nodes = (nodes == null) ? new HashSet<HostAndPort>() : nodes;
nodes.add(new HostAndPort(hostPortArr[0], Integer.valueOf(hostPortArr[1]))); nodes.add(new HostAndPort(hostPortArr[0], Integer.valueOf(hostPortArr[1])));
} else { } else {
int port = Integer.valueOf(hostPortArr[1]); int port = Integer.valueOf(hostPortArr[1]);
if (!hostPortArr[0].isEmpty() && port > 0) { if (!hostPortArr[0].isEmpty() && port > 0) {
node = (node == null) ? new ArrayList<String>() : node; node = (node == null) ? new ArrayList<String>() : node;
node.add(hostPortArr[0]); node.add(hostPortArr[0]);
node.add(String.valueOf(port)); node.add(String.valueOf(port));
break; break;
} }
} }
} }
return clusterEnabled ? nodes : node; return clusterEnabled ? nodes : node;
} }
/** /**
* To load data-cache properties * To load data-cache properties
* */
* @param filePath private Properties loadProperties() {
* @return Properties properties = new Properties();
*/ try {
private Properties loadProperties() { String filePath = System.getProperty(SessionConstants.CATALINA_BASE).concat(File.separator)
Properties properties = new Properties(); .concat(SessionConstants.CONF).concat(File.separator)
try { .concat(RedisConstants.PROPERTIES_FILE);
String filePath = System.getProperty(SessionConstants.CATALINA_BASE).concat(File.separator)
.concat(SessionConstants.CONF).concat(File.separator).concat(RedisConstants.PROPERTIES_FILE);
InputStream resourceStream = null; InputStream resourceStream = null;
try { try {
resourceStream = (filePath != null && !filePath.isEmpty() && new File(filePath).exists()) resourceStream = (filePath != null && !filePath.isEmpty() && new File(filePath).exists())
? new FileInputStream(filePath) : null; ? new FileInputStream(filePath) : null;
if (resourceStream == null) { if (resourceStream == null) {
ClassLoader loader = Thread.currentThread().getContextClassLoader(); ClassLoader loader = Thread.currentThread().getContextClassLoader();
resourceStream = loader.getResourceAsStream(RedisConstants.PROPERTIES_FILE); resourceStream = loader.getResourceAsStream(RedisConstants.PROPERTIES_FILE);
} }
properties.load(resourceStream); properties.load(resourceStream);
} finally { } finally {
resourceStream.close(); if (resourceStream != null) {
} resourceStream.close();
} catch (IOException ex) { }
log.error("Error while loading task scheduler properties", ex); }
} } catch (IOException ex) {
return properties; log.error("Error while loading task scheduler properties", ex);
} }
return properties;
}
} }

View File

@ -10,33 +10,33 @@ import org.apache.catalina.valves.ValveBase;
/** /**
* Tomcat clustering with Redis data-cache implementation. * Tomcat clustering with Redis data-cache implementation.
* *
* Valve that implements per-request session persistence. It is intended to be * Valve that implements per-request session persistence. It is intended to be used with non-sticky
* used with non-sticky load-balancers. * load-balancers.
* *
* @author Ranjith Manickam * @author Ranjith Manickam
* @since 2.0 * @since 2.0
*/ */
public class SessionHandlerValve extends ValveBase { public class SessionHandlerValve extends ValveBase {
private SessionManager manager; private SessionManager manager;
/** /**
* To set session manager * To set session manager
* */
* @param manager public void setSessionManager(SessionManager manager) {
*/ this.manager = manager;
public void setSessionManager(SessionManager manager) { }
this.manager = manager;
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void invoke(Request request, Response response) throws IOException, ServletException { */
try { @Override
getNext().invoke(request, response); public void invoke(Request request, Response response) throws IOException, ServletException {
} finally { try {
manager.afterRequest(request); getNext().invoke(request, response);
} } finally {
} manager.afterRequest(request);
}
}
} }

View File

@ -27,381 +27,385 @@ import tomcat.request.session.data.cache.impl.RedisDataCache;
/** /**
* Tomcat clustering with Redis data-cache implementation. * Tomcat clustering with Redis data-cache implementation.
* *
* Manager that implements per-request session persistence. It is intended to be * Manager that implements per-request session persistence. It is intended to be used with
* used with non-sticky load-balancers. * non-sticky load-balancers.
* *
* @author Ranjith Manickam * @author Ranjith Manickam
* @since 2.0 * @since 2.0
*/ */
public class SessionManager extends ManagerBase implements Lifecycle { public class SessionManager extends ManagerBase implements Lifecycle {
private DataCache dataCache; private DataCache dataCache;
protected SerializationUtil serializer; protected SerializationUtil serializer;
protected ThreadLocal<SessionContext> sessionContext = new ThreadLocal<>(); protected ThreadLocal<SessionContext> sessionContext = new ThreadLocal<>();
protected SessionHandlerValve handlerValve; protected SessionHandlerValve handlerValve;
protected Set<SessionPolicy> sessionPolicy = EnumSet.of(SessionPolicy.DEFAULT); protected Set<SessionPolicy> sessionPolicy = EnumSet.of(SessionPolicy.DEFAULT);
private Log log = LogFactory.getLog(SessionManager.class); private Log log = LogFactory.getLog(SessionManager.class);
enum SessionPolicy { enum SessionPolicy {
DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST; DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST;
static SessionPolicy fromName(String name) { static SessionPolicy fromName(String name) {
for (SessionPolicy policy : SessionPolicy.values()) { for (SessionPolicy policy : SessionPolicy.values()) {
if (policy.name().equalsIgnoreCase(name)) { if (policy.name().equalsIgnoreCase(name)) {
return policy; return policy;
} }
} }
throw new IllegalArgumentException("Invalid session policy [" + name + "]"); throw new IllegalArgumentException("Invalid session policy [" + name + "]");
} }
} }
/** /**
* To get session persist policies * To get session persist policies
* */
* @return public String getSessionPersistPolicies() {
*/ String policyStr = null;
public String getSessionPersistPolicies() { for (SessionPolicy policy : this.sessionPolicy) {
String policyStr = null; policyStr = (policyStr == null) ? policy.name() : policyStr.concat(",").concat(policy.name());
for (SessionPolicy policy : this.sessionPolicy) { }
policyStr = (policyStr == null) ? policy.name() : policyStr.concat(",").concat(policy.name()); return policyStr;
} }
return policyStr;
}
/** /**
* To set session persist policies * To set session persist policies
* */
* @param policyStr public void setSessionPersistPolicies(String policyStr) {
*/ Set<SessionPolicy> policySet = EnumSet.of(SessionPolicy.DEFAULT);
public void setSessionPersistPolicies(String policyStr) { String[] policyArray = policyStr.split(",");
Set<SessionPolicy> policySet = EnumSet.of(SessionPolicy.DEFAULT);
String[] policyArray = policyStr.split(",");
for (String policy : policyArray) { for (String policy : policyArray) {
policySet.add(SessionPolicy.fromName(policy)); policySet.add(SessionPolicy.fromName(policy));
} }
this.sessionPolicy = policySet; this.sessionPolicy = policySet;
} }
/** public boolean getSaveOnChange() {
* @return return this.sessionPolicy.contains(SessionPolicy.SAVE_ON_CHANGE);
*/ }
public boolean getSaveOnChange() {
return this.sessionPolicy.contains(SessionPolicy.SAVE_ON_CHANGE);
}
/** public boolean getAlwaysSaveAfterRequest() {
* @return return this.sessionPolicy.contains(SessionPolicy.ALWAYS_SAVE_AFTER_REQUEST);
*/ }
public boolean getAlwaysSaveAfterRequest() {
return this.sessionPolicy.contains(SessionPolicy.ALWAYS_SAVE_AFTER_REQUEST);
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void addLifecycleListener(LifecycleListener listener) { */
super.addLifecycleListener(listener); @Override
} public void addLifecycleListener(LifecycleListener listener) {
super.addLifecycleListener(listener);
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public LifecycleListener[] findLifecycleListeners() { */
return super.findLifecycleListeners(); @Override
} public LifecycleListener[] findLifecycleListeners() {
return super.findLifecycleListeners();
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void removeLifecycleListener(LifecycleListener listener) { */
super.removeLifecycleListener(listener); @Override
} public void removeLifecycleListener(LifecycleListener listener) {
super.removeLifecycleListener(listener);
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
protected synchronized void startInternal() throws LifecycleException { */
super.startInternal(); @Override
super.setState(LifecycleState.STARTING); protected synchronized void startInternal() throws LifecycleException {
super.startInternal();
super.setState(LifecycleState.STARTING);
boolean initializedValve = false; boolean initializedValve = false;
Context context = getContextIns(); Context context = getContextIns();
for (Valve valve : context.getPipeline().getValves()) { for (Valve valve : context.getPipeline().getValves()) {
if (valve instanceof SessionHandlerValve) { if (valve instanceof SessionHandlerValve) {
this.handlerValve = (SessionHandlerValve) valve; this.handlerValve = (SessionHandlerValve) valve;
this.handlerValve.setSessionManager(this); this.handlerValve.setSessionManager(this);
initializedValve = true; initializedValve = true;
break; break;
} }
} }
if (!initializedValve) if (!initializedValve) {
throw new LifecycleException("Session handling valve is not initialized.."); throw new LifecycleException("Session handling valve is not initialized..");
}
initialize(); initialize();
log.info("The sessions will expire after " + (getSessionTimeout(null)) + " seconds."); log.info("The sessions will expire after " + (getSessionTimeout(null)) + " seconds.");
context.setDistributable(true); context.setDistributable(true);
} }
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
protected synchronized void stopInternal() throws LifecycleException { */
super.setState(LifecycleState.STOPPING); @Override
super.stopInternal(); protected synchronized void stopInternal() throws LifecycleException {
} super.setState(LifecycleState.STOPPING);
super.stopInternal();
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Session createSession(String sessionId) { */
if (sessionId != null) { @Override
sessionId = (this.dataCache.setnx(sessionId, SessionConstants.NULL_SESSION) == 0L) ? null : sessionId; public Session createSession(String sessionId) {
} else { if (sessionId != null) {
do { sessionId =
sessionId = generateSessionId(); (this.dataCache.setnx(sessionId, SessionConstants.NULL_SESSION) == 0L) ? null : sessionId;
} while (this.dataCache.setnx(sessionId, SessionConstants.NULL_SESSION) == 0L); } else {
} do {
sessionId = generateSessionId();
} while (this.dataCache.setnx(sessionId, SessionConstants.NULL_SESSION) == 0L);
}
Session session = (sessionId != null) ? (Session) createEmptySession() : null; Session session = (sessionId != null) ? createEmptySession() : null;
if (session != null) { if (session != null) {
session.setId(sessionId); session.setId(sessionId);
session.setNew(true); session.setNew(true);
session.setValid(true); session.setValid(true);
session.setCreationTime(System.currentTimeMillis()); session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(getSessionTimeout(session)); session.setMaxInactiveInterval(getSessionTimeout(session));
session.tellNew(); session.tellNew();
} }
setValues(sessionId, session, false, new SessionMetadata()); setValues(sessionId, session, false, new SessionMetadata());
if (session != null) { if (session != null) {
try { try {
save(session, true); save(session, true);
} catch (Exception ex) { } catch (Exception ex) {
log.error("Error occured while creating session..", ex); log.error("Error occurred while creating session..", ex);
setValues(null, null); setValues(null, null);
session = null; session = null;
} }
} }
return session; return session;
} }
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Session createEmptySession() { */
return new Session(this); @Override
} public Session createEmptySession() {
return new Session(this);
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void add(org.apache.catalina.Session session) { */
save(session, false); @Override
} public void add(org.apache.catalina.Session session) {
save(session, false);
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public Session findSession(String sessionId) throws IOException { */
Session session = null; @Override
if (sessionId != null && this.sessionContext.get() != null && sessionId.equals(this.sessionContext.get().getId())) { public Session findSession(String sessionId) throws IOException {
session = this.sessionContext.get().getSession(); Session session = null;
} else { if (sessionId != null && this.sessionContext.get() != null && sessionId
byte[] data = this.dataCache.get(sessionId); .equals(this.sessionContext.get().getId())) {
session = this.sessionContext.get().getSession();
} else {
byte[] data = this.dataCache.get(sessionId);
boolean isPersisted = false; boolean isPersisted = false;
SessionMetadata metadata = null; SessionMetadata metadata = null;
if (data == null) { if (data == null) {
session = null; session = null;
metadata = null; metadata = null;
sessionId = null; sessionId = null;
isPersisted = false; isPersisted = false;
} else { } else {
if (Arrays.equals(SessionConstants.NULL_SESSION, data)) { if (Arrays.equals(SessionConstants.NULL_SESSION, data)) {
throw new IOException("NULL session data"); throw new IOException("NULL session data");
} }
try { try {
metadata = new SessionMetadata(); metadata = new SessionMetadata();
Session newSession = (Session) createEmptySession(); Session newSession = createEmptySession();
this.serializer.deserializeSessionData(data, newSession, metadata); this.serializer.deserializeSessionData(data, newSession, metadata);
newSession.setId(sessionId); newSession.setId(sessionId);
newSession.access(); newSession.access();
newSession.setNew(false); newSession.setNew(false);
newSession.setValid(true); newSession.setValid(true);
newSession.resetDirtyTracking(); newSession.resetDirtyTracking();
newSession.setMaxInactiveInterval(getSessionTimeout(newSession)); newSession.setMaxInactiveInterval(getSessionTimeout(newSession));
session = newSession; session = newSession;
isPersisted = true; isPersisted = true;
} catch (Exception ex) { } catch (Exception ex) {
log.error("Error occured while de-serializing the session object..", ex); log.error("Error occurred while de-serializing the session object..", ex);
} }
} }
setValues(sessionId, session, isPersisted, metadata); setValues(sessionId, session, isPersisted, metadata);
} }
return session; return session;
} }
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void remove(org.apache.catalina.Session session) { */
remove(session, false); @Override
} public void remove(org.apache.catalina.Session session) {
remove(session, false);
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void remove(org.apache.catalina.Session session, boolean update) { */
this.dataCache.expire(session.getId(), 10); @Override
} public void remove(org.apache.catalina.Session session, boolean update) {
this.dataCache.expire(session.getId(), 10);
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void load() throws ClassNotFoundException, IOException { */
// Auto-generated method stub @Override
} public void load() throws ClassNotFoundException, IOException {
// Auto-generated method stub
}
/** {@inheritDoc} */ /**
@Override * {@inheritDoc}
public void unload() throws IOException { */
// Auto-generated method stub @Override
} public void unload() throws IOException {
// Auto-generated method stub
}
/** /**
* To initialize the session manager * To initialize the session manager
*/ */
private void initialize() { private void initialize() {
try { try {
this.dataCache = new RedisDataCache(); this.dataCache = new RedisDataCache();
this.serializer = new SerializationUtil(); this.serializer = new SerializationUtil();
Context context = getContextIns(); Context context = getContextIns();
ClassLoader loader = (context != null && context.getLoader() != null) ? context.getLoader().getClassLoader() : null; ClassLoader loader =
this.serializer.setClassLoader(loader); (context != null && context.getLoader() != null) ? context.getLoader().getClassLoader()
} catch (Exception ex) { : null;
log.error("Error occured while initializing the session manager..", ex); this.serializer.setClassLoader(loader);
throw ex; } catch (Exception ex) {
} log.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
* */
* @param session public void save(org.apache.catalina.Session session, boolean forceSave) {
* @param forceSave try {
*/ Boolean isPersisted;
public void save(org.apache.catalina.Session session, boolean forceSave) { Session newSession = (Session) session;
try { byte[] hash =
Boolean isPersisted; (this.sessionContext.get() != null && this.sessionContext.get().getMetadata() != null)
Session newSession = (Session) session; ? this.sessionContext.get().getMetadata().getAttributesHash() : null;
byte[] hash = (this.sessionContext.get() != null && this.sessionContext.get().getMetadata() != null) byte[] currentHash = serializer.getSessionAttributesHashCode(newSession);
? this.sessionContext.get().getMetadata().getAttributesHash() : null;
byte[] currentHash = serializer.getSessionAttributesHashCode(newSession);
if (forceSave || newSession.isDirty() if (forceSave || newSession.isDirty()
|| (isPersisted = (this.sessionContext.get() != null) ? this.sessionContext.get().isPersisted() : null) == null || (isPersisted =
|| !isPersisted || !Arrays.equals(hash, currentHash)) { (this.sessionContext.get() != null) ? this.sessionContext.get().isPersisted() : null)
== null
|| !isPersisted || !Arrays.equals(hash, currentHash)) {
SessionMetadata metadata = new SessionMetadata(); SessionMetadata metadata = new SessionMetadata();
metadata.setAttributesHash(currentHash); metadata.setAttributesHash(currentHash);
this.dataCache.set(newSession.getId(), serializer.serializeSessionData(newSession, metadata)); this.dataCache
newSession.resetDirtyTracking(); .set(newSession.getId(), serializer.serializeSessionData(newSession, metadata));
setValues(true, metadata); newSession.resetDirtyTracking();
} setValues(true, metadata);
}
int timeout = getSessionTimeout(newSession); int timeout = getSessionTimeout(newSession);
this.dataCache.expire(newSession.getId(), timeout); this.dataCache.expire(newSession.getId(), timeout);
log.trace("Session [" + newSession.getId() + "] expire in [" + timeout + "] seconds."); log.trace("Session [" + newSession.getId() + "] expire in [" + timeout + "] seconds.");
} catch (IOException ex) { } catch (IOException ex) {
log.error("Error occured while saving the session object in data cache..", ex); log.error("Error occurred while saving the session object in data cache..", ex);
} }
} }
/** /**
* To process post request process * To process post request process
* */
* @param request public void afterRequest(Request request) {
*/ Session session = null;
public void afterRequest(Request request) { try {
Session session = null; session = (this.sessionContext.get() != null) ? this.sessionContext.get().getSession() : null;
try { if (session != null) {
session = (this.sessionContext.get() != null) ? this.sessionContext.get().getSession() : null; if (session.isValid()) {
if (session != null) { save(session, getAlwaysSaveAfterRequest());
if (session.isValid()) } else {
save(session, getAlwaysSaveAfterRequest()); remove(session);
else }
remove(session); log.trace(
log.trace("Session object " + (session.isValid() ? "saved: " : "removed: ") + session.getId()); "Session object " + (session.isValid() ? "saved: " : "removed: ") + session.getId());
} }
} catch (Exception ex) { } catch (Exception ex) {
log.error("Error occured while processing post request process..", ex); log.error("Error occurred while processing post request process..", ex);
} finally { } finally {
this.sessionContext.remove(); this.sessionContext.remove();
log.trace("Session removed from ThreadLocal:" + ((session != null) ? session.getIdInternal() : "")); log.trace(
} "Session removed from ThreadLocal:" + ((session != null) ? session.getIdInternal() : ""));
} }
}
/** private int getSessionTimeout(Session session) {
* @return int timeout = getContextIns().getSessionTimeout() * 60;
*/ int sessionTimeout = (session == null) ? 0 : session.getMaxInactiveInterval();
private int getSessionTimeout(Session session) { return (sessionTimeout < timeout) ? ((timeout < 1800) ? 1800 : timeout) : sessionTimeout;
int timeout = getContextIns().getSessionTimeout() * 60; }
int sessionTimeout = (session == null) ? 0 : session.getMaxInactiveInterval();
return (sessionTimeout < timeout) ? ((timeout < 1800) ? 1800 : timeout) : sessionTimeout;
}
/** private void setValues(String sessionId, Session session) {
* @param sessionId if (this.sessionContext.get() == null) {
* @param session this.sessionContext.set(new SessionContext());
*/ }
private void setValues(String sessionId, Session session) { this.sessionContext.get().setId(sessionId);
if (this.sessionContext.get() == null) { this.sessionContext.get().setSession(session);
this.sessionContext.set(new SessionContext()); }
}
this.sessionContext.get().setId(sessionId);
this.sessionContext.get().setSession(session);
}
/** private void setValues(boolean isPersisted, SessionMetadata metadata) {
* @param isPersisted if (this.sessionContext.get() == null) {
* @param metadata this.sessionContext.set(new SessionContext());
*/ }
private void setValues(boolean isPersisted, SessionMetadata metadata) { this.sessionContext.get().setMetadata(metadata);
if (this.sessionContext.get() == null) { this.sessionContext.get().setPersisted(isPersisted);
this.sessionContext.set(new SessionContext()); }
}
this.sessionContext.get().setMetadata(metadata);
this.sessionContext.get().setPersisted(isPersisted);
}
/** private void setValues(String sessionId, Session session, boolean isPersisted,
* @param sessionId SessionMetadata metadata) {
* @param session setValues(sessionId, session);
* @param isPersisted setValues(isPersisted, metadata);
* @param metadata }
*/
private void setValues(String sessionId, Session session, boolean isPersisted, SessionMetadata metadata) {
setValues(sessionId, session);
setValues(isPersisted, metadata);
}
/** private Context getContextIns() {
* @return try {
*/ Method method = this.getClass().getSuperclass().getDeclaredMethod("getContext");
private Context getContextIns() { return (Context) method.invoke(this);
try { } catch (Exception ex) {
Method method = this.getClass().getSuperclass().getDeclaredMethod("getContext"); try {
return (Context) method.invoke(this); Method method = this.getClass().getSuperclass().getDeclaredMethod("getContainer");
} catch (Exception ex) { return (Context) method.invoke(this);
try { } catch (Exception ex2) {
Method method = this.getClass().getSuperclass().getDeclaredMethod("getContainer"); log.error("Error in getContext", ex2);
return (Context) method.invoke(this); }
} catch (Exception ex2) { }
log.error("Error in getContext", ex2); throw new RuntimeException("Error occurred while creating container instance");
} }
}
return null;
}
} }