Compare commits
39 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
d0de8bb6e2 | |
![]() |
8593f88243 | |
![]() |
9da24f2328 | |
![]() |
e31ea59636 | |
![]() |
45b0b98725 | |
![]() |
2e505deee1 | |
![]() |
df80fa0da3 | |
![]() |
f4c6322793 | |
![]() |
a0eab9407e | |
![]() |
1f6e57c164 | |
![]() |
5d0c7aa7a1 | |
![]() |
3916e217a5 | |
![]() |
39df08832a | |
![]() |
372332e8dc | |
![]() |
7ace9f2b7b | |
![]() |
0ecdfc2661 | |
![]() |
80acc01418 | |
![]() |
ab04189b93 | |
![]() |
246b64def9 | |
![]() |
2b13fdadc9 | |
![]() |
16fffca534 | |
![]() |
b1d33afd0a | |
![]() |
920662654e | |
![]() |
905ae577c5 | |
![]() |
45b1c5a9b0 | |
![]() |
9a409e43a4 | |
![]() |
113933975c | |
![]() |
f45e1b23a9 | |
![]() |
b73c1b78c2 | |
![]() |
094f612138 | |
![]() |
3d5afda53e | |
![]() |
8902534782 | |
![]() |
7ea845ebca | |
![]() |
4eb0e433b1 | |
![]() |
df14798b1c | |
![]() |
a0c0a81332 | |
![]() |
43816ec3c7 | |
![]() |
8dcdb983fa | |
![]() |
068967a773 |
54
README.md
54
README.md
|
@ -14,11 +14,31 @@ Going forward, we no need to enable sticky session (JSESSIONID) in Load Balancer
|
||||||
- Apache Tomcat 7
|
- Apache Tomcat 7
|
||||||
- Apache Tomcat 8
|
- Apache Tomcat 8
|
||||||
- Apache Tomcat 9
|
- Apache Tomcat 9
|
||||||
|
- Apache Tomcat 10
|
||||||
|
|
||||||
## Downloads:
|
## Downloads: [](https://github.com/ran-jit/tomcat-cluster-redis-session-manager/wiki)
|
||||||
- [latest version (3.0.1)](https://github.com/ran-jit/tomcat-cluster-redis-session-manager/releases/tag/3.0.1)
|
- [latest version (4.0)](https://github.com/ran-jit/tomcat-cluster-redis-session-manager/releases/tag/4.0)
|
||||||
- [older versions](https://github.com/ran-jit/tomcat-cluster-redis-session-manager/wiki)
|
- [older versions](https://github.com/ran-jit/tomcat-cluster-redis-session-manager/wiki)
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.buymeacoffee.com/ranmanic" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-red.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Maven configuration
|
||||||
|
```
|
||||||
|
<repository>
|
||||||
|
<id>repsy</id>
|
||||||
|
<name>tomcat-cluster-redis-session-manager-repo</name>
|
||||||
|
<url>https://repo.repsy.io/mvn/ranmanic/tomcat-session-manager</url>
|
||||||
|
</repository>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>tomcat-session-manager</groupId>
|
||||||
|
<artifactId>redis</artifactId>
|
||||||
|
<version>4.0</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
#### Pre-requisite:
|
#### Pre-requisite:
|
||||||
1. jedis.jar
|
1. jedis.jar
|
||||||
2. commons-pool2.jar
|
2. commons-pool2.jar
|
||||||
|
@ -29,23 +49,33 @@ more details.. https://github.com/ran-jit/tomcat-cluster-redis-session-manager/w
|
||||||
|
|
||||||
#### Steps to be done,
|
#### Steps to be done,
|
||||||
1. Copy the downloaded jars to your tomcat/lib directory.
|
1. Copy the downloaded jars to your tomcat/lib directory.
|
||||||
- **tomcat/lib/**
|
```
|
||||||
|
tomcat/lib/
|
||||||
|
```
|
||||||
|
|
||||||
2. Add tomcat system property "catalina.base".
|
2. Add tomcat system property "catalina.base".
|
||||||
- **catalina.base="TOMCAT_LOCATION"**
|
```
|
||||||
* example: export catalina.base=/opt/tomcat
|
catalina.base="TOMCAT_LOCATION"
|
||||||
|
example: env "catalina.base=/opt/tomcat" bash
|
||||||
|
```
|
||||||
|
|
||||||
3. Copy the redis-data-cache.properties file to your tomcat/conf directory and update your Redis server details.
|
3. Copy the redis-data-cache.properties file to your tomcat/conf directory and update your Redis server details.
|
||||||
- **tomcat/conf/redis-data-cache.properties**
|
```
|
||||||
|
tomcat/conf/redis-data-cache.properties
|
||||||
|
```
|
||||||
|
|
||||||
4. Add the below two lines in your tomcat/conf/context.xml file.
|
4. Add the below two lines in your tomcat/conf/context.xml file.
|
||||||
- **<Valve className="tomcat.request.session.redis.SessionHandlerValve" />**
|
```
|
||||||
- **<Manager className="tomcat.request.session.redis.SessionManager" />**
|
<Valve className="tomcat.request.session.redis.SessionHandlerValve" />
|
||||||
|
<Manager className="tomcat.request.session.redis.SessionManager" />
|
||||||
|
```
|
||||||
|
|
||||||
5. Verify the session expiration time in tomcat/conf/web.xml file.
|
5. Verify the session expiration time in tomcat/conf/web.xml file.
|
||||||
- **<session-config>**
|
```
|
||||||
- **<session-timeout>60</session-timeout>**
|
<session-config>
|
||||||
- **</session-config>**
|
<session-timeout>60</session-timeout>
|
||||||
|
</session-config>
|
||||||
|
```
|
||||||
|
|
||||||
### Note:
|
### Note:
|
||||||
- **All your session attribute values must implement java.io.Serializable.**
|
- **All your session attribute values must implement java.io.Serializable.**
|
||||||
|
@ -63,6 +93,8 @@ more details.. https://github.com/ran-jit/tomcat-cluster-redis-session-manager/w
|
||||||
<tr><td>redis.sentinel.enabled</td><td>To enable redis sentinel mode<br/>- default: false<br>- supported values: true/false</td></tr>
|
<tr><td>redis.sentinel.enabled</td><td>To enable redis sentinel mode<br/>- default: false<br>- supported values: true/false</td></tr>
|
||||||
<tr><td>redis.sentinel.master</td><td>Redis sentinel master name<br/>- default: mymaster</td></tr>
|
<tr><td>redis.sentinel.master</td><td>Redis sentinel master name<br/>- default: mymaster</td></tr>
|
||||||
<tr><td>lb.sticky-session.enabled</td><td>To enable redis and standard session mode<br><br>If enabled,<ol><li>Must be enabled sticky session in your load balancer configuration. Else this manager may not return the updated session values</li><li>Session values are stored in local jvm and redis</li><li>If redis is down/not responding, requests uses jvm stored session values to process user requests. Redis comes back the values will be synced</li></ol>- default: false</td></tr>
|
<tr><td>lb.sticky-session.enabled</td><td>To enable redis and standard session mode<br><br>If enabled,<ol><li>Must be enabled sticky session in your load balancer configuration. Else this manager may not return the updated session values</li><li>Session values are stored in local jvm and redis</li><li>If redis is down/not responding, requests uses jvm stored session values to process user requests. Redis comes back the values will be synced</li></ol>- default: false</td></tr>
|
||||||
|
<tr><td>session.persistent.policies</td><td>session persistent policies.<br/><br/>- policies - DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST <br/><ol><li>SAVE_ON_CHANGE: every time session.setAttribute() or session.removeAttribute() is called the session will be saved.</li><li>ALWAYS_SAVE_AFTER_REQUEST: force saving after every request, regardless of whether or not the manager has detected changes to the session.</li></ol>- default: DEFAULT</td></tr>
|
||||||
|
<tr><td>redis.sso.timeout</td><td>single-sign-on session timeout.<br/>- default: 0 ms (-no expiry)</td></tr>
|
||||||
</table>
|
</table>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
60
pom.xml
60
pom.xml
|
@ -1,14 +1,22 @@
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<groupId>tomcat-cluster-redis-session-manager</groupId>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>tomcat-cluster-redis-session-manager</artifactId>
|
<groupId>tomcat-session-manager</groupId>
|
||||||
<version>3.0.2</version>
|
<artifactId>redis</artifactId>
|
||||||
|
<version>4.0</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>tomcat-cluster-redis-session-manager</name>
|
<name>tomcat-cluster-redis-session-manager</name>
|
||||||
<url>http://maven.apache.org</url>
|
<url>https://ranmanic.in</url>
|
||||||
|
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<id>ranmanic</id>
|
||||||
|
<name>Ranjith Manickam</name>
|
||||||
|
<email>ranjith@ranmanic.in</email>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
@ -24,12 +32,18 @@
|
||||||
<target-java.version>${source-java.version}</target-java.version>
|
<target-java.version>${source-java.version}</target-java.version>
|
||||||
|
|
||||||
<!-- For local development properties begins.. -->
|
<!-- For local development properties begins.. -->
|
||||||
<!--<tomcat-catalina.version>apache-tomcat-8.5.32</tomcat-catalina.version>-->
|
<tomcat.version>8.5.32</tomcat.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.. -->
|
<!-- For local development properties end.. -->
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<distributionManagement>
|
||||||
|
<repository>
|
||||||
|
<id>repsy</id>
|
||||||
|
<name>tomcat-cluster-redis-session-manager-repo</name>
|
||||||
|
<url>https://repo.repsy.io/mvn/ranmanic/tomcat-session-manager</url>
|
||||||
|
</repository>
|
||||||
|
</distributionManagement>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>redis.clients</groupId>
|
<groupId>redis.clients</groupId>
|
||||||
|
@ -48,21 +62,21 @@
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- For local development dependency begins.. -->
|
<!-- For local development dependency begins.. -->
|
||||||
<!--<dependency>-->
|
<dependency>
|
||||||
<!--<groupId>apache-tomcat</groupId>-->
|
<groupId>org.apache.tomcat</groupId>
|
||||||
<!--<artifactId>catalina</artifactId>-->
|
<artifactId>tomcat-catalina</artifactId>
|
||||||
<!--<version>${tomcat-catalina.version}</version>-->
|
<version>${tomcat.version}</version>
|
||||||
<!--</dependency>-->
|
</dependency>
|
||||||
<!--<dependency>-->
|
<dependency>
|
||||||
<!--<groupId>apache-tomcat</groupId>-->
|
<groupId>org.apache.tomcat</groupId>
|
||||||
<!--<artifactId>servlet-api</artifactId>-->
|
<artifactId>tomcat-servlet-api</artifactId>
|
||||||
<!--<version>${tomcat-servlet-api.version}</version>-->
|
<version>${tomcat.version}</version>
|
||||||
<!--</dependency>-->
|
</dependency>
|
||||||
<!--<dependency>-->
|
<dependency>
|
||||||
<!--<groupId>apache-tomcat</groupId>-->
|
<groupId>org.apache.tomcat</groupId>
|
||||||
<!--<artifactId>tomcat-api</artifactId>-->
|
<artifactId>tomcat-api</artifactId>
|
||||||
<!--<version>${tomcat-api.version}</version>-->
|
<version>${tomcat.version}</version>
|
||||||
<!--</dependency>-->
|
</dependency>
|
||||||
<!-- For local development dependency end.. -->
|
<!-- For local development dependency end.. -->
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package tomcat.request.session.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/** author: Ranjith Manickam @ 5 Feb' 2020 */
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Property {
|
||||||
|
String name() default "";
|
||||||
|
|
||||||
|
String defaultValue() default "";
|
||||||
|
|
||||||
|
PropertyType type() default PropertyType.STRING;
|
||||||
|
|
||||||
|
enum PropertyType {
|
||||||
|
STRING,
|
||||||
|
BOOLEAN,
|
||||||
|
INTEGER,
|
||||||
|
LONG
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,8 @@
|
||||||
package tomcat.request.session;
|
package tomcat.request.session.constant;
|
||||||
|
|
||||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||||
public interface SessionConstants {
|
public interface SessionConstants {
|
||||||
byte[] NULL_SESSION = "null".getBytes();
|
byte[] NULL_SESSION = "null".getBytes();
|
||||||
String CATALINA_BASE = "catalina.base";
|
|
||||||
String CONF = "conf";
|
|
||||||
|
|
||||||
enum SessionPolicy {
|
enum SessionPolicy {
|
||||||
DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST;
|
DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST;
|
|
@ -45,4 +45,12 @@ public interface DataCache {
|
||||||
* @return - Returns the number of keys that were removed.
|
* @return - Returns the number of keys that were removed.
|
||||||
*/
|
*/
|
||||||
Long delete(String key);
|
Long delete(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the key exists in data-cache.
|
||||||
|
*
|
||||||
|
* @param key - key with which the specified value is to be associated.
|
||||||
|
* @return - Returns true, if the key exists.
|
||||||
|
*/
|
||||||
|
Boolean exists(String key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
package tomcat.request.session.data.cache;
|
|
||||||
|
|
||||||
/** author: Ranjith Manickam @ 3 Dec' 2018 */
|
|
||||||
public interface DataCacheConstants {
|
|
||||||
|
|
||||||
// redis properties file name
|
|
||||||
String APPLICATION_PROPERTIES_FILE = "redis-data-cache.properties";
|
|
||||||
|
|
||||||
// redis properties
|
|
||||||
String REDIS_HOSTS = "redis.hosts";
|
|
||||||
String REDIS_CLUSTER_ENABLED = "redis.cluster.enabled:false";
|
|
||||||
String REDIS_SENTINEL_ENABLED = "redis.sentinel.enabled:false";
|
|
||||||
String LB_STICKY_SESSION_ENABLED = "lb.sticky-session.enabled:false";
|
|
||||||
|
|
||||||
String REDIS_MAX_ACTIVE = "redis.max.active:10";
|
|
||||||
String REDIS_TEST_ONBORROW = "redis.test.onBorrow:true";
|
|
||||||
String REDIS_TEST_ONRETURN = "redis.test.onReturn:true";
|
|
||||||
String REDIS_MAX_IDLE = "redis.max.idle:5";
|
|
||||||
String REDIS_MIN_IDLE = "redis.min.idle:1";
|
|
||||||
String REDIS_TEST_WHILEIDLE = "redis.test.whileIdle:true";
|
|
||||||
String REDIS_TEST_NUMPEREVICTION = "redis.test.numPerEviction:10";
|
|
||||||
String REDIS_TIME_BETWEENEVICTION = "redis.time.betweenEviction:60000";
|
|
||||||
|
|
||||||
String REDIS_PASSWORD = "redis.password";
|
|
||||||
String REDIS_DATABASE = "redis.database:0";
|
|
||||||
String REDIS_TIMEOUT = "redis.timeout:2000";
|
|
||||||
|
|
||||||
String REDIS_SENTINEL_MASTER = "redis.sentinel.master:mymaster";
|
|
||||||
|
|
||||||
String REDIS_CONN_FAILED_RETRY_MSG = "Jedis connection failed, retrying...";
|
|
||||||
|
|
||||||
String SESSION_EXPIRY_JOB_INTERVAL = "redis.session.expiry.job.interval:60";
|
|
||||||
String SESSION_DATA_SYNC_JOB_INTERVAL = "redis.session.data-sync.job.interval:10";
|
|
||||||
|
|
||||||
enum RedisConfigType {
|
|
||||||
DEFAULT,
|
|
||||||
SENTINEL,
|
|
||||||
CLUSTER
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,87 +1,25 @@
|
||||||
package tomcat.request.session.data.cache;
|
package tomcat.request.session.data.cache;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import tomcat.request.session.SessionConstants;
|
|
||||||
import tomcat.request.session.data.cache.impl.StandardDataCache;
|
import tomcat.request.session.data.cache.impl.StandardDataCache;
|
||||||
import tomcat.request.session.data.cache.impl.redis.RedisCache;
|
import tomcat.request.session.data.cache.impl.redis.RedisCache;
|
||||||
|
import tomcat.request.session.model.Config;
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
/** author: Ranjith Manickam @ 3 Dec' 2018 */
|
/** author: Ranjith Manickam @ 3 Dec' 2018 */
|
||||||
public class DataCacheFactory {
|
public class DataCacheFactory {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(DataCacheFactory.class);
|
private final Config config;
|
||||||
|
|
||||||
private final int sessionExpiryTime;
|
private final int sessionExpiryTime;
|
||||||
|
|
||||||
public DataCacheFactory(int sessionExpiryTime) {
|
public DataCacheFactory(Config config, int sessionExpiryTime) {
|
||||||
|
this.config = config;
|
||||||
this.sessionExpiryTime = sessionExpiryTime;
|
this.sessionExpiryTime = sessionExpiryTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** To get data cache. */
|
/** To get data cache. */
|
||||||
public DataCache getDataCache() {
|
public DataCache getDataCache() {
|
||||||
Properties properties = getApplicationProperties();
|
if (this.config.getLbStickySessionEnabled()) {
|
||||||
|
return new StandardDataCache(this.config, this.sessionExpiryTime);
|
||||||
if (Boolean.valueOf(getProperty(properties, DataCacheConstants.LB_STICKY_SESSION_ENABLED))) {
|
|
||||||
return new StandardDataCache(properties, this.sessionExpiryTime);
|
|
||||||
}
|
}
|
||||||
|
return new RedisCache(this.config);
|
||||||
return new RedisCache(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** To get redis data cache properties. */
|
|
||||||
private Properties getApplicationProperties() {
|
|
||||||
Properties properties = new Properties();
|
|
||||||
try {
|
|
||||||
String filePath = System.getProperty(SessionConstants.CATALINA_BASE).concat(File.separator)
|
|
||||||
.concat(SessionConstants.CONF).concat(File.separator)
|
|
||||||
.concat(DataCacheConstants.APPLICATION_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(DataCacheConstants.APPLICATION_PROPERTIES_FILE);
|
|
||||||
}
|
|
||||||
properties.load(resourceStream);
|
|
||||||
} finally {
|
|
||||||
if (resourceStream != null) {
|
|
||||||
resourceStream.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException ex) {
|
|
||||||
LOGGER.error("Error while retrieving application properties", ex);
|
|
||||||
}
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To get property with the specified key in this properties list.
|
|
||||||
*
|
|
||||||
* @param properties - properties list.
|
|
||||||
* @param key - search key.
|
|
||||||
* @return - Returns the property value.
|
|
||||||
*/
|
|
||||||
public static String getProperty(Properties properties, String key) {
|
|
||||||
return getProperty(properties, key, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To get property with the specified key in this properties list.
|
|
||||||
*
|
|
||||||
* @param properties - properties list.
|
|
||||||
* @param key - search key.
|
|
||||||
* @param defaultValue - default value.
|
|
||||||
* @return - - Returns the property value.
|
|
||||||
*/
|
|
||||||
public static String getProperty(Properties properties, String key, String defaultValue) {
|
|
||||||
String[] keyValue = key.split(":");
|
|
||||||
return properties.getProperty(keyValue[0], (keyValue.length > 1 && defaultValue == null) ? keyValue[1] : defaultValue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,12 @@ package tomcat.request.session.data.cache.impl;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import tomcat.request.session.data.cache.DataCache;
|
import tomcat.request.session.data.cache.DataCache;
|
||||||
import tomcat.request.session.data.cache.DataCacheConstants;
|
|
||||||
import tomcat.request.session.data.cache.DataCacheFactory;
|
|
||||||
import tomcat.request.session.data.cache.impl.redis.RedisCache;
|
import tomcat.request.session.data.cache.impl.redis.RedisCache;
|
||||||
|
import tomcat.request.session.model.Config;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
@ -32,15 +30,15 @@ public class StandardDataCache extends RedisCache {
|
||||||
private final Executor expiryJobExecutor;
|
private final Executor expiryJobExecutor;
|
||||||
private final Executor dataSyncJobExecutor;
|
private final Executor dataSyncJobExecutor;
|
||||||
|
|
||||||
public StandardDataCache(Properties properties, int sessionExpiryTime) {
|
public StandardDataCache(Config config, int sessionExpiryTime) {
|
||||||
super(properties);
|
super(config);
|
||||||
this.sessionExpiryTime = sessionExpiryTime;
|
this.sessionExpiryTime = sessionExpiryTime;
|
||||||
this.sessionData = new ConcurrentHashMap<>();
|
this.sessionData = new ConcurrentHashMap<>();
|
||||||
this.expiryJob = new Date().getTime();
|
this.expiryJob = new Date().getTime();
|
||||||
this.dataSyncJob = new Date().getTime();
|
this.dataSyncJob = new Date().getTime();
|
||||||
this.processDataSync = false;
|
this.processDataSync = false;
|
||||||
this.expiryJobTriggerInterval = TimeUnit.MINUTES.toMillis(Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.SESSION_EXPIRY_JOB_INTERVAL)));
|
this.expiryJobTriggerInterval = TimeUnit.MINUTES.toMillis(config.getRedisSessionExpiryJobInterval());
|
||||||
this.dataSyncJobTriggerInterval = TimeUnit.MINUTES.toMillis(Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.SESSION_DATA_SYNC_JOB_INTERVAL)));
|
this.dataSyncJobTriggerInterval = TimeUnit.MINUTES.toMillis(config.getRedisSessionDataSyncJobInterval());
|
||||||
this.expiryJobExecutor = Executors.newSingleThreadExecutor();
|
this.expiryJobExecutor = Executors.newSingleThreadExecutor();
|
||||||
this.dataSyncJobExecutor = Executors.newSingleThreadExecutor();
|
this.dataSyncJobExecutor = Executors.newSingleThreadExecutor();
|
||||||
}
|
}
|
||||||
|
@ -112,8 +110,14 @@ public class StandardDataCache extends RedisCache {
|
||||||
return (value == null) ? 0L : 1L;
|
return (value == null) ? 0L : 1L;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public Boolean exists(String key) {
|
||||||
|
return this.sessionData.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
/** Session data. */
|
/** Session data. */
|
||||||
private class SessionData implements Serializable {
|
private static class SessionData implements Serializable {
|
||||||
private byte[] value;
|
private byte[] value;
|
||||||
private long lastAccessedOn;
|
private long lastAccessedOn;
|
||||||
|
|
||||||
|
@ -157,7 +161,7 @@ public class StandardDataCache extends RedisCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Session data redis sync thread. */
|
/** Session data redis sync thread. */
|
||||||
private class SessionDataSyncThread implements Runnable {
|
private static class SessionDataSyncThread implements Runnable {
|
||||||
|
|
||||||
private final Logger LOGGER = LoggerFactory.getLogger(SessionDataSyncThread.class);
|
private final Logger LOGGER = LoggerFactory.getLogger(SessionDataSyncThread.class);
|
||||||
|
|
||||||
|
@ -190,7 +194,7 @@ public class StandardDataCache extends RedisCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Session data expiry thread. This will takes care of the session expired data removal. */
|
/** Session data expiry thread. This will takes care of the session expired data removal. */
|
||||||
private class SessionDataExpiryThread implements Runnable {
|
private static class SessionDataExpiryThread implements Runnable {
|
||||||
|
|
||||||
private final Logger LOGGER = LoggerFactory.getLogger(SessionDataExpiryThread.class);
|
private final Logger LOGGER = LoggerFactory.getLogger(SessionDataExpiryThread.class);
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,14 @@ package tomcat.request.session.data.cache.impl.redis;
|
||||||
|
|
||||||
import redis.clients.jedis.HostAndPort;
|
import redis.clients.jedis.HostAndPort;
|
||||||
import redis.clients.jedis.JedisPoolConfig;
|
import redis.clients.jedis.JedisPoolConfig;
|
||||||
import redis.clients.jedis.Protocol;
|
|
||||||
import tomcat.request.session.data.cache.DataCache;
|
import tomcat.request.session.data.cache.DataCache;
|
||||||
import tomcat.request.session.data.cache.DataCacheConstants;
|
import tomcat.request.session.model.Config;
|
||||||
import tomcat.request.session.data.cache.DataCacheConstants.RedisConfigType;
|
import tomcat.request.session.model.Config.RedisConfigType;
|
||||||
import tomcat.request.session.data.cache.DataCacheFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||||
|
@ -20,72 +17,71 @@ public class RedisCache implements DataCache {
|
||||||
|
|
||||||
private DataCache dataCache;
|
private DataCache dataCache;
|
||||||
|
|
||||||
public RedisCache(Properties properties) {
|
public RedisCache(Config config) {
|
||||||
initialize(properties);
|
initialize(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public byte[] set(String key, byte[] value) {
|
public byte[] set(String key, byte[] value) {
|
||||||
return dataCache.set(key, value);
|
return this.dataCache.set(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public Long setnx(String key, byte[] value) {
|
public Long setnx(String key, byte[] value) {
|
||||||
return dataCache.setnx(key, value);
|
return this.dataCache.setnx(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public Long expire(String key, int seconds) {
|
public Long expire(String key, int seconds) {
|
||||||
return dataCache.expire(key, seconds);
|
return this.dataCache.expire(key, seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public byte[] get(String key) {
|
public byte[] get(String key) {
|
||||||
return dataCache.get(key);
|
return this.dataCache.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public Long delete(String key) {
|
public Long delete(String key) {
|
||||||
return dataCache.delete(key);
|
return this.dataCache.delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initialize(Properties properties) {
|
/** {@inheritDoc} */
|
||||||
RedisConfigType configType;
|
@Override
|
||||||
if (Boolean.valueOf(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_CLUSTER_ENABLED))) {
|
public Boolean exists(String key) {
|
||||||
configType = RedisConfigType.CLUSTER;
|
return this.dataCache.exists(key);
|
||||||
} else if (Boolean.valueOf(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_SENTINEL_ENABLED))) {
|
}
|
||||||
configType = RedisConfigType.SENTINEL;
|
|
||||||
} else {
|
|
||||||
configType = RedisConfigType.DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
String hosts = DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_HOSTS, String.format("%s:%s", Protocol.DEFAULT_HOST, Protocol.DEFAULT_PORT));
|
private void initialize(Config config) {
|
||||||
Collection<?> nodes = getJedisNodes(hosts, configType);
|
Collection<?> nodes = getJedisNodes(config.getRedisHosts(), config.getRedisConfigType());
|
||||||
|
JedisPoolConfig poolConfig = getPoolConfig(config);
|
||||||
String password = DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_PASSWORD);
|
switch (config.getRedisConfigType()) {
|
||||||
password = (password != null && !password.isEmpty()) ? password : null;
|
|
||||||
|
|
||||||
int database = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_DATABASE));
|
|
||||||
|
|
||||||
int timeout = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TIMEOUT));
|
|
||||||
timeout = (timeout < Protocol.DEFAULT_TIMEOUT) ? Protocol.DEFAULT_TIMEOUT : timeout;
|
|
||||||
|
|
||||||
JedisPoolConfig poolConfig = getPoolConfig(properties);
|
|
||||||
switch (configType) {
|
|
||||||
case CLUSTER:
|
case CLUSTER:
|
||||||
dataCache = new RedisClusterManager((Set<HostAndPort>) nodes, password, timeout, poolConfig);
|
this.dataCache = new RedisClusterManager((Set<HostAndPort>) nodes,
|
||||||
|
config.getRedisPassword(),
|
||||||
|
config.getRedisTimeout(),
|
||||||
|
poolConfig);
|
||||||
break;
|
break;
|
||||||
case SENTINEL:
|
case SENTINEL:
|
||||||
String masterName = String.valueOf(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_SENTINEL_MASTER));
|
this.dataCache = new RedisSentinelManager((Set<String>) nodes,
|
||||||
dataCache = new RedisSentinelManager((Set<String>) nodes, masterName, password, database, timeout, poolConfig);
|
config.getRedisSentinelMaster(),
|
||||||
|
config.getRedisPassword(),
|
||||||
|
config.getRedisDatabase(),
|
||||||
|
config.getRedisTimeout(),
|
||||||
|
poolConfig);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
dataCache = new RedisStandardManager(((List<String>) nodes).get(0), Integer.parseInt(((List<String>) nodes).get(1)), password, database, timeout, poolConfig);
|
this.dataCache = new RedisStandardManager(((List<String>) nodes).get(0),
|
||||||
|
Integer.parseInt(((List<String>) nodes).get(1)),
|
||||||
|
config.getRedisPassword(),
|
||||||
|
config.getRedisDatabase(),
|
||||||
|
config.getRedisTimeout(),
|
||||||
|
poolConfig);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,34 +89,19 @@ public class RedisCache implements DataCache {
|
||||||
/**
|
/**
|
||||||
* To get redis pool config.
|
* To get redis pool config.
|
||||||
*
|
*
|
||||||
* @param properties - Redis data cache properties.
|
* @param config - Application config.
|
||||||
* @return - Returns the redis pool config.
|
* @return - Returns the redis pool config.
|
||||||
*/
|
*/
|
||||||
private JedisPoolConfig getPoolConfig(Properties properties) {
|
private JedisPoolConfig getPoolConfig(Config config) {
|
||||||
JedisPoolConfig poolConfig = new JedisPoolConfig();
|
JedisPoolConfig poolConfig = new JedisPoolConfig();
|
||||||
int maxActive = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MAX_ACTIVE));
|
poolConfig.setMaxTotal(config.getRedisMaxActive());
|
||||||
poolConfig.setMaxTotal(maxActive);
|
poolConfig.setTestOnBorrow(config.getRedisTestOnBorrow());
|
||||||
|
poolConfig.setTestOnReturn(config.getRedisTestOnReturn());
|
||||||
boolean testOnBorrow = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_ONBORROW));
|
poolConfig.setMaxIdle(config.getRedisMaxIdle());
|
||||||
poolConfig.setTestOnBorrow(testOnBorrow);
|
poolConfig.setMinIdle(config.getRedisMinIdle());
|
||||||
|
poolConfig.setTestWhileIdle(config.getRedisTestWhileIdle());
|
||||||
boolean testOnReturn = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_ONRETURN));
|
poolConfig.setNumTestsPerEvictionRun(config.getRedisTestNumPerEviction());
|
||||||
poolConfig.setTestOnReturn(testOnReturn);
|
poolConfig.setTimeBetweenEvictionRunsMillis(config.getRedisTimeBetweenEviction());
|
||||||
|
|
||||||
int maxIdle = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MAX_ACTIVE));
|
|
||||||
poolConfig.setMaxIdle(maxIdle);
|
|
||||||
|
|
||||||
int minIdle = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_MIN_IDLE));
|
|
||||||
poolConfig.setMinIdle(minIdle);
|
|
||||||
|
|
||||||
boolean testWhileIdle = Boolean.parseBoolean(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_WHILEIDLE));
|
|
||||||
poolConfig.setTestWhileIdle(testWhileIdle);
|
|
||||||
|
|
||||||
int testNumPerEviction = Integer.parseInt(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TEST_NUMPEREVICTION));
|
|
||||||
poolConfig.setNumTestsPerEvictionRun(testNumPerEviction);
|
|
||||||
|
|
||||||
long timeBetweenEviction = Long.parseLong(DataCacheFactory.getProperty(properties, DataCacheConstants.REDIS_TIME_BETWEENEVICTION));
|
|
||||||
poolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEviction);
|
|
||||||
return poolConfig;
|
return poolConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,14 +124,14 @@ public class RedisCache implements DataCache {
|
||||||
switch (configType) {
|
switch (configType) {
|
||||||
case CLUSTER:
|
case CLUSTER:
|
||||||
nodes = (nodes == null) ? new HashSet<>() : nodes;
|
nodes = (nodes == null) ? new HashSet<>() : nodes;
|
||||||
nodes.add(new HostAndPort(hostPortArr[0], Integer.valueOf(hostPortArr[1])));
|
nodes.add(new HostAndPort(hostPortArr[0], Integer.parseInt(hostPortArr[1])));
|
||||||
break;
|
break;
|
||||||
case SENTINEL:
|
case SENTINEL:
|
||||||
nodes = (nodes == null) ? new HashSet<>() : nodes;
|
nodes = (nodes == null) ? new HashSet<>() : nodes;
|
||||||
nodes.add(new HostAndPort(hostPortArr[0], Integer.valueOf(hostPortArr[1])).toString());
|
nodes.add(new HostAndPort(hostPortArr[0], Integer.parseInt(hostPortArr[1])).toString());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
int port = Integer.valueOf(hostPortArr[1]);
|
int port = Integer.parseInt(hostPortArr[1]);
|
||||||
if (!hostPortArr[0].isEmpty() && port > 0) {
|
if (!hostPortArr[0].isEmpty() && port > 0) {
|
||||||
List<String> node = new ArrayList<>();
|
List<String> node = new ArrayList<>();
|
||||||
node.add(hostPortArr[0]);
|
node.add(hostPortArr[0]);
|
||||||
|
|
|
@ -15,15 +15,15 @@ class RedisClusterManager extends RedisManager {
|
||||||
private final JedisCluster cluster;
|
private final 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_RE_DIRECTIONS = 5;
|
||||||
private static final long FAILIURE_WAIT_TIME = 4000L;
|
private static final long FAILURE_WAIT_TIME = 4000L;
|
||||||
|
|
||||||
RedisClusterManager(Set<HostAndPort> nodes,
|
RedisClusterManager(Set<HostAndPort> nodes,
|
||||||
String password,
|
String password,
|
||||||
int timeout,
|
int timeout,
|
||||||
JedisPoolConfig poolConfig) {
|
JedisPoolConfig poolConfig) {
|
||||||
super(null, FAILIURE_WAIT_TIME);
|
super(null, FAILURE_WAIT_TIME);
|
||||||
this.cluster = new JedisCluster(nodes, timeout, Protocol.DEFAULT_TIMEOUT, DEFAULT_MAX_REDIRECTIONS, password, poolConfig);
|
this.cluster = new JedisCluster(nodes, timeout, Protocol.DEFAULT_TIMEOUT, DEFAULT_MAX_RE_DIRECTIONS, password, poolConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
|
@ -115,4 +115,22 @@ class RedisClusterManager extends RedisManager {
|
||||||
} while (retry && tries <= NUM_RETRIES);
|
} while (retry && tries <= NUM_RETRIES);
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public Boolean exists(String key) {
|
||||||
|
int tries = 0;
|
||||||
|
boolean retry = true;
|
||||||
|
Boolean retVal = null;
|
||||||
|
do {
|
||||||
|
tries++;
|
||||||
|
try {
|
||||||
|
retVal = this.cluster.exists(key);
|
||||||
|
retry = false;
|
||||||
|
} catch (JedisRedirectionException | JedisConnectionException ex) {
|
||||||
|
handleException(tries, ex);
|
||||||
|
}
|
||||||
|
} while (retry && tries <= NUM_RETRIES);
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import redis.clients.jedis.Jedis;
|
||||||
import redis.clients.jedis.exceptions.JedisConnectionException;
|
import redis.clients.jedis.exceptions.JedisConnectionException;
|
||||||
import redis.clients.jedis.util.Pool;
|
import redis.clients.jedis.util.Pool;
|
||||||
import tomcat.request.session.data.cache.DataCache;
|
import tomcat.request.session.data.cache.DataCache;
|
||||||
import tomcat.request.session.data.cache.DataCacheConstants;
|
|
||||||
|
|
||||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||||
abstract class RedisManager implements DataCache {
|
abstract class RedisManager implements DataCache {
|
||||||
|
@ -14,12 +13,14 @@ abstract class RedisManager implements DataCache {
|
||||||
private static final int NUM_RETRIES = 3;
|
private static final int NUM_RETRIES = 3;
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);
|
||||||
|
|
||||||
private final Pool<Jedis> pool;
|
private static final String REDIS_CONN_FAILED_RETRY_MSG = "Jedis connection failed, retrying...";
|
||||||
private final long failiureWaitTime;
|
|
||||||
|
|
||||||
RedisManager(Pool<Jedis> pool, long failiureWaitTime) {
|
private final Pool<Jedis> pool;
|
||||||
|
private final long failureWaitTime;
|
||||||
|
|
||||||
|
RedisManager(Pool<Jedis> pool, long failureWaitTime) {
|
||||||
this.pool = pool;
|
this.pool = pool;
|
||||||
this.failiureWaitTime = failiureWaitTime;
|
this.failureWaitTime = failureWaitTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
|
@ -112,6 +113,24 @@ abstract class RedisManager implements DataCache {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public Boolean exists(String key) {
|
||||||
|
int tries = 0;
|
||||||
|
boolean retry = true;
|
||||||
|
Boolean retVal = null;
|
||||||
|
do {
|
||||||
|
tries++;
|
||||||
|
try (Jedis jedis = this.pool.getResource()) {
|
||||||
|
retVal = jedis.exists(key);
|
||||||
|
retry = false;
|
||||||
|
} catch (JedisConnectionException ex) {
|
||||||
|
handleException(tries, ex);
|
||||||
|
}
|
||||||
|
} while (retry && tries <= NUM_RETRIES);
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To handle jedis exception.
|
* To handle jedis exception.
|
||||||
*
|
*
|
||||||
|
@ -119,12 +138,12 @@ abstract class RedisManager implements DataCache {
|
||||||
* @param ex - jedis exception.
|
* @param ex - jedis exception.
|
||||||
*/
|
*/
|
||||||
void handleException(int tries, RuntimeException ex) {
|
void handleException(int tries, RuntimeException ex) {
|
||||||
LOGGER.error(DataCacheConstants.REDIS_CONN_FAILED_RETRY_MSG + tries);
|
LOGGER.error(REDIS_CONN_FAILED_RETRY_MSG + tries);
|
||||||
if (tries == NUM_RETRIES) {
|
if (tries == NUM_RETRIES) {
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Thread.sleep(this.failiureWaitTime);
|
Thread.sleep(this.failureWaitTime);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import java.util.Set;
|
||||||
/** author: Ranjith Manickam @ 3 Dec' 2018 */
|
/** author: Ranjith Manickam @ 3 Dec' 2018 */
|
||||||
class RedisSentinelManager extends RedisManager {
|
class RedisSentinelManager extends RedisManager {
|
||||||
|
|
||||||
private static final long FAILIURE_WAIT_TIME = 2000L;
|
private static final long FAILURE_WAIT_TIME = 2000L;
|
||||||
|
|
||||||
RedisSentinelManager(Set<String> nodes,
|
RedisSentinelManager(Set<String> nodes,
|
||||||
String masterName,
|
String masterName,
|
||||||
|
@ -16,6 +16,6 @@ class RedisSentinelManager extends RedisManager {
|
||||||
int database,
|
int database,
|
||||||
int timeout,
|
int timeout,
|
||||||
JedisPoolConfig poolConfig) {
|
JedisPoolConfig poolConfig) {
|
||||||
super(new JedisSentinelPool(masterName, nodes, poolConfig, timeout, password, database), FAILIURE_WAIT_TIME);
|
super(new JedisSentinelPool(masterName, nodes, poolConfig, timeout, password, database), FAILURE_WAIT_TIME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import redis.clients.jedis.JedisPoolConfig;
|
||||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||||
class RedisStandardManager extends RedisManager {
|
class RedisStandardManager extends RedisManager {
|
||||||
|
|
||||||
private static final long FAILIURE_WAIT_TIME = 2000L;
|
private static final long FAILURE_WAIT_TIME = 2000L;
|
||||||
|
|
||||||
RedisStandardManager(String host,
|
RedisStandardManager(String host,
|
||||||
int port,
|
int port,
|
||||||
|
@ -14,6 +14,6 @@ class RedisStandardManager extends RedisManager {
|
||||||
int database,
|
int database,
|
||||||
int timeout,
|
int timeout,
|
||||||
JedisPoolConfig poolConfig) {
|
JedisPoolConfig poolConfig) {
|
||||||
super(new JedisPool(poolConfig, host, port, timeout, password, database), FAILIURE_WAIT_TIME);
|
super(new JedisPool(poolConfig, host, port, timeout, password, database), FAILURE_WAIT_TIME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,266 @@
|
||||||
|
package tomcat.request.session.model;
|
||||||
|
|
||||||
|
import redis.clients.jedis.Protocol;
|
||||||
|
import tomcat.request.session.annotation.Property;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import static tomcat.request.session.annotation.Property.PropertyType.BOOLEAN;
|
||||||
|
import static tomcat.request.session.annotation.Property.PropertyType.INTEGER;
|
||||||
|
|
||||||
|
/** author: Ranjith Manickam @ 5 Feb' 2020 */
|
||||||
|
public class Config implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 3480402257971437776L;
|
||||||
|
|
||||||
|
public static final String APPLICATION_PROPERTIES_FILE = "redis-data-cache.properties";
|
||||||
|
|
||||||
|
/** Redis config type. */
|
||||||
|
public enum RedisConfigType {
|
||||||
|
DEFAULT,
|
||||||
|
SENTINEL,
|
||||||
|
CLUSTER
|
||||||
|
}
|
||||||
|
|
||||||
|
@Property(name = "redis.hosts", defaultValue = "127.0.0.1:6379")
|
||||||
|
private String redisHosts;
|
||||||
|
|
||||||
|
@Property(name = "redis.cluster.enabled", type = BOOLEAN, defaultValue = "false")
|
||||||
|
private Boolean redisClusterEnabled;
|
||||||
|
|
||||||
|
@Property(name = "redis.sentinel.enabled", type = BOOLEAN, defaultValue = "false")
|
||||||
|
private Boolean redisSentinelEnabled;
|
||||||
|
|
||||||
|
@Property(name = "lb.sticky-session.enabled", type = BOOLEAN, defaultValue = "false")
|
||||||
|
private Boolean lbStickySessionEnabled;
|
||||||
|
|
||||||
|
@Property(name = "redis.max.active", type = INTEGER, defaultValue = "10")
|
||||||
|
private Integer redisMaxActive;
|
||||||
|
|
||||||
|
@Property(name = "redis.test.onBorrow", type = BOOLEAN, defaultValue = "true")
|
||||||
|
private Boolean redisTestOnBorrow;
|
||||||
|
|
||||||
|
@Property(name = "redis.test.onReturn", type = BOOLEAN, defaultValue = "true")
|
||||||
|
private Boolean redisTestOnReturn;
|
||||||
|
|
||||||
|
@Property(name = "redis.max.idle", type = INTEGER, defaultValue = "5")
|
||||||
|
private Integer redisMaxIdle;
|
||||||
|
|
||||||
|
@Property(name = "redis.min.idle", type = INTEGER, defaultValue = "1")
|
||||||
|
private Integer redisMinIdle;
|
||||||
|
|
||||||
|
@Property(name = "redis.test.whileIdle", type = BOOLEAN, defaultValue = "true")
|
||||||
|
private Boolean redisTestWhileIdle;
|
||||||
|
|
||||||
|
@Property(name = "redis.test.numPerEviction", type = INTEGER, defaultValue = "10")
|
||||||
|
private Integer redisTestNumPerEviction;
|
||||||
|
|
||||||
|
@Property(name = "redis.time.betweenEviction", type = INTEGER, defaultValue = "60000")
|
||||||
|
private Integer redisTimeBetweenEviction;
|
||||||
|
|
||||||
|
@Property(name = "redis.password")
|
||||||
|
private String redisPassword;
|
||||||
|
|
||||||
|
@Property(name = "redis.database", type = INTEGER, defaultValue = "0")
|
||||||
|
private Integer redisDatabase;
|
||||||
|
|
||||||
|
@Property(name = "redis.timeout", type = INTEGER, defaultValue = "2000")
|
||||||
|
private Integer redisTimeout;
|
||||||
|
|
||||||
|
@Property(name = "redis.sentinel.master", defaultValue = "mymaster")
|
||||||
|
private String redisSentinelMaster;
|
||||||
|
|
||||||
|
@Property(name = "redis.session.expiry.job.interval", type = INTEGER, defaultValue = "60")
|
||||||
|
private Integer redisSessionExpiryJobInterval;
|
||||||
|
|
||||||
|
@Property(name = "redis.session.data-sync.job.interval", type = INTEGER, defaultValue = "10")
|
||||||
|
private Integer redisSessionDataSyncJobInterval;
|
||||||
|
|
||||||
|
@Property(name = "session.persistent.policies", defaultValue = "DEFAULT")
|
||||||
|
private String sessionPersistentPolicies;
|
||||||
|
|
||||||
|
@Property(name = "redis.sso.timeout", type = INTEGER, defaultValue = "0")
|
||||||
|
private Integer redisSSOTimeout;
|
||||||
|
|
||||||
|
public Config() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Config(String redisHosts,
|
||||||
|
Boolean redisClusterEnabled,
|
||||||
|
Boolean redisSentinelEnabled,
|
||||||
|
Boolean lbStickySessionEnabled,
|
||||||
|
Integer redisMaxActive,
|
||||||
|
Boolean redisTestOnBorrow,
|
||||||
|
Boolean redisTestOnReturn,
|
||||||
|
Integer redisMaxIdle,
|
||||||
|
Integer redisMinIdle,
|
||||||
|
Boolean redisTestWhileIdle,
|
||||||
|
Integer redisTestNumPerEviction,
|
||||||
|
Integer redisTimeBetweenEviction,
|
||||||
|
String redisPassword,
|
||||||
|
Integer redisDatabase,
|
||||||
|
Integer redisTimeout,
|
||||||
|
String redisSentinelMaster,
|
||||||
|
Integer redisSessionExpiryJobInterval,
|
||||||
|
Integer redisSessionDataSyncJobInterval,
|
||||||
|
String sessionPersistentPolicies,
|
||||||
|
Integer redisSSOTimeout) {
|
||||||
|
this.redisHosts = redisHosts;
|
||||||
|
this.redisClusterEnabled = redisClusterEnabled;
|
||||||
|
this.redisSentinelEnabled = redisSentinelEnabled;
|
||||||
|
this.lbStickySessionEnabled = lbStickySessionEnabled;
|
||||||
|
this.redisMaxActive = redisMaxActive;
|
||||||
|
this.redisTestOnBorrow = redisTestOnBorrow;
|
||||||
|
this.redisTestOnReturn = redisTestOnReturn;
|
||||||
|
this.redisMaxIdle = redisMaxIdle;
|
||||||
|
this.redisMinIdle = redisMinIdle;
|
||||||
|
this.redisTestWhileIdle = redisTestWhileIdle;
|
||||||
|
this.redisTestNumPerEviction = redisTestNumPerEviction;
|
||||||
|
this.redisTimeBetweenEviction = redisTimeBetweenEviction;
|
||||||
|
this.redisPassword = redisPassword;
|
||||||
|
this.redisDatabase = redisDatabase;
|
||||||
|
this.redisTimeout = redisTimeout;
|
||||||
|
this.redisSentinelMaster = redisSentinelMaster;
|
||||||
|
this.redisSessionExpiryJobInterval = redisSessionExpiryJobInterval;
|
||||||
|
this.redisSessionDataSyncJobInterval = redisSessionDataSyncJobInterval;
|
||||||
|
this.sessionPersistentPolicies = sessionPersistentPolicies;
|
||||||
|
this.redisSSOTimeout = redisSSOTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.hosts' value. */
|
||||||
|
public String getRedisHosts() {
|
||||||
|
return redisHosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.cluster.enabled' value. */
|
||||||
|
public Boolean getRedisClusterEnabled() {
|
||||||
|
return redisClusterEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.sentinel.enabled' value. */
|
||||||
|
public Boolean getRedisSentinelEnabled() {
|
||||||
|
return redisSentinelEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'lb.sticky-session.enabled' value. */
|
||||||
|
public Boolean getLbStickySessionEnabled() {
|
||||||
|
return lbStickySessionEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.max.active' value. */
|
||||||
|
public Integer getRedisMaxActive() {
|
||||||
|
return redisMaxActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.test.onBorrow' value. */
|
||||||
|
public Boolean getRedisTestOnBorrow() {
|
||||||
|
return redisTestOnBorrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.test.onReturn' value. */
|
||||||
|
public Boolean getRedisTestOnReturn() {
|
||||||
|
return redisTestOnReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.max.idle' value. */
|
||||||
|
public Integer getRedisMaxIdle() {
|
||||||
|
return redisMaxIdle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.min.idle' value. */
|
||||||
|
public Integer getRedisMinIdle() {
|
||||||
|
return redisMinIdle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.test.whileIdle' value. */
|
||||||
|
public Boolean getRedisTestWhileIdle() {
|
||||||
|
return redisTestWhileIdle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.test.numPerEviction' value. */
|
||||||
|
public Integer getRedisTestNumPerEviction() {
|
||||||
|
return redisTestNumPerEviction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.time.betweenEviction' value. */
|
||||||
|
public Integer getRedisTimeBetweenEviction() {
|
||||||
|
return redisTimeBetweenEviction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.password' value. */
|
||||||
|
public String getRedisPassword() {
|
||||||
|
return (redisPassword == null || redisPassword.isEmpty()) ? null : redisPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.database' value. */
|
||||||
|
public Integer getRedisDatabase() {
|
||||||
|
return redisDatabase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.timeout' value. */
|
||||||
|
public Integer getRedisTimeout() {
|
||||||
|
return Math.max(redisTimeout, Protocol.DEFAULT_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.sentinel.master' value. */
|
||||||
|
public String getRedisSentinelMaster() {
|
||||||
|
return redisSentinelMaster;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.session.expiry.job.interval' value. */
|
||||||
|
public Integer getRedisSessionExpiryJobInterval() {
|
||||||
|
return redisSessionExpiryJobInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.session.data-sync.job.interval' value. */
|
||||||
|
public Integer getRedisSessionDataSyncJobInterval() {
|
||||||
|
return redisSessionDataSyncJobInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'session.persistent.policies' value */
|
||||||
|
public String getSessionPersistentPolicies() {
|
||||||
|
return sessionPersistentPolicies;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get 'redis.sso.timeout' value */
|
||||||
|
public Integer getRedisSSOTimeout() {
|
||||||
|
return redisSSOTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Config{" +
|
||||||
|
"redisHosts='" + redisHosts + '\'' +
|
||||||
|
", redisClusterEnabled=" + redisClusterEnabled +
|
||||||
|
", redisSentinelEnabled=" + redisSentinelEnabled +
|
||||||
|
", lbStickySessionEnabled=" + lbStickySessionEnabled +
|
||||||
|
", redisMaxActive=" + redisMaxActive +
|
||||||
|
", redisTestOnBorrow=" + redisTestOnBorrow +
|
||||||
|
", redisTestOnReturn=" + redisTestOnReturn +
|
||||||
|
", redisMaxIdle=" + redisMaxIdle +
|
||||||
|
", redisMinIdle=" + redisMinIdle +
|
||||||
|
", redisTestWhileIdle=" + redisTestWhileIdle +
|
||||||
|
", redisTestNumPerEviction=" + redisTestNumPerEviction +
|
||||||
|
", redisTimeBetweenEviction=" + redisTimeBetweenEviction +
|
||||||
|
", redisPassword='" + redisPassword + '\'' +
|
||||||
|
", redisDatabase=" + redisDatabase +
|
||||||
|
", redisTimeout=" + redisTimeout +
|
||||||
|
", redisSentinelMaster='" + redisSentinelMaster + '\'' +
|
||||||
|
", redisSessionExpiryJobInterval=" + redisSessionExpiryJobInterval +
|
||||||
|
", redisSessionDataSyncJobInterval=" + redisSessionDataSyncJobInterval +
|
||||||
|
", sessionPersistentPolicies='" + sessionPersistentPolicies + '\'' +
|
||||||
|
", redisSSOTimeout='" + redisSSOTimeout + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get redis config type. */
|
||||||
|
public RedisConfigType getRedisConfigType() {
|
||||||
|
if (this.getRedisClusterEnabled()) {
|
||||||
|
return RedisConfigType.CLUSTER;
|
||||||
|
} else if (this.getRedisSentinelEnabled()) {
|
||||||
|
return RedisConfigType.SENTINEL;
|
||||||
|
}
|
||||||
|
return RedisConfigType.DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,8 @@
|
||||||
package tomcat.request.session;
|
package tomcat.request.session.model;
|
||||||
|
|
||||||
|
import org.apache.catalina.Manager;
|
||||||
|
import org.apache.catalina.session.StandardSession;
|
||||||
|
import tomcat.request.session.redis.SessionManager;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
||||||
|
@ -8,11 +12,6 @@ import java.util.Enumeration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.catalina.Manager;
|
|
||||||
import org.apache.catalina.session.StandardSession;
|
|
||||||
|
|
||||||
import tomcat.request.session.redis.SessionManager;
|
|
||||||
|
|
||||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||||
public class Session extends StandardSession {
|
public class Session extends StandardSession {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package tomcat.request.session;
|
package tomcat.request.session.model;
|
||||||
|
|
||||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||||
public class SessionContext {
|
public class SessionContext {
|
|
@ -1,4 +1,4 @@
|
||||||
package tomcat.request.session;
|
package tomcat.request.session.model;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInputStream;
|
import java.io.ObjectInputStream;
|
|
@ -0,0 +1,100 @@
|
||||||
|
package tomcat.request.session.model;
|
||||||
|
|
||||||
|
import org.apache.catalina.Session;
|
||||||
|
import org.apache.catalina.authenticator.SingleSignOnListener;
|
||||||
|
import org.apache.catalina.authenticator.SingleSignOnSessionKey;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
/** author: Ranjith Manickam @ 20 Mar' 2020 */
|
||||||
|
public class SingleSignOnEntry implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 4590485271396917062L;
|
||||||
|
|
||||||
|
private String authType;
|
||||||
|
private String password;
|
||||||
|
private Principal principal;
|
||||||
|
private String username;
|
||||||
|
private boolean canReauthenticate = false;
|
||||||
|
private final ConcurrentMap<SingleSignOnSessionKey, SingleSignOnSessionKey> sessionKeys;
|
||||||
|
|
||||||
|
public SingleSignOnEntry() {
|
||||||
|
this.sessionKeys = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleSignOnEntry(Principal principal, String authType, String username, String password) {
|
||||||
|
this.sessionKeys = new ConcurrentHashMap<>();
|
||||||
|
this.updateCredentials(principal, authType, username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSession(String ssoId, Session session) {
|
||||||
|
SingleSignOnSessionKey key = new SingleSignOnSessionKey(session);
|
||||||
|
SingleSignOnSessionKey currentKey = this.sessionKeys.putIfAbsent(key, key);
|
||||||
|
if (currentKey == null) {
|
||||||
|
session.addSessionListener(new SingleSignOnListener(ssoId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeSession(Session session) {
|
||||||
|
SingleSignOnSessionKey key = new SingleSignOnSessionKey(session);
|
||||||
|
this.sessionKeys.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<SingleSignOnSessionKey> findSessions() {
|
||||||
|
return this.sessionKeys.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthType() {
|
||||||
|
return this.authType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getCanReauthenticate() {
|
||||||
|
return this.canReauthenticate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return this.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Principal getPrincipal() {
|
||||||
|
return this.principal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return this.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void updateCredentials(Principal principal, String authType, String username, String password) {
|
||||||
|
this.principal = principal;
|
||||||
|
this.authType = authType;
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.canReauthenticate = "BASIC".equals(authType) || "FORM".equals(authType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeObjectData(ObjectOutputStream out) throws IOException {
|
||||||
|
try (ObjectOutputStream outputStream = new ObjectOutputStream(out)) {
|
||||||
|
outputStream.writeObject(this);
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readObjectData(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||||
|
try (ObjectInputStream inputStream = new ObjectInputStream(in)) {
|
||||||
|
SingleSignOnEntry entry = (SingleSignOnEntry) inputStream.readObject();
|
||||||
|
this.authType = entry.authType;
|
||||||
|
this.password = entry.password;
|
||||||
|
this.principal = entry.principal;
|
||||||
|
this.username = entry.username;
|
||||||
|
this.canReauthenticate = entry.canReauthenticate;
|
||||||
|
this.sessionKeys.putAll(entry.sessionKeys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,7 +31,9 @@ public class SessionHandlerValve extends ValveBase {
|
||||||
LOGGER.error("Error processing request", ex);
|
LOGGER.error("Error processing request", ex);
|
||||||
throw new BackendException();
|
throw new BackendException();
|
||||||
} finally {
|
} finally {
|
||||||
manager.afterRequest();
|
if (this.manager != null) {
|
||||||
|
this.manager.afterRequest();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,14 +9,17 @@ import org.apache.catalina.Valve;
|
||||||
import org.apache.catalina.session.ManagerBase;
|
import org.apache.catalina.session.ManagerBase;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import tomcat.request.session.SerializationUtil;
|
import tomcat.request.session.constant.SessionConstants;
|
||||||
import tomcat.request.session.Session;
|
import tomcat.request.session.constant.SessionConstants.SessionPolicy;
|
||||||
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.DataCache;
|
||||||
import tomcat.request.session.data.cache.DataCacheFactory;
|
import tomcat.request.session.data.cache.DataCacheFactory;
|
||||||
|
import tomcat.request.session.model.Config;
|
||||||
|
import tomcat.request.session.model.Session;
|
||||||
|
import tomcat.request.session.model.SessionContext;
|
||||||
|
import tomcat.request.session.model.SessionMetadata;
|
||||||
|
import tomcat.request.session.model.SingleSignOnEntry;
|
||||||
|
import tomcat.request.session.util.ConfigUtil;
|
||||||
|
import tomcat.request.session.util.SerializationUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
@ -27,18 +30,22 @@ import java.util.Set;
|
||||||
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
/** author: Ranjith Manickam @ 12 Jul' 2018 */
|
||||||
public class SessionManager extends ManagerBase implements Lifecycle {
|
public class SessionManager extends ManagerBase implements Lifecycle {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(SessionManager.class);
|
||||||
|
|
||||||
|
private Integer ssoTimeout;
|
||||||
private DataCache dataCache;
|
private DataCache dataCache;
|
||||||
private SerializationUtil serializer;
|
private SerializationUtil serializer;
|
||||||
|
private final ThreadLocal<SessionContext> sessionContext = new ThreadLocal<>();
|
||||||
private ThreadLocal<SessionContext> sessionContext = new ThreadLocal<>();
|
private final Set<SessionPolicy> sessionPolicy = EnumSet.of(SessionPolicy.DEFAULT);
|
||||||
private Set<SessionPolicy> sessionPolicy = EnumSet.of(SessionPolicy.DEFAULT);
|
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(SessionManager.class);
|
|
||||||
|
|
||||||
public boolean getSaveOnChange() {
|
public boolean getSaveOnChange() {
|
||||||
return this.sessionPolicy.contains(SessionPolicy.SAVE_ON_CHANGE);
|
return this.sessionPolicy.contains(SessionPolicy.SAVE_ON_CHANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean getAlwaysSaveAfterRequest() {
|
||||||
|
return this.sessionPolicy.contains(SessionPolicy.ALWAYS_SAVE_AFTER_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public void addLifecycleListener(LifecycleListener listener) {
|
public void addLifecycleListener(LifecycleListener listener) {
|
||||||
|
@ -203,11 +210,16 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
||||||
/** To initialize the session manager. */
|
/** To initialize the session manager. */
|
||||||
private void initialize() {
|
private void initialize() {
|
||||||
try {
|
try {
|
||||||
this.dataCache = new DataCacheFactory(getSessionTimeout(null)).getDataCache();
|
Config config = ConfigUtil.getConfig();
|
||||||
|
this.ssoTimeout = config.getRedisSSOTimeout();
|
||||||
|
this.dataCache = new DataCacheFactory(config, getSessionTimeout(null)).getDataCache();
|
||||||
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 = (context != null && context.getLoader() != null) ? context.getLoader().getClassLoader() : null;
|
||||||
this.serializer.setClassLoader(loader);
|
this.serializer.setClassLoader(loader);
|
||||||
|
|
||||||
|
setSessionPersistentPolicies(config);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LOGGER.error("Error occurred while initializing the session manager..", ex);
|
LOGGER.error("Error occurred while initializing the session manager..", ex);
|
||||||
throw ex;
|
throw ex;
|
||||||
|
@ -252,7 +264,7 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
||||||
session = (this.sessionContext.get() != null) ? this.sessionContext.get().getSession() : null;
|
session = (this.sessionContext.get() != null) ? this.sessionContext.get().getSession() : null;
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
if (session.isValid()) {
|
if (session.isValid()) {
|
||||||
save(session, this.sessionPolicy.contains(SessionPolicy.ALWAYS_SAVE_AFTER_REQUEST));
|
save(session, getAlwaysSaveAfterRequest());
|
||||||
} else {
|
} else {
|
||||||
remove(session);
|
remove(session);
|
||||||
}
|
}
|
||||||
|
@ -270,7 +282,7 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
||||||
private int getSessionTimeout(Session session) {
|
private int getSessionTimeout(Session session) {
|
||||||
int timeout = getContextIns().getSessionTimeout() * 60;
|
int timeout = getContextIns().getSessionTimeout() * 60;
|
||||||
int sessionTimeout = (session == null) ? 0 : session.getMaxInactiveInterval();
|
int sessionTimeout = (session == null) ? 0 : session.getMaxInactiveInterval();
|
||||||
return (sessionTimeout < timeout) ? ((timeout < 1800) ? 1800 : timeout) : sessionTimeout;
|
return (sessionTimeout < timeout) ? (Math.max(timeout, 1800)) : sessionTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** To set values to session context. */
|
/** To set values to session context. */
|
||||||
|
@ -312,4 +324,61 @@ public class SessionManager extends ManagerBase implements Lifecycle {
|
||||||
}
|
}
|
||||||
throw new RuntimeException("Error occurred while creating container instance");
|
throw new RuntimeException("Error occurred while creating container instance");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** To set session persistent policies */
|
||||||
|
private void setSessionPersistentPolicies(Config config) {
|
||||||
|
String sessionPolicies = config.getSessionPersistentPolicies();
|
||||||
|
if (sessionPolicies == null || sessionPolicies.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionPolicies = sessionPolicies.replaceAll("\\s", "");
|
||||||
|
String[] sessionPolicyNames = sessionPolicies.split(",");
|
||||||
|
for (String sessionPolicyName : sessionPolicyNames) {
|
||||||
|
this.sessionPolicy.add(SessionPolicy.fromName(sessionPolicyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To set single-sign-on entry to cache. */
|
||||||
|
void setSingleSignOnEntry(String ssoId, SingleSignOnEntry entry) {
|
||||||
|
if (entry == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
byte[] data = this.serializer.serializeSingleSignOnEntry(entry);
|
||||||
|
this.dataCache.set(ssoId, data);
|
||||||
|
if (this.ssoTimeout > 0) {
|
||||||
|
this.dataCache.expire(ssoId, this.ssoTimeout);
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOGGER.error("Error occurred while serializing the single-sign-on entry..", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get single-sign-on entry from cache. */
|
||||||
|
SingleSignOnEntry getSingleSignOnEntry(String ssoId) {
|
||||||
|
byte[] data = this.dataCache.get(ssoId);
|
||||||
|
if (data == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleSignOnEntry entry = new SingleSignOnEntry();
|
||||||
|
try {
|
||||||
|
this.serializer.deserializeSingleSignOnEntry(data, entry);
|
||||||
|
} catch (IOException | ClassNotFoundException ex) {
|
||||||
|
LOGGER.error("Error occurred while de-serializing the single-sign-on entry..", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To check single-sign-on entry exists from cache. */
|
||||||
|
Boolean singleSignOnEntryExists(String ssoId) {
|
||||||
|
return this.dataCache.exists(ssoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To delete single-sign-on entry from cache. */
|
||||||
|
void deleteSingleSignOnEntry(String ssoId) {
|
||||||
|
this.dataCache.delete(ssoId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,263 @@
|
||||||
|
package tomcat.request.session.redis;
|
||||||
|
|
||||||
|
import org.apache.catalina.Container;
|
||||||
|
import org.apache.catalina.Context;
|
||||||
|
import org.apache.catalina.Engine;
|
||||||
|
import org.apache.catalina.LifecycleException;
|
||||||
|
import org.apache.catalina.Manager;
|
||||||
|
import org.apache.catalina.Realm;
|
||||||
|
import org.apache.catalina.Session;
|
||||||
|
import org.apache.catalina.authenticator.Constants;
|
||||||
|
import org.apache.catalina.authenticator.SingleSignOn;
|
||||||
|
import org.apache.catalina.authenticator.SingleSignOnSessionKey;
|
||||||
|
import org.apache.catalina.connector.Request;
|
||||||
|
import org.apache.catalina.connector.Response;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import tomcat.request.session.exception.BackendException;
|
||||||
|
import tomcat.request.session.model.SingleSignOnEntry;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/** author: Ranjith Manickam @ 20 Mar' 2020 */
|
||||||
|
public class SingleSignOnValve extends SingleSignOn {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(SingleSignOnValve.class);
|
||||||
|
|
||||||
|
private Engine engine;
|
||||||
|
private SessionManager manager;
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
protected synchronized void startInternal() throws LifecycleException {
|
||||||
|
Container c;
|
||||||
|
for (c = this.getContainer(); c != null && !(c instanceof Engine); c = c.getParent()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c instanceof Engine) {
|
||||||
|
this.engine = (Engine) c;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.startInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void invoke(Request request, Response response) throws BackendException {
|
||||||
|
try {
|
||||||
|
this.setSessionManager(request.getContext().getManager());
|
||||||
|
|
||||||
|
request.removeNote("org.apache.catalina.request.SSOID");
|
||||||
|
LOGGER.debug("singleSignOn.debug.invoke, requestURI: {}", request.getRequestURI());
|
||||||
|
|
||||||
|
if (request.getUserPrincipal() == null) {
|
||||||
|
LOGGER.debug("singleSignOn.debug.cookieCheck");
|
||||||
|
Cookie cookie = null;
|
||||||
|
Cookie[] cookies = request.getCookies();
|
||||||
|
if (cookies != null) {
|
||||||
|
for (Cookie value : cookies) {
|
||||||
|
if (Constants.SINGLE_SIGN_ON_COOKIE.equals(value.getName())) {
|
||||||
|
cookie = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cookie == null) {
|
||||||
|
LOGGER.debug("singleSignOn.debug.cookieNotFound");
|
||||||
|
} else {
|
||||||
|
LOGGER.debug("singleSignOn.debug.principalCheck, ssoId: {}", cookie.getValue());
|
||||||
|
|
||||||
|
SingleSignOnEntry entry = this.manager.getSingleSignOnEntry(cookie.getValue());
|
||||||
|
if (entry == null) {
|
||||||
|
LOGGER.debug("singleSignOn.debug.principalNotFound, ssoId: {}", cookie.getValue());
|
||||||
|
cookie.setValue("REMOVE");
|
||||||
|
cookie.setMaxAge(0);
|
||||||
|
cookie.setPath("/");
|
||||||
|
String domain = this.getCookieDomain();
|
||||||
|
if (domain != null) {
|
||||||
|
cookie.setDomain(domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie.setSecure(request.isSecure());
|
||||||
|
if (request.getServletContext().getSessionCookieConfig().isHttpOnly() || request.getContext().getUseHttpOnly()) {
|
||||||
|
cookie.setHttpOnly(true);
|
||||||
|
}
|
||||||
|
response.addCookie(cookie);
|
||||||
|
} else {
|
||||||
|
LOGGER.debug("singleSignOn.debug.principalFound, principal: {}, authType: {}", (entry.getPrincipal() != null ? entry.getPrincipal().getName() : ""), entry.getAuthType());
|
||||||
|
request.setNote("org.apache.catalina.request.SSOID", cookie.getValue());
|
||||||
|
if (!this.getRequireReauthentication()) {
|
||||||
|
request.setAuthType(entry.getAuthType());
|
||||||
|
request.setUserPrincipal(entry.getPrincipal());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOGGER.debug("singleSignOn.debug.hasPrincipal, principal: {}", request.getUserPrincipal().getName());
|
||||||
|
}
|
||||||
|
this.getNext().invoke(request, response);
|
||||||
|
|
||||||
|
} catch (IOException | ServletException | RuntimeException ex) {
|
||||||
|
LOGGER.error("Error processing request", ex);
|
||||||
|
throw new BackendException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
public void sessionDestroyed(String ssoId, Session session) {
|
||||||
|
if (this.getState().isAvailable()) {
|
||||||
|
if ((session.getMaxInactiveInterval() <= 0 ||
|
||||||
|
session.getIdleTimeInternal() < (long) (session.getMaxInactiveInterval() * 1000))
|
||||||
|
&& session.getManager().getContext().getState().isAvailable()) {
|
||||||
|
|
||||||
|
LOGGER.debug("singleSignOn.debug.sessionLogout, session: {}", session);
|
||||||
|
this.removeSession(ssoId, session);
|
||||||
|
if (this.manager.singleSignOnEntryExists(ssoId)) {
|
||||||
|
this.deregister(ssoId);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debug("singleSignOn.debug.sessionTimeout, ssoId: {}, session: {}", ssoId, session);
|
||||||
|
this.removeSession(ssoId, session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
protected boolean associate(String ssoId, Session session) {
|
||||||
|
SingleSignOnEntry entry = this.manager.getSingleSignOnEntry(ssoId);
|
||||||
|
if (entry == null) {
|
||||||
|
LOGGER.debug("singleSignOn.debug.associateFail, ssoId: {}, session: {}", ssoId, session);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debug("singleSignOn.debug.associate, ssoId: {}, session: {}", ssoId, session);
|
||||||
|
entry.addSession(ssoId, session);
|
||||||
|
this.manager.setSingleSignOnEntry(ssoId, entry);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
protected void deregister(String ssoId) {
|
||||||
|
SingleSignOnEntry entry = this.manager.getSingleSignOnEntry(ssoId);
|
||||||
|
this.manager.deleteSingleSignOnEntry(ssoId);
|
||||||
|
if (entry == null) {
|
||||||
|
LOGGER.debug("singleSignOn.debug.deregisterFail, ssoId: {}", ssoId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<SingleSignOnSessionKey> ssoKeys = entry.findSessions();
|
||||||
|
if (ssoKeys.isEmpty()) {
|
||||||
|
LOGGER.debug("singleSignOn.debug.deregisterNone, ssoId: {}", ssoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SingleSignOnSessionKey ssoKey : ssoKeys) {
|
||||||
|
this.expire(ssoKey);
|
||||||
|
LOGGER.debug("singleSignOn.debug.deregister, ssoKey: {}, ssoId: {}", ssoKey, ssoId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
protected boolean reauthenticate(String ssoId, Realm realm, Request request) {
|
||||||
|
if (ssoId == null || realm == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean reAuthenticated = false;
|
||||||
|
SingleSignOnEntry entry = this.manager.getSingleSignOnEntry(ssoId);
|
||||||
|
if (entry != null && entry.getCanReauthenticate()) {
|
||||||
|
String username = entry.getUsername();
|
||||||
|
if (username != null) {
|
||||||
|
Principal reAuthPrincipal = realm.authenticate(username, entry.getPassword());
|
||||||
|
if (reAuthPrincipal != null) {
|
||||||
|
reAuthenticated = true;
|
||||||
|
request.setAuthType(entry.getAuthType());
|
||||||
|
request.setUserPrincipal(reAuthPrincipal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reAuthenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
protected void register(String ssoId, Principal principal, String authType, String username, String password) {
|
||||||
|
LOGGER.debug("singleSignOn.debug.register, ssoId: {}, principal: {}, authType: {}", ssoId, (principal != null ? principal.getName() : ""), authType);
|
||||||
|
SingleSignOnEntry entry = new SingleSignOnEntry(principal, authType, username, password);
|
||||||
|
this.manager.setSingleSignOnEntry(ssoId, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
protected boolean update(String ssoId, Principal principal, String authType, String username, String password) {
|
||||||
|
SingleSignOnEntry entry = this.manager.getSingleSignOnEntry(ssoId);
|
||||||
|
if (entry == null || !entry.getCanReauthenticate()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debug("singleSignOn.debug.update, ssoId: {}, authType: {}", ssoId, authType);
|
||||||
|
entry.updateCredentials(principal, authType, username, password);
|
||||||
|
this.manager.setSingleSignOnEntry(ssoId, entry);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
@Override
|
||||||
|
protected void removeSession(String ssoId, Session session) {
|
||||||
|
LOGGER.debug("singleSignOn.debug.removeSession, ssoId: {}, session: {}", ssoId, session);
|
||||||
|
SingleSignOnEntry entry = this.manager.getSingleSignOnEntry(ssoId);
|
||||||
|
if (entry != null) {
|
||||||
|
entry.removeSession(session);
|
||||||
|
if (entry.findSessions().size() == 0) {
|
||||||
|
this.deregister(ssoId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To set session manager. */
|
||||||
|
void setSessionManager(Manager manager) {
|
||||||
|
if (manager != null) {
|
||||||
|
this.manager = (SessionManager) manager;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To expire session. */
|
||||||
|
private void expire(SingleSignOnSessionKey key) {
|
||||||
|
if (this.engine == null) {
|
||||||
|
LOGGER.warn("singleSignOn.sessionExpire.engineNull, key: {}", key);
|
||||||
|
} else {
|
||||||
|
Container host = this.engine.findChild(key.getHostName());
|
||||||
|
if (host == null) {
|
||||||
|
LOGGER.warn("singleSignOn.sessionExpire.hostNotFound, key: {}", key);
|
||||||
|
} else {
|
||||||
|
Context context = (Context) host.findChild(key.getContextName());
|
||||||
|
if (context == null) {
|
||||||
|
LOGGER.warn("singleSignOn.sessionExpire.contextNotFound, key: {}", key);
|
||||||
|
} else {
|
||||||
|
Session session;
|
||||||
|
try {
|
||||||
|
session = this.manager.findSession(key.getSessionId());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOGGER.warn("singleSignOn.sessionExpire.managerError, key: {}, exception: {}", key, ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session == null) {
|
||||||
|
LOGGER.warn("singleSignOn.sessionExpire.sessionNotFound, key: {}", key);
|
||||||
|
} else {
|
||||||
|
session.expire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
package tomcat.request.session.util;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import tomcat.request.session.annotation.Property;
|
||||||
|
import tomcat.request.session.model.Config;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/** author: Ranjith Manickam @ 5 Feb' 2020 */
|
||||||
|
public class ConfigUtil {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigUtil.class);
|
||||||
|
|
||||||
|
private static final String CONF = "conf";
|
||||||
|
private static final String CATALINA_BASE = "catalina.base";
|
||||||
|
|
||||||
|
/** To get application config. */
|
||||||
|
public static Config getConfig() {
|
||||||
|
Properties properties = getApplicationProperties();
|
||||||
|
Config config = new Config();
|
||||||
|
|
||||||
|
for (Field field : Config.class.getDeclaredFields()) {
|
||||||
|
Property property = field.getAnnotation(Property.class);
|
||||||
|
if (property == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String propertyName = property.name();
|
||||||
|
Property.PropertyType propertyType = property.type();
|
||||||
|
|
||||||
|
if (propertyName.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String value = properties.getProperty(propertyName);
|
||||||
|
if (isSystemProperty(value)) {
|
||||||
|
value = getSystemProperty(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
value = property.defaultValue();
|
||||||
|
|
||||||
|
if (value.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
field.setAccessible(true);
|
||||||
|
try {
|
||||||
|
switch (propertyType) {
|
||||||
|
case BOOLEAN:
|
||||||
|
field.set(config, Boolean.parseBoolean(value));
|
||||||
|
break;
|
||||||
|
case INTEGER:
|
||||||
|
field.set(config, Integer.parseInt(value));
|
||||||
|
break;
|
||||||
|
case LONG:
|
||||||
|
field.set(config, Long.parseLong(value));
|
||||||
|
break;
|
||||||
|
case STRING:
|
||||||
|
default:
|
||||||
|
field.set(config, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.error("Error while initializing application properties", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To get redis data cache properties. */
|
||||||
|
private static Properties getApplicationProperties() {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
try {
|
||||||
|
String filePath = System.getProperty(CATALINA_BASE)
|
||||||
|
.concat(File.separator)
|
||||||
|
.concat(CONF).concat(File.separator)
|
||||||
|
.concat(Config.APPLICATION_PROPERTIES_FILE);
|
||||||
|
|
||||||
|
InputStream resourceStream = null;
|
||||||
|
try {
|
||||||
|
resourceStream = (!filePath.isEmpty() && new File(filePath).exists()) ? new FileInputStream(filePath) : null;
|
||||||
|
if (resourceStream == null) {
|
||||||
|
LOGGER.info("Initializing tomcat redis session manager with default properties");
|
||||||
|
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||||
|
resourceStream = loader.getResourceAsStream(Config.APPLICATION_PROPERTIES_FILE);
|
||||||
|
}
|
||||||
|
properties.load(resourceStream);
|
||||||
|
} finally {
|
||||||
|
if (resourceStream != null) {
|
||||||
|
resourceStream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
LOGGER.error("Error while retrieving application properties", ex);
|
||||||
|
}
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To get property with the specified key from system property.
|
||||||
|
*
|
||||||
|
* @param key - search key.
|
||||||
|
* @return - Returns the system property value.
|
||||||
|
*/
|
||||||
|
private static String getSystemProperty(String key) {
|
||||||
|
int fromIndex = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
int beginIndex = key.indexOf("${", fromIndex);
|
||||||
|
int endIndex = key.indexOf("}", fromIndex);
|
||||||
|
|
||||||
|
if (beginIndex < 0 || endIndex < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
String expression = key.substring(beginIndex + 2, endIndex);
|
||||||
|
String value = System.getProperty(expression);
|
||||||
|
|
||||||
|
if (value == null || value.isEmpty()) {
|
||||||
|
fromIndex = endIndex + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
key = key.replace(String.format("${%s}", expression), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To check if the value is from system property.
|
||||||
|
*
|
||||||
|
* @param key - search key.
|
||||||
|
* @return - Returns true if the key start with '${' and ends with '}'.
|
||||||
|
*/
|
||||||
|
private static boolean isSystemProperty(String key) {
|
||||||
|
if (key == null || key.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int beginIndex = key.indexOf("${");
|
||||||
|
int endIndex = key.indexOf("}");
|
||||||
|
return beginIndex >= 0 && endIndex >= 0 && beginIndex < endIndex;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
package tomcat.request.session;
|
package tomcat.request.session.util;
|
||||||
|
|
||||||
import org.apache.catalina.util.CustomObjectInputStream;
|
import org.apache.catalina.util.CustomObjectInputStream;
|
||||||
|
import tomcat.request.session.model.Session;
|
||||||
|
import tomcat.request.session.model.SessionMetadata;
|
||||||
|
import tomcat.request.session.model.SingleSignOnEntry;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
|
@ -74,4 +77,25 @@ public class SerializationUtil {
|
||||||
session.readObjectData(ois);
|
session.readObjectData(ois);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** To serialize single-sign-on entry. */
|
||||||
|
public byte[] serializeSingleSignOnEntry(SingleSignOnEntry entry) throws IOException {
|
||||||
|
byte[] serialized;
|
||||||
|
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(bos))) {
|
||||||
|
entry.writeObjectData(oos);
|
||||||
|
oos.flush();
|
||||||
|
serialized = bos.toByteArray();
|
||||||
|
}
|
||||||
|
return serialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To de-serialize single-sign-on entry. */
|
||||||
|
public void deserializeSingleSignOnEntry(byte[] data, SingleSignOnEntry entry)
|
||||||
|
throws IOException, ClassNotFoundException {
|
||||||
|
try (BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(data));
|
||||||
|
ObjectInputStream ois = new CustomObjectInputStream(bis, this.loader)) {
|
||||||
|
entry.readObjectData(ois);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Binary file not shown.
After Width: | Height: | Size: 141 KiB |
|
@ -0,0 +1,33 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="download_summary"></div>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||||
|
<script>
|
||||||
|
$( document ).ready(function() {
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
dataType: "jsonp",
|
||||||
|
url: "https://api.github.com/repos/ran-jit/tomcat-cluster-redis-session-manager/releases",
|
||||||
|
success: function(data){
|
||||||
|
let content = "<table border='1px' style='width: 80%;margin-left: 10%;margin-right: 10%;line-height: 1.5;'><tr><th style='width: 30%;'>tag</th><th>asset name & download count</th></tr>";
|
||||||
|
for(let i=0; i< data.data.length; i++) {
|
||||||
|
const tag = data.data[i];
|
||||||
|
for(var j=0; j< tag.assets.length; j++) {
|
||||||
|
const asset = tag.assets[j];
|
||||||
|
|
||||||
|
content = content + "<tr><td style='width: 10%;'>"+ tag.tag_name + "</td>";
|
||||||
|
content = content + "<td>" + asset.name + "<div style='text-align:right;'>" + asset.download_count + "</div></td></tr>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content = content + "</table>";
|
||||||
|
document.getElementById("download_summary").innerHTML=content;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,23 +1,25 @@
|
||||||
#-- Redis data-cache configuration
|
#-- Redis data-cache configuration
|
||||||
|
|
||||||
#- redis hosts ex: 127.0.0.1:6379, 127.0.0.2:6379, 127.0.0.2:6380, ....
|
# - ${ENV_VARIABLE_NAME}
|
||||||
|
|
||||||
|
#- 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.hosts=127.0.0.1:6379
|
||||||
|
|
||||||
#- redis password
|
#- redis password.
|
||||||
#redis.password=
|
#redis.password=
|
||||||
|
|
||||||
#- set true to enable redis cluster mode (default value: false)
|
#- set true to enable redis cluster mode. (default value: false)
|
||||||
redis.cluster.enabled=false
|
redis.cluster.enabled=false
|
||||||
|
|
||||||
#- set true to enable redis sentinel mode (default value: false)
|
#- set true to enable redis sentinel mode. (default value: false)
|
||||||
redis.sentinel.enabled=false
|
redis.sentinel.enabled=false
|
||||||
# redis sentinel master name (default value: mymaster)
|
# redis sentinel master name. (default value: mymaster)
|
||||||
redis.sentinel.master=mymaster
|
redis.sentinel.master=mymaster
|
||||||
|
|
||||||
#- redis database (default value: 0)
|
#- redis database. (default value: 0)
|
||||||
#redis.database=0
|
#redis.database=0
|
||||||
|
|
||||||
#- redis connection timeout (default value: 2000 ms)
|
#- redis connection timeout. (default value: 2000 ms)
|
||||||
#redis.timeout=2000
|
#redis.timeout=2000
|
||||||
|
|
||||||
#- enable redis and standard session mode. (default value: false)
|
#- enable redis and standard session mode. (default value: false)
|
||||||
|
@ -26,3 +28,12 @@ redis.sentinel.master=mymaster
|
||||||
# 2. Session values are stored in local jvm and redis.
|
# 2. Session values are stored in local jvm and redis.
|
||||||
# 3. If redis is down/not responding, requests uses jvm stored session values to process user requests. Redis comes back the values will be synced.
|
# 3. If redis is down/not responding, requests uses jvm stored session values to process user requests. Redis comes back the values will be synced.
|
||||||
lb.sticky-session.enabled=false
|
lb.sticky-session.enabled=false
|
||||||
|
|
||||||
|
#- session persistent policies. (default value: DEFAULT) ex: DEFAULT, SAVE_ON_CHANGE
|
||||||
|
# policies - DEFAULT, SAVE_ON_CHANGE, ALWAYS_SAVE_AFTER_REQUEST
|
||||||
|
# 1. SAVE_ON_CHANGE: every time session.setAttribute() or session.removeAttribute() is called the session will be saved.
|
||||||
|
# 2. ALWAYS_SAVE_AFTER_REQUEST: force saving after every request, regardless of whether or not the manager has detected changes to the session.
|
||||||
|
session.persistent.policies=DEFAULT
|
||||||
|
|
||||||
|
#- single-sign-on session timeout. (default value: 0 ms (-no expiry))
|
||||||
|
redis.sso.timeout=0
|
||||||
|
|
Loading…
Reference in New Issue