Tomcat session manager implemented. #590

pull/709/head
Nikita 8 years ago
parent 7c0ac961e7
commit c378299009

@ -51,6 +51,7 @@
<modules>
<module>redisson</module>
<module>redisson-all</module>
<module>redisson-tomcat</module>
</modules>
<profiles>

@ -0,0 +1,61 @@
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.redisson</groupId>
<artifactId>redisson-parent</artifactId>
<version>2.6.1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>redisson-tomcat</artifactId>
<packaging>pom</packaging>
<name>Redisson/Tomcat</name>
<modules>
<module>redisson-tomcat-6</module>
<module>redisson-tomcat-7</module>
<module>redisson-tomcat-8</module>
</modules>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.4</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>fluent-hc</artifactId>
<version>4.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

@ -0,0 +1,64 @@
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.redisson</groupId>
<artifactId>redisson-tomcat</artifactId>
<version>2.6.1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>redisson-tomcat-6</artifactId>
<packaging>jar</packaging>
<name>Redisson/Tomcat-6</name>
<dependencies>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>catalina</artifactId>
<version>6.0.48</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>3.0</version>
<configuration>
<basedir>${basedir}</basedir>
<header>${basedir}/../../header.txt</header>
<quiet>false</quiet>
<failIfMissing>true</failIfMissing>
<aggregate>false</aggregate>
<includes>
<include>src/main/java/org/redisson/</include>
</includes>
<excludes>
<exclude>target/**</exclude>
</excludes>
<useDefaultExcludes>true</useDefaultExcludes>
<mapping>
<java>JAVADOC_STYLE</java>
</mapping>
<strictCheck>true</strictCheck>
<useDefaultMapping>true</useDefaultMapping>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,172 @@
/**
* Copyright 2016 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.tomcat;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.catalina.session.StandardSession;
import org.redisson.api.RMap;
/**
* Redisson Session object for Apache Tomcat
*
* @author Nikita Koksharov
*
*/
public class RedissonSession extends StandardSession {
private final RedissonSessionManager redissonManager;
private RMap<String, Object> map;
public RedissonSession(RedissonSessionManager manager) {
super(manager);
this.redissonManager = manager;
}
private static final long serialVersionUID = -2518607181636076487L;
@Override
public void setId(String id, boolean notify) {
super.setId(id, notify);
map = redissonManager.getMap(id);
}
@Override
public void setCreationTime(long time) {
super.setCreationTime(time);
if (map != null) {
map.fastPut("session:creationTime", creationTime);
map.fastPut("session:lastAccessedTime", lastAccessedTime);
map.fastPut("session:thisAccessedTime", thisAccessedTime);
}
}
@Override
public void access() {
super.access();
if (map != null) {
map.fastPut("session:lastAccessedTime", lastAccessedTime);
map.fastPut("session:thisAccessedTime", thisAccessedTime);
if (getMaxInactiveInterval() >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
}
@Override
public void setMaxInactiveInterval(int interval) {
super.setMaxInactiveInterval(interval);
if (map != null) {
map.fastPut("session:maxInactiveInterval", maxInactiveInterval);
if (maxInactiveInterval >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
}
@Override
public void setValid(boolean isValid) {
super.setValid(isValid);
if (map != null) {
map.fastPut("session:isValid", isValid);
}
}
@Override
public void setNew(boolean isNew) {
super.setNew(isNew);
if (map != null) {
map.fastPut("session:isNew", isNew);
}
}
@Override
public void endAccess() {
boolean oldValue = isNew;
super.endAccess();
if (isNew != oldValue) {
map.fastPut("session:isNew", isNew);
}
}
@Override
public void setAttribute(String name, Object value, boolean notify) {
super.setAttribute(name, value, notify);
if (map != null && value != null) {
map.fastPut(name, value);
}
}
@Override
protected void removeAttributeInternal(String name, boolean notify) {
super.removeAttributeInternal(name, notify);
if (getId() != null) {
RMap<String, Object> map = redissonManager.getMap(getId());
map.fastRemove(name);
}
}
public void save() {
RMap<String, Object> map = redissonManager.getMap(getId());
map.fastPut("session:creationTime", creationTime);
map.fastPut("session:lastAccessedTime", lastAccessedTime);
map.fastPut("session:thisAccessedTime", thisAccessedTime);
map.fastPut("session:maxInactiveInterval", maxInactiveInterval);
map.fastPut("session:isValid", isValid);
map.fastPut("session:isNew", isNew);
for (Entry<String, Object> entry : attributes.entrySet()) {
map.fastPut(entry.getKey(), entry.getValue());
}
}
public void load() {
RMap<String, Object> map = redissonManager.getMap(getId());
if (!map.isExists()) {
return;
}
Set<Entry<String, Object>> entrySet = map.readAllEntrySet();
for (Entry<String, Object> entry : entrySet) {
if ("session:creationTime".equals(entry.getKey())) {
creationTime = (Long) entry.getValue();
} else if ("session:lastAccessedTime".equals(entry.getKey())) {
lastAccessedTime = (Long) entry.getValue();
} else if ("session:thisAccessedTime".equals(entry.getKey())) {
thisAccessedTime = (Long) entry.getValue();
} else if ("session:maxInactiveInterval".equals(entry.getKey())) {
maxInactiveInterval = (Integer) entry.getValue();
} else if ("session:isValid".equals(entry.getKey())) {
isValid = (Boolean) entry.getValue();
} else if ("session:isNew".equals(entry.getKey())) {
isNew = (Boolean) entry.getValue();
} else {
setAttribute(entry.getKey(), entry.getValue(), false);
}
}
}
}

@ -0,0 +1,179 @@
/**
* Copyright 2016 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.tomcat;
import java.io.File;
import java.io.IOException;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Session;
import org.apache.catalina.session.ManagerBase;
import org.apache.catalina.util.LifecycleSupport;
import org.redisson.Redisson;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
/**
* Redisson Session Manager for Apache Tomcat
*
* @author Nikita Koksharov
*
*/
public class RedissonSessionManager extends ManagerBase implements Lifecycle {
private final Log log = LogFactory.getLog(RedissonSessionManager.class);
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
private RedissonClient redisson;
private String configPath;
public void setConfigPath(String configPath) {
this.configPath = configPath;
}
public String getConfigPath() {
return configPath;
}
@Override
public int getRejectedSessions() {
return 0;
}
@Override
public void load() throws ClassNotFoundException, IOException {
}
@Override
public void setRejectedSessions(int sessions) {
}
@Override
public void unload() throws IOException {
}
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
@Override
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
@Override
public Session createSession(String sessionId) {
RedissonSession session = (RedissonSession) createEmptySession();
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(((Context) getContainer()).getSessionTimeout() * 60);
if (sessionId == null) {
sessionId = generateSessionId();
}
session.setId(sessionId);
session.save();
return session;
}
public RMap<String, Object> getMap(String sessionId) {
return redisson.getMap("redisson_tomcat_session:" + sessionId);
}
@Override
public Session findSession(String id) throws IOException {
Session result = super.findSession(id);
if (result == null && id != null) {
RedissonSession session = (RedissonSession) createEmptySession();
session.setId(id);
session.load();
return session;
}
return result;
}
@Override
public Session createEmptySession() {
return new RedissonSession(this);
}
@Override
public void remove(Session session) {
super.remove(session);
getMap(session.getId()).delete();
}
public RedissonClient getRedisson() {
return redisson;
}
@Override
public void start() throws LifecycleException {
Config config = null;
try {
config = Config.fromJSON(new File(configPath));
} catch (IOException e) {
// trying next format
try {
config = Config.fromYAML(new File(configPath));
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
}
}
try {
redisson = Redisson.create(config);
} catch (Exception e) {
throw new LifecycleException(e);
}
lifecycle.fireLifecycleEvent(START_EVENT, null);
}
@Override
public void stop() throws LifecycleException {
try {
if (redisson != null) {
redisson.shutdown();
}
} catch (Exception e) {
throw new LifecycleException(e);
}
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
}
}

@ -0,0 +1,153 @@
package org.redisson.tomcat;
import java.io.IOException;
import org.apache.catalina.LifecycleException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.junit.Assert;
import org.junit.Test;
public class RedissonSessionManagerTest {
@Test
public void testSwitchServer() throws LifecycleException, InterruptedException, ClientProtocolException, IOException {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "/src/test/");
server.start();
Executor executor = Executor.newInstance();
BasicCookieStore cookieStore = new BasicCookieStore();
executor.use(cookieStore);
write(executor, "test", "1234");
Cookie cookie = cookieStore.getCookies().get(0);
Executor.closeIdleConnections();
server.stop();
server = new TomcatServer("myapp", 8080, "/src/test/");
server.start();
executor = Executor.newInstance();
cookieStore = new BasicCookieStore();
cookieStore.addCookie(cookie);
executor.use(cookieStore);
read(executor, "test", "1234");
remove(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testWriteReadRemove() throws LifecycleException, InterruptedException, ClientProtocolException, IOException {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "/src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1234");
read(executor, "test", "1234");
remove(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testRecreate() throws LifecycleException, InterruptedException, ClientProtocolException, IOException {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "/src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1");
recreate(executor, "test", "2");
read(executor, "test", "2");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testUpdate() throws LifecycleException, InterruptedException, ClientProtocolException, IOException {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "/src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1");
read(executor, "test", "1");
write(executor, "test", "2");
read(executor, "test", "2");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testInvalidate() throws LifecycleException, InterruptedException, ClientProtocolException, IOException {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "/src/test/");
server.start();
Executor executor = Executor.newInstance();
BasicCookieStore cookieStore = new BasicCookieStore();
executor.use(cookieStore);
write(executor, "test", "1234");
Cookie cookie = cookieStore.getCookies().get(0);
invalidate(executor);
Executor.closeIdleConnections();
executor = Executor.newInstance();
cookieStore = new BasicCookieStore();
cookieStore.addCookie(cookie);
executor.use(cookieStore);
read(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
private void write(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/write?key=" + key + "&value=" + value;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
private void read(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/read?key=" + key;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals(value, response);
}
private void remove(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/remove?key=" + key;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals(value, response);
}
private void invalidate(Executor executor) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/invalidate";
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
private void recreate(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/recreate?key=" + key + "&value=" + value;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
}

@ -0,0 +1,94 @@
package org.redisson.tomcat;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 1243830648280853203L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
if (req.getPathInfo().equals("/write")) {
String[] params = req.getQueryString().split("&");
String key = null;
String value = null;
for (String param : params) {
String[] paramLine = param.split("=");
String keyParam = paramLine[0];
String valueParam = paramLine[1];
if ("key".equals(keyParam)) {
key = valueParam;
}
if ("value".equals(keyParam)) {
value = valueParam;
}
}
session.setAttribute(key, value);
resp.getWriter().print("OK");
} else if (req.getPathInfo().equals("/read")) {
String[] params = req.getQueryString().split("&");
String key = null;
for (String param : params) {
String[] line = param.split("=");
String keyParam = line[0];
if ("key".equals(keyParam)) {
key = line[1];
}
}
Object attr = session.getAttribute(key);
resp.getWriter().print(attr);
} else if (req.getPathInfo().equals("/remove")) {
String[] params = req.getQueryString().split("&");
String key = null;
for (String param : params) {
String[] line = param.split("=");
String keyParam = line[0];
if ("key".equals(keyParam)) {
key = line[1];
}
}
session.removeAttribute(key);
resp.getWriter().print(String.valueOf(session.getAttribute(key)));
} else if (req.getPathInfo().equals("/invalidate")) {
session.invalidate();
resp.getWriter().print("OK");
} else if (req.getPathInfo().equals("/recreate")) {
session.invalidate();
session = req.getSession();
String[] params = req.getQueryString().split("&");
String key = null;
String value = null;
for (String param : params) {
String[] paramLine = param.split("=");
String keyParam = paramLine[0];
String valueParam = paramLine[1];
if ("key".equals(keyParam)) {
key = valueParam;
}
if ("value".equals(keyParam)) {
value = valueParam;
}
}
session.setAttribute(key, value);
resp.getWriter().print("OK");
}
}
}

@ -0,0 +1,109 @@
package org.redisson.tomcat;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Embedded;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TomcatServer {
private Embedded server;
private int port;
private boolean isRunning;
private static final Logger LOG = LoggerFactory.getLogger(TomcatServer.class);
private static final boolean isInfo = LOG.isInfoEnabled();
/**
* Create a new Tomcat embedded server instance. Setup looks like:
* <pre><Server>
* <Service>
* <Connector />
* <Engine&gt
* <Host>
* <Context />
* </Host>
* </Engine>
* </Service>
*</Server></pre>
* <Server> & <Service> will be created automcatically. We need to hook the remaining to an {@link Embedded} instnace
* @param contextPath Context path for the application
* @param port Port number to be used for the embedded Tomcat server
* @param appBase Path to the Application files (for Maven based web apps, in general: <code>/src/main/</code>)
* @param shutdownHook If true, registers a server' shutdown hook with JVM. This is useful to shutdown the server
* in erroneous cases.
* @throws Exception
*/
public TomcatServer(String contextPath, int port, String appBase) {
if(contextPath == null || appBase == null || appBase.length() == 0) {
throw new IllegalArgumentException("Context path or appbase should not be null");
}
if(!contextPath.startsWith("/")) {
contextPath = "/" + contextPath;
}
this.port = port;
server = new Embedded();
server.setName("TomcatEmbeddedServer");
Host localHost = server.createHost("localhost", appBase);
localHost.setAutoDeploy(false);
StandardContext rootContext = (StandardContext) server.createContext(contextPath, "webapp");
rootContext.setDefaultWebXml("web.xml");
localHost.addChild(rootContext);
Engine engine = server.createEngine();
engine.setDefaultHost(localHost.getName());
engine.setName("TomcatEngine");
engine.addChild(localHost);
server.addEngine(engine);
Connector connector = server.createConnector(localHost.getName(), port, false);
server.addConnector(connector);
}
/**
* Start the tomcat embedded server
*/
public void start() throws LifecycleException {
if(isRunning) {
LOG.warn("Tomcat server is already running @ port={}; ignoring the start", port);
return;
}
if(isInfo) LOG.info("Starting the Tomcat server @ port={}", port);
server.setAwait(true);
server.start();
isRunning = true;
}
/**
* Stop the tomcat embedded server
*/
public void stop() throws LifecycleException {
if(!isRunning) {
LOG.warn("Tomcat server is not running @ port={}", port);
return;
}
if(isInfo) LOG.info("Stopping the Tomcat server");
server.stop();
isRunning = false;
}
public boolean isRunning() {
return isRunning;
}
}

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<Context>
<Manager className="org.redisson.tomcat.RedissonSessionManager"
configPath="${catalina.base}/src/test/webapp/WEB-INF/redisson.yaml" />
</Context>

@ -0,0 +1,3 @@
singleServerConfig:
address:
- "//127.0.0.1:6379"

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>testServlet</servlet-name>
<servlet-class>org.redisson.tomcat.TestServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>testServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>

@ -0,0 +1,82 @@
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.redisson</groupId>
<artifactId>redisson-tomcat</artifactId>
<version>2.6.1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>redisson-tomcat-7</artifactId>
<packaging>jar</packaging>
<name>Redisson/Tomcat-7</name>
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>7.0.73</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<version>7.0.73</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>7.0.73</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>7.0.73</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>3.0</version>
<configuration>
<basedir>${basedir}</basedir>
<header>${basedir}/../../header.txt</header>
<quiet>false</quiet>
<failIfMissing>true</failIfMissing>
<aggregate>false</aggregate>
<includes>
<include>src/main/java/org/redisson/</include>
</includes>
<excludes>
<exclude>target/**</exclude>
</excludes>
<useDefaultExcludes>true</useDefaultExcludes>
<mapping>
<java>JAVADOC_STYLE</java>
</mapping>
<strictCheck>true</strictCheck>
<useDefaultMapping>true</useDefaultMapping>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,172 @@
/**
* Copyright 2016 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.tomcat;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.catalina.session.StandardSession;
import org.redisson.api.RMap;
/**
* Redisson Session object for Apache Tomcat
*
* @author Nikita Koksharov
*
*/
public class RedissonSession extends StandardSession {
private final RedissonSessionManager redissonManager;
private RMap<String, Object> map;
public RedissonSession(RedissonSessionManager manager) {
super(manager);
this.redissonManager = manager;
}
private static final long serialVersionUID = -2518607181636076487L;
@Override
public void setId(String id, boolean notify) {
super.setId(id, notify);
map = redissonManager.getMap(id);
}
@Override
public void setCreationTime(long time) {
super.setCreationTime(time);
if (map != null) {
map.fastPut("session:creationTime", creationTime);
map.fastPut("session:lastAccessedTime", lastAccessedTime);
map.fastPut("session:thisAccessedTime", thisAccessedTime);
}
}
@Override
public void access() {
super.access();
if (map != null) {
map.fastPut("session:lastAccessedTime", lastAccessedTime);
map.fastPut("session:thisAccessedTime", thisAccessedTime);
if (getMaxInactiveInterval() >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
}
@Override
public void setMaxInactiveInterval(int interval) {
super.setMaxInactiveInterval(interval);
if (map != null) {
map.fastPut("session:maxInactiveInterval", maxInactiveInterval);
if (maxInactiveInterval >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
}
@Override
public void setValid(boolean isValid) {
super.setValid(isValid);
if (map != null) {
map.fastPut("session:isValid", isValid);
}
}
@Override
public void setNew(boolean isNew) {
super.setNew(isNew);
if (map != null) {
map.fastPut("session:isNew", isNew);
}
}
@Override
public void endAccess() {
boolean oldValue = isNew;
super.endAccess();
if (isNew != oldValue) {
map.fastPut("session:isNew", isNew);
}
}
@Override
public void setAttribute(String name, Object value, boolean notify) {
super.setAttribute(name, value, notify);
if (map != null && value != null) {
map.fastPut(name, value);
}
}
@Override
protected void removeAttributeInternal(String name, boolean notify) {
super.removeAttributeInternal(name, notify);
if (getId() != null) {
RMap<String, Object> map = redissonManager.getMap(getId());
map.fastRemove(name);
}
}
public void save() {
RMap<String, Object> map = redissonManager.getMap(getId());
map.fastPut("session:creationTime", creationTime);
map.fastPut("session:lastAccessedTime", lastAccessedTime);
map.fastPut("session:thisAccessedTime", thisAccessedTime);
map.fastPut("session:maxInactiveInterval", maxInactiveInterval);
map.fastPut("session:isValid", isValid);
map.fastPut("session:isNew", isNew);
for (Entry<String, Object> entry : attributes.entrySet()) {
map.fastPut(entry.getKey(), entry.getValue());
}
}
public void load() {
RMap<String, Object> map = redissonManager.getMap(getId());
if (!map.isExists()) {
return;
}
Set<Entry<String, Object>> entrySet = map.readAllEntrySet();
for (Entry<String, Object> entry : entrySet) {
if ("session:creationTime".equals(entry.getKey())) {
creationTime = (Long) entry.getValue();
} else if ("session:lastAccessedTime".equals(entry.getKey())) {
lastAccessedTime = (Long) entry.getValue();
} else if ("session:thisAccessedTime".equals(entry.getKey())) {
thisAccessedTime = (Long) entry.getValue();
} else if ("session:maxInactiveInterval".equals(entry.getKey())) {
maxInactiveInterval = (Integer) entry.getValue();
} else if ("session:isValid".equals(entry.getKey())) {
isValid = (Boolean) entry.getValue();
} else if ("session:isNew".equals(entry.getKey())) {
isNew = (Boolean) entry.getValue();
} else {
setAttribute(entry.getKey(), entry.getValue(), false);
}
}
}
}

@ -0,0 +1,185 @@
/**
* Copyright 2016 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.tomcat;
import java.io.File;
import java.io.IOException;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Session;
import org.apache.catalina.session.ManagerBase;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.redisson.Redisson;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
/**
* Redisson Session Manager for Apache Tomcat
*
* @author Nikita Koksharov
*
*/
public class RedissonSessionManager extends ManagerBase implements Lifecycle {
private final Log log = LogFactory.getLog(RedissonSessionManager.class);
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
private RedissonClient redisson;
private String configPath;
public void setConfigPath(String configPath) {
this.configPath = configPath;
}
public String getConfigPath() {
return configPath;
}
@Override
public String getName() {
return RedissonSessionManager.class.getSimpleName();
}
@Override
public int getRejectedSessions() {
return 0;
}
@Override
public void load() throws ClassNotFoundException, IOException {
}
@Override
public void unload() throws IOException {
}
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
@Override
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
@Override
public Session createSession(String sessionId) {
RedissonSession session = (RedissonSession) createEmptySession();
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(((Context) getContainer()).getSessionTimeout() * 60);
if (sessionId == null) {
sessionId = generateSessionId();
}
session.setId(sessionId);
session.save();
return session;
}
public RMap<String, Object> getMap(String sessionId) {
return redisson.getMap("redisson_tomcat_session:" + sessionId);
}
@Override
public Session findSession(String id) throws IOException {
Session result = super.findSession(id);
if (result == null && id != null) {
RedissonSession session = (RedissonSession) createEmptySession();
session.setId(id);
session.load();
return session;
}
return result;
}
@Override
public Session createEmptySession() {
return new RedissonSession(this);
}
@Override
public void remove(Session session) {
super.remove(session);
getMap(session.getId()).delete();
}
public RedissonClient getRedisson() {
return redisson;
}
@Override
protected void startInternal() throws LifecycleException {
super.startInternal();
Config config = null;
try {
config = Config.fromJSON(new File(configPath));
} catch (IOException e) {
// trying next format
try {
config = Config.fromYAML(new File(configPath));
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
}
}
try {
redisson = Redisson.create(config);
} catch (Exception e) {
throw new LifecycleException(e);
}
setState(LifecycleState.STARTING);
}
@Override
protected void stopInternal() throws LifecycleException {
super.stopInternal();
setState(LifecycleState.STOPPING);
try {
if (redisson != null) {
redisson.shutdown();
}
} catch (Exception e) {
throw new LifecycleException(e);
}
}
}

@ -0,0 +1,152 @@
package org.redisson.tomcat;
import java.io.IOException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.junit.Assert;
import org.junit.Test;
public class RedissonSessionManagerTest {
@Test
public void testSwitchServer() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
BasicCookieStore cookieStore = new BasicCookieStore();
executor.use(cookieStore);
write(executor, "test", "1234");
Cookie cookie = cookieStore.getCookies().get(0);
Executor.closeIdleConnections();
server.stop();
server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
executor = Executor.newInstance();
cookieStore = new BasicCookieStore();
cookieStore.addCookie(cookie);
executor.use(cookieStore);
read(executor, "test", "1234");
remove(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testWriteReadRemove() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1234");
read(executor, "test", "1234");
remove(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testRecreate() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1");
recreate(executor, "test", "2");
read(executor, "test", "2");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testUpdate() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1");
read(executor, "test", "1");
write(executor, "test", "2");
read(executor, "test", "2");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testInvalidate() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
BasicCookieStore cookieStore = new BasicCookieStore();
executor.use(cookieStore);
write(executor, "test", "1234");
Cookie cookie = cookieStore.getCookies().get(0);
invalidate(executor);
Executor.closeIdleConnections();
executor = Executor.newInstance();
cookieStore = new BasicCookieStore();
cookieStore.addCookie(cookie);
executor.use(cookieStore);
read(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
private void write(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/write?key=" + key + "&value=" + value;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
private void read(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/read?key=" + key;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals(value, response);
}
private void remove(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/remove?key=" + key;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals(value, response);
}
private void invalidate(Executor executor) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/invalidate";
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
private void recreate(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/recreate?key=" + key + "&value=" + value;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
}

@ -0,0 +1,94 @@
package org.redisson.tomcat;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 1243830648280853203L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
if (req.getPathInfo().equals("/write")) {
String[] params = req.getQueryString().split("&");
String key = null;
String value = null;
for (String param : params) {
String[] paramLine = param.split("=");
String keyParam = paramLine[0];
String valueParam = paramLine[1];
if ("key".equals(keyParam)) {
key = valueParam;
}
if ("value".equals(keyParam)) {
value = valueParam;
}
}
session.setAttribute(key, value);
resp.getWriter().print("OK");
} else if (req.getPathInfo().equals("/read")) {
String[] params = req.getQueryString().split("&");
String key = null;
for (String param : params) {
String[] line = param.split("=");
String keyParam = line[0];
if ("key".equals(keyParam)) {
key = line[1];
}
}
Object attr = session.getAttribute(key);
resp.getWriter().print(attr);
} else if (req.getPathInfo().equals("/remove")) {
String[] params = req.getQueryString().split("&");
String key = null;
for (String param : params) {
String[] line = param.split("=");
String keyParam = line[0];
if ("key".equals(keyParam)) {
key = line[1];
}
}
session.removeAttribute(key);
resp.getWriter().print(String.valueOf(session.getAttribute(key)));
} else if (req.getPathInfo().equals("/invalidate")) {
session.invalidate();
resp.getWriter().print("OK");
} else if (req.getPathInfo().equals("/recreate")) {
session.invalidate();
session = req.getSession();
String[] params = req.getQueryString().split("&");
String key = null;
String value = null;
for (String param : params) {
String[] paramLine = param.split("=");
String keyParam = paramLine[0];
String valueParam = paramLine[1];
if ("key".equals(keyParam)) {
key = valueParam;
}
if ("value".equals(keyParam)) {
value = valueParam;
}
}
session.setAttribute(key, value);
resp.getWriter().print("OK");
}
}
}

@ -0,0 +1,64 @@
package org.redisson.tomcat;
import java.net.MalformedURLException;
import javax.servlet.ServletException;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TomcatServer {
private Tomcat tomcat = new Tomcat();
private int port;
private boolean isRunning;
private static final Logger LOG = LoggerFactory.getLogger(TomcatServer.class);
private static final boolean isInfo = LOG.isInfoEnabled();
public TomcatServer(String contextPath, int port, String appBase) throws MalformedURLException, ServletException {
if(contextPath == null || appBase == null || appBase.length() == 0) {
throw new IllegalArgumentException("Context path or appbase should not be null");
}
if(!contextPath.startsWith("/")) {
contextPath = "/" + contextPath;
}
tomcat.setBaseDir("."); // location where temp dir is created
tomcat.setPort(port);
tomcat.getHost().setAppBase(".");
tomcat.addWebapp(contextPath, appBase + "webapp");
}
/**
* Start the tomcat embedded server
*/
public void start() throws LifecycleException {
tomcat.start();
isRunning = true;
}
/**
* Stop the tomcat embedded server
*/
public void stop() throws LifecycleException {
if(!isRunning) {
LOG.warn("Tomcat server is not running @ port={}", port);
return;
}
if(isInfo) LOG.info("Stopping the Tomcat server");
tomcat.stop();
tomcat.destroy();
isRunning = false;
}
public boolean isRunning() {
return isRunning;
}
}

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<Context>
<Manager className="org.redisson.tomcat.RedissonSessionManager"
configPath="${catalina.base}/src/test/webapp/WEB-INF/redisson.yaml" />
</Context>

@ -0,0 +1,3 @@
singleServerConfig:
address:
- "//127.0.0.1:6379"

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>testServlet</servlet-name>
<servlet-class>org.redisson.tomcat.TestServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>testServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>

@ -0,0 +1,81 @@
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.redisson</groupId>
<artifactId>redisson-tomcat</artifactId>
<version>2.6.1-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
<artifactId>redisson-tomcat-8</artifactId>
<packaging>jar</packaging>
<name>Redisson/Tomcat-8</name>
<dependencies>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.0.39</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-logging-juli</artifactId>
<version>8.0.39</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<version>8.0.39</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper</artifactId>
<version>8.0.39</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>3.0</version>
<configuration>
<basedir>${basedir}</basedir>
<header>${basedir}/../../header.txt</header>
<quiet>false</quiet>
<failIfMissing>true</failIfMissing>
<aggregate>false</aggregate>
<includes>
<include>src/main/java/org/redisson/</include>
</includes>
<excludes>
<exclude>target/**</exclude>
</excludes>
<useDefaultExcludes>true</useDefaultExcludes>
<mapping>
<java>JAVADOC_STYLE</java>
</mapping>
<strictCheck>true</strictCheck>
<useDefaultMapping>true</useDefaultMapping>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,172 @@
/**
* Copyright 2016 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.tomcat;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.catalina.session.StandardSession;
import org.redisson.api.RMap;
/**
* Redisson Session object for Apache Tomcat
*
* @author Nikita Koksharov
*
*/
public class RedissonSession extends StandardSession {
private final RedissonSessionManager redissonManager;
private RMap<String, Object> map;
public RedissonSession(RedissonSessionManager manager) {
super(manager);
this.redissonManager = manager;
}
private static final long serialVersionUID = -2518607181636076487L;
@Override
public void setId(String id, boolean notify) {
super.setId(id, notify);
map = redissonManager.getMap(id);
}
@Override
public void setCreationTime(long time) {
super.setCreationTime(time);
if (map != null) {
map.fastPut("session:creationTime", creationTime);
map.fastPut("session:lastAccessedTime", lastAccessedTime);
map.fastPut("session:thisAccessedTime", thisAccessedTime);
}
}
@Override
public void access() {
super.access();
if (map != null) {
map.fastPut("session:lastAccessedTime", lastAccessedTime);
map.fastPut("session:thisAccessedTime", thisAccessedTime);
if (getMaxInactiveInterval() >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
}
@Override
public void setMaxInactiveInterval(int interval) {
super.setMaxInactiveInterval(interval);
if (map != null) {
map.fastPut("session:maxInactiveInterval", maxInactiveInterval);
if (maxInactiveInterval >= 0) {
map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
}
}
}
@Override
public void setValid(boolean isValid) {
super.setValid(isValid);
if (map != null) {
map.fastPut("session:isValid", isValid);
}
}
@Override
public void setNew(boolean isNew) {
super.setNew(isNew);
if (map != null) {
map.fastPut("session:isNew", isNew);
}
}
@Override
public void endAccess() {
boolean oldValue = isNew;
super.endAccess();
if (isNew != oldValue) {
map.fastPut("session:isNew", isNew);
}
}
@Override
public void setAttribute(String name, Object value, boolean notify) {
super.setAttribute(name, value, notify);
if (map != null && value != null) {
map.fastPut(name, value);
}
}
@Override
protected void removeAttributeInternal(String name, boolean notify) {
super.removeAttributeInternal(name, notify);
if (getId() != null) {
RMap<String, Object> map = redissonManager.getMap(getId());
map.fastRemove(name);
}
}
public void save() {
RMap<String, Object> map = redissonManager.getMap(getId());
map.fastPut("session:creationTime", creationTime);
map.fastPut("session:lastAccessedTime", lastAccessedTime);
map.fastPut("session:thisAccessedTime", thisAccessedTime);
map.fastPut("session:maxInactiveInterval", maxInactiveInterval);
map.fastPut("session:isValid", isValid);
map.fastPut("session:isNew", isNew);
for (Entry<String, Object> entry : attributes.entrySet()) {
map.fastPut(entry.getKey(), entry.getValue());
}
}
public void load() {
RMap<String, Object> map = redissonManager.getMap(getId());
if (!map.isExists()) {
return;
}
Set<Entry<String, Object>> entrySet = map.readAllEntrySet();
for (Entry<String, Object> entry : entrySet) {
if ("session:creationTime".equals(entry.getKey())) {
creationTime = (Long) entry.getValue();
} else if ("session:lastAccessedTime".equals(entry.getKey())) {
lastAccessedTime = (Long) entry.getValue();
} else if ("session:thisAccessedTime".equals(entry.getKey())) {
thisAccessedTime = (Long) entry.getValue();
} else if ("session:maxInactiveInterval".equals(entry.getKey())) {
maxInactiveInterval = (Integer) entry.getValue();
} else if ("session:isValid".equals(entry.getKey())) {
isValid = (Boolean) entry.getValue();
} else if ("session:isNew".equals(entry.getKey())) {
isNew = (Boolean) entry.getValue();
} else {
setAttribute(entry.getKey(), entry.getValue(), false);
}
}
}
}

@ -0,0 +1,185 @@
/**
* Copyright 2016 Nikita Koksharov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.redisson.tomcat;
import java.io.File;
import java.io.IOException;
import org.apache.catalina.Context;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.Session;
import org.apache.catalina.session.ManagerBase;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.redisson.Redisson;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
/**
* Redisson Session Manager for Apache Tomcat
*
* @author Nikita Koksharov
*
*/
public class RedissonSessionManager extends ManagerBase implements Lifecycle {
private final Log log = LogFactory.getLog(RedissonSessionManager.class);
protected LifecycleSupport lifecycle = new LifecycleSupport(this);
private RedissonClient redisson;
private String configPath;
public void setConfigPath(String configPath) {
this.configPath = configPath;
}
public String getConfigPath() {
return configPath;
}
@Override
public String getName() {
return RedissonSessionManager.class.getSimpleName();
}
@Override
public int getRejectedSessions() {
return 0;
}
@Override
public void load() throws ClassNotFoundException, IOException {
}
@Override
public void unload() throws IOException {
}
@Override
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
@Override
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
@Override
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
@Override
public Session createSession(String sessionId) {
RedissonSession session = (RedissonSession) createEmptySession();
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
session.setMaxInactiveInterval(((Context) getContainer()).getSessionTimeout() * 60);
if (sessionId == null) {
sessionId = generateSessionId();
}
session.setId(sessionId);
session.save();
return session;
}
public RMap<String, Object> getMap(String sessionId) {
return redisson.getMap("redisson_tomcat_session:" + sessionId);
}
@Override
public Session findSession(String id) throws IOException {
Session result = super.findSession(id);
if (result == null && id != null) {
RedissonSession session = (RedissonSession) createEmptySession();
session.setId(id);
session.load();
return session;
}
return result;
}
@Override
public Session createEmptySession() {
return new RedissonSession(this);
}
@Override
public void remove(Session session) {
super.remove(session);
getMap(session.getId()).delete();
}
public RedissonClient getRedisson() {
return redisson;
}
@Override
protected void startInternal() throws LifecycleException {
super.startInternal();
Config config = null;
try {
config = Config.fromJSON(new File(configPath));
} catch (IOException e) {
// trying next format
try {
config = Config.fromYAML(new File(configPath));
} catch (IOException e1) {
log.error("Can't parse json config " + configPath, e);
throw new LifecycleException("Can't parse yaml config " + configPath, e1);
}
}
try {
redisson = Redisson.create(config);
} catch (Exception e) {
throw new LifecycleException(e);
}
setState(LifecycleState.STARTING);
}
@Override
protected void stopInternal() throws LifecycleException {
super.stopInternal();
setState(LifecycleState.STOPPING);
try {
if (redisson != null) {
redisson.shutdown();
}
} catch (Exception e) {
throw new LifecycleException(e);
}
}
}

@ -0,0 +1,152 @@
package org.redisson.tomcat;
import java.io.IOException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.junit.Assert;
import org.junit.Test;
public class RedissonSessionManagerTest {
@Test
public void testSwitchServer() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
BasicCookieStore cookieStore = new BasicCookieStore();
executor.use(cookieStore);
write(executor, "test", "1234");
Cookie cookie = cookieStore.getCookies().get(0);
Executor.closeIdleConnections();
server.stop();
server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
executor = Executor.newInstance();
cookieStore = new BasicCookieStore();
cookieStore.addCookie(cookie);
executor.use(cookieStore);
read(executor, "test", "1234");
remove(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testWriteReadRemove() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1234");
read(executor, "test", "1234");
remove(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testRecreate() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1");
recreate(executor, "test", "2");
read(executor, "test", "2");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testUpdate() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
write(executor, "test", "1");
read(executor, "test", "1");
write(executor, "test", "2");
read(executor, "test", "2");
Executor.closeIdleConnections();
server.stop();
}
@Test
public void testInvalidate() throws Exception {
// start the server at http://localhost:8080/myapp
TomcatServer server = new TomcatServer("myapp", 8080, "src/test/");
server.start();
Executor executor = Executor.newInstance();
BasicCookieStore cookieStore = new BasicCookieStore();
executor.use(cookieStore);
write(executor, "test", "1234");
Cookie cookie = cookieStore.getCookies().get(0);
invalidate(executor);
Executor.closeIdleConnections();
executor = Executor.newInstance();
cookieStore = new BasicCookieStore();
cookieStore.addCookie(cookie);
executor.use(cookieStore);
read(executor, "test", "null");
Executor.closeIdleConnections();
server.stop();
}
private void write(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/write?key=" + key + "&value=" + value;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
private void read(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/read?key=" + key;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals(value, response);
}
private void remove(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/remove?key=" + key;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals(value, response);
}
private void invalidate(Executor executor) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/invalidate";
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
private void recreate(Executor executor, String key, String value) throws IOException, ClientProtocolException {
String url = "http://localhost:8080/myapp/recreate?key=" + key + "&value=" + value;
String response = executor.execute(Request.Get(url)).returnContent().asString();
Assert.assertEquals("OK", response);
}
}

@ -0,0 +1,94 @@
package org.redisson.tomcat;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class TestServlet extends HttpServlet {
private static final long serialVersionUID = 1243830648280853203L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
if (req.getPathInfo().equals("/write")) {
String[] params = req.getQueryString().split("&");
String key = null;
String value = null;
for (String param : params) {
String[] paramLine = param.split("=");
String keyParam = paramLine[0];
String valueParam = paramLine[1];
if ("key".equals(keyParam)) {
key = valueParam;
}
if ("value".equals(keyParam)) {
value = valueParam;
}
}
session.setAttribute(key, value);
resp.getWriter().print("OK");
} else if (req.getPathInfo().equals("/read")) {
String[] params = req.getQueryString().split("&");
String key = null;
for (String param : params) {
String[] line = param.split("=");
String keyParam = line[0];
if ("key".equals(keyParam)) {
key = line[1];
}
}
Object attr = session.getAttribute(key);
resp.getWriter().print(attr);
} else if (req.getPathInfo().equals("/remove")) {
String[] params = req.getQueryString().split("&");
String key = null;
for (String param : params) {
String[] line = param.split("=");
String keyParam = line[0];
if ("key".equals(keyParam)) {
key = line[1];
}
}
session.removeAttribute(key);
resp.getWriter().print(String.valueOf(session.getAttribute(key)));
} else if (req.getPathInfo().equals("/invalidate")) {
session.invalidate();
resp.getWriter().print("OK");
} else if (req.getPathInfo().equals("/recreate")) {
session.invalidate();
session = req.getSession();
String[] params = req.getQueryString().split("&");
String key = null;
String value = null;
for (String param : params) {
String[] paramLine = param.split("=");
String keyParam = paramLine[0];
String valueParam = paramLine[1];
if ("key".equals(keyParam)) {
key = valueParam;
}
if ("value".equals(keyParam)) {
value = valueParam;
}
}
session.setAttribute(key, value);
resp.getWriter().print("OK");
}
}
}

@ -0,0 +1,64 @@
package org.redisson.tomcat;
import java.net.MalformedURLException;
import javax.servlet.ServletException;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TomcatServer {
private Tomcat tomcat = new Tomcat();
private int port;
private boolean isRunning;
private static final Logger LOG = LoggerFactory.getLogger(TomcatServer.class);
private static final boolean isInfo = LOG.isInfoEnabled();
public TomcatServer(String contextPath, int port, String appBase) throws MalformedURLException, ServletException {
if(contextPath == null || appBase == null || appBase.length() == 0) {
throw new IllegalArgumentException("Context path or appbase should not be null");
}
if(!contextPath.startsWith("/")) {
contextPath = "/" + contextPath;
}
tomcat.setBaseDir("."); // location where temp dir is created
tomcat.setPort(port);
tomcat.getHost().setAppBase(".");
tomcat.addWebapp(contextPath, appBase + "webapp");
}
/**
* Start the tomcat embedded server
*/
public void start() throws LifecycleException {
tomcat.start();
isRunning = true;
}
/**
* Stop the tomcat embedded server
*/
public void stop() throws LifecycleException {
if(!isRunning) {
LOG.warn("Tomcat server is not running @ port={}", port);
return;
}
if(isInfo) LOG.info("Stopping the Tomcat server");
tomcat.stop();
tomcat.destroy();
isRunning = false;
}
public boolean isRunning() {
return isRunning;
}
}

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<Context>
<Manager className="org.redisson.tomcat.RedissonSessionManager"
configPath="${catalina.base}/src/test/webapp/WEB-INF/redisson.yaml" />
</Context>

@ -0,0 +1,3 @@
singleServerConfig:
address:
- "//127.0.0.1:6379"

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<servlet>
<servlet-name>testServlet</servlet-name>
<servlet-class>org.redisson.tomcat.TestServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>testServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>

@ -1,13 +0,0 @@
Copyright 2016 Nikita Koksharov
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Loading…
Cancel
Save