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);
+ }
+
+}
diff --git a/redisson-tomcat/redisson-tomcat-6/src/test/java/org/redisson/tomcat/RedissonSessionManagerTest.java b/redisson-tomcat/redisson-tomcat-6/src/test/java/org/redisson/tomcat/RedissonSessionManagerTest.java
new file mode 100644
index 000000000..dd8703be4
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-6/src/test/java/org/redisson/tomcat/RedissonSessionManagerTest.java
@@ -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);
+ }
+
+}
diff --git a/redisson-tomcat/redisson-tomcat-6/src/test/java/org/redisson/tomcat/TestServlet.java b/redisson-tomcat/redisson-tomcat-6/src/test/java/org/redisson/tomcat/TestServlet.java
new file mode 100644
index 000000000..1c68da965
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-6/src/test/java/org/redisson/tomcat/TestServlet.java
@@ -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");
+ }
+ }
+
+}
diff --git a/redisson-tomcat/redisson-tomcat-6/src/test/java/org/redisson/tomcat/TomcatServer.java b/redisson-tomcat/redisson-tomcat-6/src/test/java/org/redisson/tomcat/TomcatServer.java
new file mode 100644
index 000000000..62c0525fb
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-6/src/test/java/org/redisson/tomcat/TomcatServer.java
@@ -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:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * & 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: /src/main/
)
+ * @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;
+ }
+
+}
\ No newline at end of file
diff --git a/redisson-tomcat/redisson-tomcat-6/src/test/webapp/META-INF/context.xml b/redisson-tomcat/redisson-tomcat-6/src/test/webapp/META-INF/context.xml
new file mode 100644
index 000000000..52b0eafd8
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-6/src/test/webapp/META-INF/context.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/redisson-tomcat/redisson-tomcat-6/src/test/webapp/WEB-INF/redisson.yaml b/redisson-tomcat/redisson-tomcat-6/src/test/webapp/WEB-INF/redisson.yaml
new file mode 100644
index 000000000..28466f3ba
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-6/src/test/webapp/WEB-INF/redisson.yaml
@@ -0,0 +1,2 @@
+singleServerConfig:
+ address: "redis://127.0.0.1:6379"
\ No newline at end of file
diff --git a/redisson-tomcat/redisson-tomcat-6/src/test/webapp/WEB-INF/web.xml b/redisson-tomcat/redisson-tomcat-6/src/test/webapp/WEB-INF/web.xml
new file mode 100644
index 000000000..5940ccb8a
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-6/src/test/webapp/WEB-INF/web.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ testServlet
+ org.redisson.tomcat.TestServlet
+ 1
+
+
+
+ testServlet
+ /*
+
+
+
+ 30
+
+
+
diff --git a/redisson-tomcat/redisson-tomcat-7/pom.xml b/redisson-tomcat/redisson-tomcat-7/pom.xml
new file mode 100644
index 000000000..e6b67b474
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-7/pom.xml
@@ -0,0 +1,82 @@
+
+ 4.0.0
+
+
+ org.redisson
+ redisson-tomcat
+ 2.8.1-SNAPSHOT
+ ../
+
+
+ redisson-tomcat-7
+ jar
+
+ Redisson/Tomcat-7
+
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-core
+ 7.0.73
+ provided
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-logging-juli
+ 7.0.73
+ provided
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-jasper
+ 7.0.73
+ provided
+
+
+ org.apache.tomcat
+ tomcat-jasper
+ 7.0.73
+ provided
+
+
+
+
+
+
+ com.mycila
+ license-maven-plugin
+ 3.0
+
+ ${basedir}
+ ${basedir}/../../header.txt
+ false
+ true
+ false
+
+ src/main/java/org/redisson/
+
+
+ target/**
+
+ true
+
+ JAVADOC_STYLE
+
+ true
+ true
+ UTF-8
+
+
+
+
+ check
+
+
+
+
+
+
+
+
+
+
diff --git a/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSession.java b/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSession.java
new file mode 100644
index 000000000..31db2671f
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSession.java
@@ -0,0 +1,186 @@
+/**
+ * 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.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+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 final Map attrs;
+ private RMap map;
+
+ public RedissonSession(RedissonSessionManager manager) {
+ super(manager);
+ this.redissonManager = manager;
+ try {
+ Field attr = StandardSession.class.getDeclaredField("attributes");
+ attrs = (Map) attr.get(this);
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ 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 newMap = new HashMap(3);
+ newMap.put("session:creationTime", creationTime);
+ newMap.put("session:lastAccessedTime", lastAccessedTime);
+ newMap.put("session:thisAccessedTime", thisAccessedTime);
+ map.putAll(newMap);
+ }
+ }
+
+ @Override
+ public void access() {
+ super.access();
+
+ if (map != null) {
+ Map newMap = new HashMap(2);
+ newMap.put("session:lastAccessedTime", lastAccessedTime);
+ newMap.put("session:thisAccessedTime", thisAccessedTime);
+ map.putAll(newMap);
+ 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 (map != null) {
+ map.fastRemove(name);
+ }
+ }
+
+ public void save() {
+ Map newMap = new HashMap();
+ newMap.put("session:creationTime", creationTime);
+ newMap.put("session:lastAccessedTime", lastAccessedTime);
+ newMap.put("session:thisAccessedTime", thisAccessedTime);
+ newMap.put("session:maxInactiveInterval", maxInactiveInterval);
+ newMap.put("session:isValid", isValid);
+ newMap.put("session:isNew", isNew);
+
+ for (Entry entry : attrs.entrySet()) {
+ newMap.put(entry.getKey(), entry.getValue());
+ }
+
+ map.putAll(newMap);
+
+ if (maxInactiveInterval >= 0) {
+ map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
+ }
+ }
+
+ public void load() {
+ Set> entrySet = map.readAllEntrySet();
+ for (Entry 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);
+ }
+ }
+ }
+
+}
diff --git a/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSessionManager.java b/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSessionManager.java
new file mode 100644
index 000000000..87d41f81a
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-7/src/main/java/org/redisson/tomcat/RedissonSessionManager.java
@@ -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 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);
+ }
+
+ }
+
+}
diff --git a/redisson-tomcat/redisson-tomcat-7/src/test/java/org/redisson/tomcat/RedissonSessionManagerTest.java b/redisson-tomcat/redisson-tomcat-7/src/test/java/org/redisson/tomcat/RedissonSessionManagerTest.java
new file mode 100644
index 000000000..2d260c587
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-7/src/test/java/org/redisson/tomcat/RedissonSessionManagerTest.java
@@ -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);
+ }
+
+}
diff --git a/redisson-tomcat/redisson-tomcat-7/src/test/java/org/redisson/tomcat/TestServlet.java b/redisson-tomcat/redisson-tomcat-7/src/test/java/org/redisson/tomcat/TestServlet.java
new file mode 100644
index 000000000..1c68da965
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-7/src/test/java/org/redisson/tomcat/TestServlet.java
@@ -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");
+ }
+ }
+
+}
diff --git a/redisson-tomcat/redisson-tomcat-7/src/test/java/org/redisson/tomcat/TomcatServer.java b/redisson-tomcat/redisson-tomcat-7/src/test/java/org/redisson/tomcat/TomcatServer.java
new file mode 100644
index 000000000..6ec7af3b6
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-7/src/test/java/org/redisson/tomcat/TomcatServer.java
@@ -0,0 +1,65 @@
+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();
+ tomcat.getServer().await();
+ isRunning = false;
+ }
+
+ public boolean isRunning() {
+ return isRunning;
+ }
+
+}
\ No newline at end of file
diff --git a/redisson-tomcat/redisson-tomcat-7/src/test/webapp/META-INF/context.xml b/redisson-tomcat/redisson-tomcat-7/src/test/webapp/META-INF/context.xml
new file mode 100644
index 000000000..52b0eafd8
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-7/src/test/webapp/META-INF/context.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/redisson-tomcat/redisson-tomcat-7/src/test/webapp/WEB-INF/redisson.yaml b/redisson-tomcat/redisson-tomcat-7/src/test/webapp/WEB-INF/redisson.yaml
new file mode 100644
index 000000000..28466f3ba
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-7/src/test/webapp/WEB-INF/redisson.yaml
@@ -0,0 +1,2 @@
+singleServerConfig:
+ address: "redis://127.0.0.1:6379"
\ No newline at end of file
diff --git a/redisson-tomcat/redisson-tomcat-7/src/test/webapp/WEB-INF/web.xml b/redisson-tomcat/redisson-tomcat-7/src/test/webapp/WEB-INF/web.xml
new file mode 100644
index 000000000..5940ccb8a
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-7/src/test/webapp/WEB-INF/web.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ testServlet
+ org.redisson.tomcat.TestServlet
+ 1
+
+
+
+ testServlet
+ /*
+
+
+
+ 30
+
+
+
diff --git a/redisson-tomcat/redisson-tomcat-8/pom.xml b/redisson-tomcat/redisson-tomcat-8/pom.xml
new file mode 100644
index 000000000..448561403
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-8/pom.xml
@@ -0,0 +1,81 @@
+
+ 4.0.0
+
+
+ org.redisson
+ redisson-tomcat
+ 2.8.1-SNAPSHOT
+ ../
+
+
+ redisson-tomcat-8
+ jar
+
+ Redisson/Tomcat-8
+
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-core
+ 8.0.39
+ provided
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-logging-juli
+ 8.0.39
+ provided
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-jasper
+ 8.0.39
+ provided
+
+
+ org.apache.tomcat
+ tomcat-jasper
+ 8.0.39
+ provided
+
+
+
+
+
+
+ com.mycila
+ license-maven-plugin
+ 3.0
+
+ ${basedir}
+ ${basedir}/../../header.txt
+ false
+ true
+ false
+
+ src/main/java/org/redisson/
+
+
+ target/**
+
+ true
+
+ JAVADOC_STYLE
+
+ true
+ true
+ UTF-8
+
+
+
+
+ check
+
+
+
+
+
+
+
+
+
diff --git a/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSession.java b/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSession.java
new file mode 100644
index 000000000..a8981b95b
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSession.java
@@ -0,0 +1,187 @@
+/**
+ * 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.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+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 final Map attrs;
+ private RMap map;
+
+ public RedissonSession(RedissonSessionManager manager) {
+ super(manager);
+ this.redissonManager = manager;
+
+ try {
+ Field attr = StandardSession.class.getDeclaredField("attributes");
+ attrs = (Map) attr.get(this);
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ 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 newMap = new HashMap(3);
+ newMap.put("session:creationTime", creationTime);
+ newMap.put("session:lastAccessedTime", lastAccessedTime);
+ newMap.put("session:thisAccessedTime", thisAccessedTime);
+ map.putAll(newMap);
+ }
+ }
+
+ @Override
+ public void access() {
+ super.access();
+
+ if (map != null) {
+ Map newMap = new HashMap(2);
+ newMap.put("session:lastAccessedTime", lastAccessedTime);
+ newMap.put("session:thisAccessedTime", thisAccessedTime);
+ map.putAll(newMap);
+ 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 (map != null) {
+ map.fastRemove(name);
+ }
+ }
+
+ public void save() {
+ Map newMap = new HashMap();
+ newMap.put("session:creationTime", creationTime);
+ newMap.put("session:lastAccessedTime", lastAccessedTime);
+ newMap.put("session:thisAccessedTime", thisAccessedTime);
+ newMap.put("session:maxInactiveInterval", maxInactiveInterval);
+ newMap.put("session:isValid", isValid);
+ newMap.put("session:isNew", isNew);
+
+ for (Entry entry : attrs.entrySet()) {
+ newMap.put(entry.getKey(), entry.getValue());
+ }
+
+ map.putAll(newMap);
+
+ if (maxInactiveInterval >= 0) {
+ map.expire(getMaxInactiveInterval(), TimeUnit.SECONDS);
+ }
+ }
+
+ public void load() {
+ Set> entrySet = map.readAllEntrySet();
+ for (Entry 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);
+ }
+ }
+ }
+
+}
diff --git a/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSessionManager.java b/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSessionManager.java
new file mode 100644
index 000000000..87d41f81a
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-8/src/main/java/org/redisson/tomcat/RedissonSessionManager.java
@@ -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 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);
+ }
+
+ }
+
+}
diff --git a/redisson-tomcat/redisson-tomcat-8/src/test/java/org/redisson/tomcat/RedissonSessionManagerTest.java b/redisson-tomcat/redisson-tomcat-8/src/test/java/org/redisson/tomcat/RedissonSessionManagerTest.java
new file mode 100644
index 000000000..2d260c587
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-8/src/test/java/org/redisson/tomcat/RedissonSessionManagerTest.java
@@ -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);
+ }
+
+}
diff --git a/redisson-tomcat/redisson-tomcat-8/src/test/java/org/redisson/tomcat/TestServlet.java b/redisson-tomcat/redisson-tomcat-8/src/test/java/org/redisson/tomcat/TestServlet.java
new file mode 100644
index 000000000..1c68da965
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-8/src/test/java/org/redisson/tomcat/TestServlet.java
@@ -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");
+ }
+ }
+
+}
diff --git a/redisson-tomcat/redisson-tomcat-8/src/test/java/org/redisson/tomcat/TomcatServer.java b/redisson-tomcat/redisson-tomcat-8/src/test/java/org/redisson/tomcat/TomcatServer.java
new file mode 100644
index 000000000..6ec7af3b6
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-8/src/test/java/org/redisson/tomcat/TomcatServer.java
@@ -0,0 +1,65 @@
+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();
+ tomcat.getServer().await();
+ isRunning = false;
+ }
+
+ public boolean isRunning() {
+ return isRunning;
+ }
+
+}
\ No newline at end of file
diff --git a/redisson-tomcat/redisson-tomcat-8/src/test/webapp/META-INF/context.xml b/redisson-tomcat/redisson-tomcat-8/src/test/webapp/META-INF/context.xml
new file mode 100644
index 000000000..52b0eafd8
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-8/src/test/webapp/META-INF/context.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/redisson-tomcat/redisson-tomcat-8/src/test/webapp/WEB-INF/redisson.yaml b/redisson-tomcat/redisson-tomcat-8/src/test/webapp/WEB-INF/redisson.yaml
new file mode 100644
index 000000000..28466f3ba
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-8/src/test/webapp/WEB-INF/redisson.yaml
@@ -0,0 +1,2 @@
+singleServerConfig:
+ address: "redis://127.0.0.1:6379"
\ No newline at end of file
diff --git a/redisson-tomcat/redisson-tomcat-8/src/test/webapp/WEB-INF/web.xml b/redisson-tomcat/redisson-tomcat-8/src/test/webapp/WEB-INF/web.xml
new file mode 100644
index 000000000..5940ccb8a
--- /dev/null
+++ b/redisson-tomcat/redisson-tomcat-8/src/test/webapp/WEB-INF/web.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ testServlet
+ org.redisson.tomcat.TestServlet
+ 1
+
+
+
+ testServlet
+ /*
+
+
+
+ 30
+
+
+
diff --git a/redisson/header.txt b/redisson/header.txt
deleted file mode 100644
index ac956a4f9..000000000
--- a/redisson/header.txt
+++ /dev/null
@@ -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.
diff --git a/redisson/pom.xml b/redisson/pom.xml
index 9ac0cc7a0..32222c03e 100644
--- a/redisson/pom.xml
+++ b/redisson/pom.xml
@@ -4,7 +4,7 @@
org.redisson
redisson-parent
- 2.5.0-SNAPSHOT
+ 2.8.1-SNAPSHOT
../
@@ -21,13 +21,6 @@
http://redisson.org/
-
- true
- 1.6
- 1.8
- UTF-8
-
-
unit-test
@@ -41,35 +34,40 @@
io.netty
netty-transport-native-epoll
- 4.0.41.Final
+ 4.1.8.Final
provided
io.netty
netty-common
- 4.0.41.Final
+ 4.1.8.Final
io.netty
netty-codec
- 4.0.41.Final
+ 4.1.8.Final
io.netty
netty-buffer
- 4.0.41.Final
+ 4.1.8.Final
io.netty
netty-transport
- 4.0.41.Final
+ 4.1.8.Final
io.netty
netty-handler
- 4.0.41.Final
+ 4.1.8.Final
+
+ javax.cache
+ cache-api
+ 1.0.0
+
io.projectreactor
reactor-stream
@@ -107,6 +105,43 @@
test
+
+ org.apache.tomcat.embed
+ tomcat-embed-core
+ 7.0.73
+ test
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-logging-juli
+ 7.0.73
+ test
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-jasper
+ 7.0.73
+ test
+
+
+ org.apache.tomcat
+ tomcat-jasper
+ 7.0.73
+ test
+
+
+ org.apache.httpcomponents
+ fluent-hc
+ 4.5.2
+ test
+
+
+ org.springframework
+ spring-web
+ [3.1,)
+ test
+
+
net.jpountz.lz4
lz4
@@ -203,7 +238,12 @@
[3.1,)
provided
-
+
+ org.springframework.session
+ spring-session
+ 1.2.2.RELEASE
+ provided
+
@@ -359,7 +399,7 @@
org.apache.felix
maven-bundle-plugin
- 3.0.1
+ 3.2.0
true
@@ -375,7 +415,7 @@
2.11
${basedir}
-
+
false
true
false
diff --git a/redisson/src/main/java/org/redisson/BaseRemoteService.java b/redisson/src/main/java/org/redisson/BaseRemoteService.java
index 565de722d..31695cec9 100644
--- a/redisson/src/main/java/org/redisson/BaseRemoteService.java
+++ b/redisson/src/main/java/org/redisson/BaseRemoteService.java
@@ -20,7 +20,9 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import org.redisson.api.RBlockingQueue;
@@ -184,7 +186,7 @@ public abstract class BaseRemoteService {
final RBlockingQueue requestQueue = redisson.getBlockingQueue(requestQueueName,
getCodec());
- final RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), args,
+ final RemoteServiceRequest request = new RemoteServiceRequest(requestId, method.getName(), getMethodSignatures(method), args,
optionsCopy, System.currentTimeMillis());
final RemotePromise