├── .gitignore
├── README.md
├── pom.xml
└── src
└── main
├── java
└── ru
│ └── zinin
│ └── redis
│ ├── factory
│ └── JedisPoolFactory.java
│ ├── session
│ ├── RedisEventListenerThread.java
│ ├── RedisHttpSession.java
│ ├── RedisManager.java
│ ├── RedisSessionKeys.java
│ ├── RedisSessionTemplate.java
│ └── event
│ │ ├── RedisSessionAddAttributeEvent.java
│ │ ├── RedisSessionAttributeEvent.java
│ │ ├── RedisSessionCreatedEvent.java
│ │ ├── RedisSessionDestroyedEvent.java
│ │ ├── RedisSessionEvent.java
│ │ ├── RedisSessionRemoveAttributeEvent.java
│ │ └── RedisSessionReplaceAttributeEvent.java
│ └── util
│ ├── Base64Util.java
│ ├── CustomObjectInputStream.java
│ └── RedisSerializationUtil.java
└── resources
└── META-INF
├── LICENCE
└── NOTICE
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.jar
3 | *.war
4 | *.ear
5 | *.iml
6 | .idea
7 | target
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tomcat Redis Session
2 |
3 | ## Introduction
4 | Tomcat Redis Session is an implementation of
5 | [Tomcat Manager Component](http://tomcat.apache.org/tomcat-7.0-doc/config/manager.html)
6 | using [Redis](http://redis.io/) key-value store.
7 |
8 | ## Standalone configuration
9 |
10 | First you must download the following dependencies:
11 |
12 | - [Commons Pool 2](http://commons.apache.org/pool/)
13 | - [Jedis](https://github.com/xetorthio/jedis)
14 |
15 | Downloaded jar files put in CATALINA.HOME/lib folder or your web application lib folder (WEB-INF/lib).
16 |
17 | Configure global context (CATALINA.HOME/conf/context.xml) or you web application context (META-INF/context.xml) for using Tomcat Redis Session Manager by inserting this line:
18 |
19 | ```xml
20 |
21 | ```
22 |
23 | By default RedisManager looking for "pool/jedis" by JNDI. You can override this by adding property "jedisJndiName".
24 |
25 | ```xml
26 |
27 | ```
28 |
29 | ## Embedded configuration
30 |
31 | See our [Embedded example](https://github.com/zinin/tomcat-redis-session-example)
32 |
33 | Add tomcat-redis-session as maven dependency:
34 |
35 | ```xml
36 |
37 | ru.zinin
38 | tomcat-redis-session
39 | 0.7
40 |
41 | ```
42 |
43 | Use it:
44 |
45 | ```java
46 | RedisManager redisManager = new RedisManager();
47 | redisManager.setDisableListeners(true);
48 | ctx.setManager(redisManager);
49 | ```
50 |
51 | ## Contacts
52 |
53 | If you have questions you can [mail me](mailto:mail@zinin.ru)
54 |
55 | [Bug tracker](https://github.com/zinin/tomcat-redis-session/issues?state=open)
56 |
57 | ## License
58 | Copyright 2011 Alexander V. Zinin
59 |
60 | Licensed under the Apache License, Version 2.0 (the "License");
61 | you may not use this file except in compliance with the License.
62 | You may obtain a copy of the License at
63 |
64 | http://www.apache.org/licenses/LICENSE-2.0
65 |
66 | Unless required by applicable law or agreed to in writing, software
67 | distributed under the License is distributed on an "AS IS" BASIS,
68 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
69 | See the License for the specific language governing permissions and
70 | limitations under the License.
71 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | org.sonatype.oss
4 | oss-parent
5 | 9
6 |
7 |
8 | 4.0.0
9 | ru.zinin
10 | tomcat-redis-session
11 | jar
12 | Redis HttpSession for Tomcat
13 | 0.9-SNAPSHOT
14 | Redis HttpSession Implementation for Tomcat
15 | https://github.com/zinin/tomcat-redis-session
16 |
17 |
18 |
19 | The Apache Software License, Version 2.0
20 | http://www.apache.org/licenses/LICENSE-2.0.txt
21 | repo
22 |
23 |
24 |
25 |
26 | scm:git|ssh://git@github.com/zinin/tomcat-redis-session.git
27 | scm:git|ssh://git@github.com/zinin/tomcat-redis-session.git
28 | scm:git|ssh://git@github.com/zinin/tomcat-redis-session.git
29 | HEAD
30 |
31 |
32 |
33 | guthub
34 | https://github.com/zinin/tomcat-redis-session/issues
35 |
36 |
37 |
38 |
39 | zinin
40 | Alexander V. Zinin
41 | mail@zinin.ru
42 |
43 |
44 |
45 |
46 | utf-8
47 | 7.0.57
48 |
49 |
50 |
51 |
52 | redis.clients
53 | jedis
54 | 2.6.2
55 |
56 |
57 |
58 | org.apache.commons
59 | commons-pool2
60 | 2.3
61 |
62 |
63 |
64 | org.apache.tomcat
65 | tomcat-servlet-api
66 | ${tomcat.version}
67 | provided
68 |
69 |
70 | org.apache.tomcat
71 | tomcat-catalina
72 | ${tomcat.version}
73 | provided
74 |
75 |
76 | org.apache.tomcat
77 | tomcat-jasper
78 | ${tomcat.version}
79 | provided
80 |
81 |
82 |
83 |
84 |
85 |
86 | org.apache.maven.plugins
87 | maven-clean-plugin
88 | 2.6.1
89 |
90 |
91 |
92 | org.apache.maven.plugins
93 | maven-compiler-plugin
94 | 3.2
95 |
96 | 1.6
97 | 1.6
98 | utf-8
99 | true
100 | true
101 | true
102 | true
103 | true
104 | 512m
105 |
106 |
107 |
108 |
109 | org.apache.maven.plugins
110 | maven-resources-plugin
111 | 2.7
112 |
113 | utf-8
114 |
115 |
116 |
117 |
118 | org.apache.maven.plugins
119 | maven-source-plugin
120 | 2.4
121 |
122 |
123 | attach-sources
124 | compile
125 |
126 | jar-no-fork
127 |
128 |
129 |
130 |
131 |
132 |
133 | org.apache.maven.plugins
134 | maven-gpg-plugin
135 | 1.6
136 |
137 |
138 | sign-artifacts
139 | verify
140 |
141 | sign
142 |
143 |
144 |
145 |
146 |
147 |
148 | org.apache.maven.plugins
149 | maven-javadoc-plugin
150 | 2.10.1
151 |
152 | private
153 | true
154 |
155 |
156 |
157 | atach-javadoc
158 | compile
159 |
160 | jar
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | org.apache.maven.plugins
169 | maven-release-plugin
170 | 2.5.1
171 |
172 |
173 |
174 |
175 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/factory/JedisPoolFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.factory;
18 |
19 | import org.apache.juli.logging.Log;
20 | import org.apache.juli.logging.LogFactory;
21 | import redis.clients.jedis.JedisPool;
22 | import redis.clients.jedis.JedisPoolConfig;
23 | import redis.clients.jedis.Protocol;
24 |
25 | import javax.naming.Context;
26 | import javax.naming.Name;
27 | import javax.naming.RefAddr;
28 | import javax.naming.Reference;
29 | import javax.naming.spi.ObjectFactory;
30 | import java.util.Hashtable;
31 |
32 | /**
33 | *
ObjectFactory for creation JedisPool objects.
34 | * Support properties:
35 | *
36 | * - host - redis server hostname, "localhost" by default
37 | * - port - redis server port, {@link redis.clients.jedis.Protocol#DEFAULT_PORT} by default
38 | * - timeout - connection timeout in ms, {@link redis.clients.jedis.Protocol#DEFAULT_TIMEOUT} by default
39 | * - database - connection timeout in ms, {@link redis.clients.jedis.Protocol#DEFAULT_DATABASE} by default
40 | * - password - redis server password
41 | *
42 | *
43 | * Date: 30.10.11 22:15
44 | *
45 | * @author Alexander V. Zinin (mail@zinin.ru)
46 | */
47 | public class JedisPoolFactory implements ObjectFactory {
48 | private final Log log = LogFactory.getLog(JedisPoolFactory.class);
49 |
50 | /** {@inheritDoc} */
51 | @Override
52 | public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable, ?> environment) throws Exception {
53 | if ((obj == null) || !(obj instanceof Reference)) {
54 | return (null);
55 | }
56 | Reference ref = (Reference) obj;
57 | if (!"redis.clients.jedis.JedisPool".equals(ref.getClassName())) {
58 | return (null);
59 | }
60 |
61 | String host = "localhost";
62 | int port = Protocol.DEFAULT_PORT;
63 | int timeout = Protocol.DEFAULT_TIMEOUT;
64 | int database = Protocol.DEFAULT_DATABASE;
65 | String password = null;
66 |
67 | RefAddr hostRefAddr = ref.get("host");
68 | if (hostRefAddr != null) {
69 | host = hostRefAddr.getContent().toString();
70 | }
71 | RefAddr portRefAddr = ref.get("port");
72 | if (portRefAddr != null) {
73 | port = Integer.parseInt(portRefAddr.getContent().toString());
74 | }
75 | RefAddr timeoutRefAddr = ref.get("timeout");
76 | if (timeoutRefAddr != null) {
77 | timeout = Integer.parseInt(timeoutRefAddr.getContent().toString());
78 | }
79 | RefAddr databaseRefAddr = ref.get("database");
80 | if (databaseRefAddr != null) {
81 | database = Integer.parseInt(databaseRefAddr.getContent().toString());
82 | }
83 | RefAddr passwordRefAddr = ref.get("password");
84 | if (passwordRefAddr != null) {
85 | password = passwordRefAddr.getContent().toString();
86 | }
87 |
88 | log.debug("Creating pool...");
89 | log.debug("Host: " + host);
90 | log.debug("Port: " + port);
91 | log.debug("Timeout: " + timeout);
92 | log.trace("Password: " + password);
93 |
94 | JedisPoolConfig config = new JedisPoolConfig();
95 | return new JedisPool(config, host, port, timeout, password, database);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/session/RedisEventListenerThread.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.session;
18 |
19 | import org.apache.catalina.Context;
20 | import org.apache.catalina.core.StandardContext;
21 | import org.apache.catalina.session.Constants;
22 | import org.apache.jasper.util.ExceptionUtils;
23 | import org.apache.juli.logging.Log;
24 | import org.apache.juli.logging.LogFactory;
25 | import org.apache.tomcat.util.res.StringManager;
26 | import redis.clients.jedis.Jedis;
27 | import redis.clients.jedis.JedisPubSub;
28 | import ru.zinin.redis.session.event.*;
29 | import ru.zinin.redis.util.Base64Util;
30 | import ru.zinin.redis.util.RedisSerializationUtil;
31 |
32 | import javax.servlet.http.HttpSessionAttributeListener;
33 | import javax.servlet.http.HttpSessionBindingEvent;
34 | import javax.servlet.http.HttpSessionEvent;
35 | import javax.servlet.http.HttpSessionListener;
36 | import java.util.concurrent.atomic.AtomicBoolean;
37 |
38 | /**
39 | * Date: 01.11.11 20:43
40 | *
41 | * @author Alexander V. Zinin (mail@zinin.ru)
42 | */
43 | public class RedisEventListenerThread implements Runnable {
44 | private final Log log = LogFactory.getLog(RedisEventListenerThread.class);
45 |
46 | private static final StringManager sm = StringManager.getManager(Constants.Package);
47 |
48 | private RedisManager manager;
49 |
50 | private AtomicBoolean stopFlag = new AtomicBoolean(false);
51 |
52 | private JedisPubSub pubSub = new JedisPubSub() {
53 | @Override
54 | public void onMessage(String channel, String message) {
55 | byte[] bytes = Base64Util.decode(message);
56 |
57 | RedisSessionEvent event = RedisSerializationUtil.decode(bytes, manager.getContainerClassLoader());
58 |
59 | log.debug("Event from " + channel + ": " + event);
60 |
61 | if (event instanceof RedisSessionCreatedEvent) {
62 | sessionCreatedEvent((RedisSessionCreatedEvent) event);
63 | } else if (event instanceof RedisSessionDestroyedEvent) {
64 | sessionDestroyedEvent((RedisSessionDestroyedEvent) event);
65 | } else if (event instanceof RedisSessionAddAttributeEvent) {
66 | sessionAttributeAddEvent((RedisSessionAddAttributeEvent) event);
67 | } else if (event instanceof RedisSessionRemoveAttributeEvent) {
68 | sessionAttributeRemoveEvent((RedisSessionRemoveAttributeEvent) event);
69 | } else if (event instanceof RedisSessionReplaceAttributeEvent) {
70 | sessionAttributeReplaceEvent((RedisSessionReplaceAttributeEvent) event);
71 | }
72 | }
73 |
74 | @Override
75 | public void onPMessage(String pattern, String channel, String message) {
76 | log.debug("PMessage " + pattern + " from " + channel + ": " + message);
77 | }
78 |
79 | @Override
80 | public void onSubscribe(String channel, int subscribedChannels) {
81 | log.debug("Subscribe: " + channel + "; count: " + subscribedChannels);
82 | }
83 |
84 | @Override
85 | public void onUnsubscribe(String channel, int subscribedChannels) {
86 | log.debug("Unsubscribe: " + channel + "; count: " + subscribedChannels);
87 | }
88 |
89 | @Override
90 | public void onPUnsubscribe(String pattern, int subscribedChannels) {
91 | log.debug("PUnsubscribe: " + pattern + "; count: " + subscribedChannels);
92 | }
93 |
94 | @Override
95 | public void onPSubscribe(String pattern, int subscribedChannels) {
96 | log.debug("PSubscribe: " + pattern + "; count: " + subscribedChannels);
97 | }
98 | };
99 |
100 | public RedisEventListenerThread(RedisManager manager) {
101 | this.manager = manager;
102 | }
103 |
104 | @Override
105 | public void run() {
106 | while (!stopFlag.get()) {
107 | Jedis jedis = manager.getPool().getResource();
108 | try {
109 | String sessionChannel = RedisSessionKeys.getSessionChannel(manager.getContainer().getName());
110 | log.debug("Subscribed to " + sessionChannel);
111 | jedis.subscribe(pubSub, sessionChannel);
112 | log.debug("Done.");
113 |
114 | manager.getPool().returnResource(jedis);
115 | } catch (Throwable e) {
116 | manager.getPool().returnBrokenResource(jedis);
117 | log.error(e.getMessage(), e);
118 |
119 | try {
120 | Thread.sleep(1000);
121 | } catch (InterruptedException e1) {
122 | log.error(e.getMessage(), e);
123 | }
124 | }
125 | }
126 |
127 | log.debug("Exiting from listener thread...");
128 | }
129 |
130 | public void stop() {
131 | stopFlag.set(true);
132 | pubSub.unsubscribe();
133 | }
134 |
135 | private void fireContainerEvent(Context context, String type, Object data) throws Exception {
136 | if (context instanceof StandardContext) {
137 | context.fireContainerEvent(type, data);
138 | }
139 | }
140 |
141 | private void sessionCreatedEvent(RedisSessionCreatedEvent sessionCreatedEvent) {
142 | Context context = (Context) manager.getContainer();
143 | Object listeners[] = context.getApplicationLifecycleListeners();
144 |
145 | if (listeners != null) {
146 | RedisHttpSession session = new RedisHttpSession(sessionCreatedEvent.getId(), manager);
147 |
148 | HttpSessionEvent event = new HttpSessionEvent(session);
149 | //noinspection ForLoopReplaceableByForEach
150 | for (int i = 0; i < listeners.length; i++) {
151 | if (!(listeners[i] instanceof HttpSessionListener)) {
152 | continue;
153 | }
154 | HttpSessionListener listener = (HttpSessionListener) listeners[i];
155 | try {
156 | fireContainerEvent(context, "beforeSessionCreated", listener);
157 |
158 | listener.sessionCreated(event);
159 |
160 | fireContainerEvent(context, "afterSessionCreated", listener);
161 | } catch (Throwable t) {
162 | ExceptionUtils.handleThrowable(t);
163 | try {
164 | fireContainerEvent(context, "afterSessionCreated", listener);
165 | } catch (Exception e) {
166 | // Ignore
167 | }
168 | manager.getContainer().getLogger().error(sm.getString("standardSession.sessionEvent"), t);
169 | }
170 | }
171 | }
172 | }
173 |
174 | private void sessionDestroyedEvent(RedisSessionDestroyedEvent sessionDestroyedEvent) {
175 | Context context = (Context) manager.getContainer();
176 | Object listeners[] = context.getApplicationLifecycleListeners();
177 |
178 | if (listeners != null) {
179 | RedisHttpSession session = new RedisHttpSession(sessionDestroyedEvent.getId(), manager);
180 |
181 | HttpSessionEvent event = new HttpSessionEvent(session);
182 | for (int i = 0; i < listeners.length; i++) {
183 | int j = (listeners.length - 1) - i;
184 |
185 | if (!(listeners[j] instanceof HttpSessionListener)) {
186 | continue;
187 | }
188 |
189 | HttpSessionListener listener = (HttpSessionListener) listeners[j];
190 | try {
191 | fireContainerEvent(context, "beforeSessionDestroyed", listener);
192 |
193 | listener.sessionDestroyed(event);
194 |
195 | fireContainerEvent(context, "afterSessionDestroyed", listener);
196 | } catch (Throwable t) {
197 | ExceptionUtils.handleThrowable(t);
198 | try {
199 | fireContainerEvent(context, "afterSessionDestroyed", listener);
200 | } catch (Exception e) {
201 | // Ignore
202 | }
203 | manager.getContainer().getLogger().error(sm.getString("standardSession.sessionEvent"), t);
204 | }
205 | }
206 | }
207 | }
208 |
209 | private void sessionAttributeAddEvent(RedisSessionAddAttributeEvent sessionAddAttributeEvent) {
210 | Context context = (Context) manager.getContainer();
211 | Object listeners[] = context.getApplicationEventListeners();
212 | if (listeners != null) {
213 | RedisHttpSession session = new RedisHttpSession(sessionAddAttributeEvent.getId(), manager);
214 |
215 | //noinspection ForLoopReplaceableByForEach
216 | for (int i = 0; i < listeners.length; i++) {
217 | if (!(listeners[i] instanceof HttpSessionAttributeListener)) {
218 | continue;
219 | }
220 |
221 | HttpSessionAttributeListener listener = (HttpSessionAttributeListener) listeners[i];
222 | try {
223 | fireContainerEvent(context, "beforeSessionAttributeAdded", listener);
224 | HttpSessionBindingEvent event = new HttpSessionBindingEvent(session, sessionAddAttributeEvent.getName(), sessionAddAttributeEvent.getValue());
225 | listener.attributeAdded(event);
226 | fireContainerEvent(context, "afterSessionAttributeAdded", listener);
227 | } catch (Throwable t) {
228 | ExceptionUtils.handleThrowable(t);
229 | try {
230 | fireContainerEvent(context, "afterSessionAttributeAdded", listener);
231 | } catch (Exception e) {
232 | // Ignore
233 | }
234 | manager.getContainer().getLogger().error(sm.getString("standardSession.attributeEvent"), t);
235 | }
236 | }
237 | }
238 | }
239 |
240 | private void sessionAttributeRemoveEvent(RedisSessionRemoveAttributeEvent sessionRemoveAttributeEvent) {
241 | Context context = (Context) manager.getContainer();
242 | Object listeners[] = context.getApplicationEventListeners();
243 | if (listeners != null) {
244 | RedisHttpSession session = new RedisHttpSession(sessionRemoveAttributeEvent.getId(), manager);
245 |
246 | //noinspection ForLoopReplaceableByForEach
247 | for (int i = 0; i < listeners.length; i++) {
248 | if (!(listeners[i] instanceof HttpSessionAttributeListener)) {
249 | continue;
250 | }
251 |
252 | HttpSessionAttributeListener listener = (HttpSessionAttributeListener) listeners[i];
253 | try {
254 | fireContainerEvent(context, "beforeSessionAttributeRemoved", listener);
255 | HttpSessionBindingEvent event = new HttpSessionBindingEvent(session, sessionRemoveAttributeEvent.getName(), sessionRemoveAttributeEvent.getValue());
256 | listener.attributeRemoved(event);
257 | fireContainerEvent(context, "afterSessionAttributeRemoved", listener);
258 | } catch (Throwable t) {
259 | ExceptionUtils.handleThrowable(t);
260 | try {
261 | fireContainerEvent(context, "afterSessionAttributeRemoved", listener);
262 | } catch (Exception e) {
263 | // Ignore
264 | }
265 | manager.getContainer().getLogger().error(sm.getString("standardSession.attributeEvent"), t);
266 | }
267 | }
268 | }
269 | }
270 |
271 | private void sessionAttributeReplaceEvent(RedisSessionReplaceAttributeEvent sessionReplaceAttributeEvent) {
272 | Context context = (Context) manager.getContainer();
273 | Object listeners[] = context.getApplicationEventListeners();
274 | if (listeners != null) {
275 | RedisHttpSession session = new RedisHttpSession(sessionReplaceAttributeEvent.getId(), manager);
276 |
277 | //noinspection ForLoopReplaceableByForEach
278 | for (int i = 0; i < listeners.length; i++) {
279 | if (!(listeners[i] instanceof HttpSessionAttributeListener)) {
280 | continue;
281 | }
282 |
283 | HttpSessionAttributeListener listener = (HttpSessionAttributeListener) listeners[i];
284 | try {
285 | fireContainerEvent(context, "beforeSessionAttributeReplaced", listener);
286 | HttpSessionBindingEvent event = new HttpSessionBindingEvent(session, sessionReplaceAttributeEvent.getName(), sessionReplaceAttributeEvent.getValue());
287 | listener.attributeReplaced(event);
288 | fireContainerEvent(context, "afterSessionAttributeReplaced", listener);
289 | } catch (Throwable t) {
290 | ExceptionUtils.handleThrowable(t);
291 | try {
292 | fireContainerEvent(context, "afterSessionAttributeReplaced", listener);
293 | } catch (Exception e) {
294 | // Ignore
295 | }
296 | manager.getContainer().getLogger().error(sm.getString("standardSession.attributeEvent"), t);
297 | }
298 | }
299 | }
300 | }
301 | }
302 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/session/RedisHttpSession.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.session;
18 |
19 | import org.apache.catalina.Context;
20 | import org.apache.catalina.Manager;
21 | import org.apache.catalina.Session;
22 | import org.apache.catalina.SessionListener;
23 | import org.apache.catalina.util.Enumerator;
24 | import org.apache.juli.logging.Log;
25 | import org.apache.juli.logging.LogFactory;
26 | import redis.clients.jedis.Jedis;
27 | import redis.clients.jedis.JedisPool;
28 | import redis.clients.jedis.Transaction;
29 | import ru.zinin.redis.session.event.*;
30 | import ru.zinin.redis.util.Base64Util;
31 | import ru.zinin.redis.util.RedisSerializationUtil;
32 |
33 | import javax.servlet.ServletContext;
34 | import javax.servlet.http.HttpSession;
35 | import javax.servlet.http.HttpSessionContext;
36 | import java.beans.PropertyChangeSupport;
37 | import java.io.Serializable;
38 | import java.security.Principal;
39 | import java.util.*;
40 | import java.util.concurrent.atomic.AtomicBoolean;
41 |
42 | /**
43 | * Date: 28.10.11 21:44
44 | *
45 | * @author Alexander V. Zinin (mail@zinin.ru)
46 | */
47 | public class RedisHttpSession implements HttpSession, Session, Serializable {
48 | private final Log log = LogFactory.getLog(RedisHttpSession.class);
49 |
50 | private static final String info = "RedisSession/1.0";
51 |
52 | private String id;
53 |
54 | private RedisManager manager;
55 | private JedisPool pool;
56 | private ServletContext servletContext;
57 | private boolean disableListeners = false;
58 |
59 | private String authType;
60 | private Principal principal;
61 | private Map notes = new Hashtable();
62 |
63 | private AtomicBoolean isNew = new AtomicBoolean(false);
64 |
65 | private PropertyChangeSupport support = new PropertyChangeSupport(this);
66 |
67 | RedisHttpSession(String id, RedisManager manager) {
68 | log.trace("Create session [OLD]");
69 |
70 | this.id = id;
71 | setManager(manager);
72 | }
73 |
74 | RedisHttpSession(String id, JedisPool pool, ServletContext servletContext, boolean disableListeners) {
75 | log.trace("Create session [OLD] from RedisSessionTemplate.");
76 |
77 | this.id = id;
78 | this.pool = pool;
79 | this.servletContext = servletContext;
80 | this.disableListeners = disableListeners;
81 | }
82 |
83 | RedisHttpSession(String id, RedisManager manager, int maxInactiveInterval) {
84 | log.trace("Create session [NEW]. maxInactiveInterval = " + maxInactiveInterval);
85 |
86 | this.id = id;
87 | setManager(manager);
88 |
89 | isNew.set(true);
90 |
91 | String sessionsKey = RedisSessionKeys.getSessionsKey();
92 | String creationTimeKey = RedisSessionKeys.getCreationTimeKey(id);
93 | String lastAccessTimeKey = RedisSessionKeys.getLastAccessTimeKey(id);
94 | String expiresAtKey = RedisSessionKeys.getExpireAtKey(id);
95 | String timeoutKey = RedisSessionKeys.getSessionTimeoutKey(id);
96 |
97 | long currentTime = System.currentTimeMillis();
98 | long expireAtTime = currentTime + (maxInactiveInterval * 1000);
99 | long expireAtTimeWithReserve = currentTime + (maxInactiveInterval * 1000 * 2);
100 |
101 | Jedis jedis = pool.getResource();
102 | try {
103 | Transaction transaction = jedis.multi();
104 |
105 | transaction.set(creationTimeKey, Long.toString(currentTime));
106 | transaction.set(lastAccessTimeKey, Long.toString(currentTime));
107 | transaction.set(expiresAtKey, Long.toString(expireAtTimeWithReserve));
108 | transaction.set(timeoutKey, Integer.toString(maxInactiveInterval));
109 |
110 | transaction.expireAt(creationTimeKey, getUnixTime(expireAtTimeWithReserve));
111 | transaction.expireAt(lastAccessTimeKey, getUnixTime(expireAtTime));
112 | transaction.expireAt(expiresAtKey, getUnixTime(expireAtTimeWithReserve));
113 | transaction.expireAt(timeoutKey, getUnixTime(expireAtTimeWithReserve));
114 |
115 | transaction.zadd(sessionsKey, currentTime, id);
116 |
117 | transaction.exec();
118 |
119 | pool.returnResource(jedis);
120 | } catch (Throwable e) {
121 | pool.returnBrokenResource(jedis);
122 | throw new RuntimeException(e);
123 | }
124 |
125 | tellNew();
126 | }
127 |
128 | private long getUnixTime(long time) {
129 | return time / 1000;
130 | }
131 |
132 | public void tellNew() {
133 | if (!disableListeners) {
134 | Jedis jedis = pool.getResource();
135 | try {
136 | RedisSessionEvent redisSessionEvent = new RedisSessionCreatedEvent(id);
137 | byte[] bytes = RedisSerializationUtil.encode(redisSessionEvent);
138 | String message = new String(Base64Util.encode(bytes));
139 |
140 | jedis.publish(RedisSessionKeys.getSessionChannel(manager.getContainer().getName()), message);
141 |
142 | log.debug("Sended " + redisSessionEvent.toString());
143 |
144 | pool.returnResource(jedis);
145 | } catch (Throwable e) {
146 | pool.returnBrokenResource(jedis);
147 | throw new RuntimeException(e);
148 | }
149 | }
150 | }
151 |
152 | @Override
153 | public String getAuthType() {
154 | log.trace("EXEC getAuthType();");
155 |
156 | return authType;
157 | }
158 |
159 | @Override
160 | public void setAuthType(String authType) {
161 | log.trace(String.format("EXEC setAuthType(%s);", authType));
162 |
163 | String oldAuthType = this.authType;
164 | this.authType = authType;
165 |
166 | support.firePropertyChange("authType", oldAuthType, this.authType);
167 | }
168 |
169 | @Override
170 | public long getCreationTime() {
171 | log.trace("EXEC getCreationTime();");
172 |
173 | String key = RedisSessionKeys.getCreationTimeKey(id);
174 |
175 | String creationTime;
176 | Jedis jedis = pool.getResource();
177 | try {
178 | creationTime = jedis.get(key);
179 |
180 | pool.returnResource(jedis);
181 | } catch (Throwable e) {
182 | pool.returnBrokenResource(jedis);
183 | throw new RuntimeException(e);
184 | }
185 |
186 | if (creationTime == null) {
187 | throw new IllegalStateException("Can't get creation time from redis.");
188 | }
189 |
190 | return Long.parseLong(creationTime);
191 | }
192 |
193 | @Override
194 | public long getCreationTimeInternal() {
195 | log.trace("EXEC getCreationTimeInternal();");
196 |
197 | return getCreationTime();
198 | }
199 |
200 | @Override
201 | public void setCreationTime(long time) {
202 | log.trace(String.format("EXEC setAuthType(%d);", time));
203 |
204 | throw new UnsupportedOperationException("Can't set creation time");
205 | }
206 |
207 | @Override
208 | public String getId() {
209 | log.trace("EXEC getId();");
210 |
211 | return id;
212 | }
213 |
214 | @Override
215 | public String getIdInternal() {
216 | log.trace("EXEC getIdInternal();");
217 |
218 | return id;
219 | }
220 |
221 | @Override
222 | public void setId(String id) {
223 | log.trace(String.format("EXEC setId(%s);", id));
224 |
225 | setId(id, false);
226 | }
227 |
228 | @Override
229 | public void setId(String id, boolean notify) {
230 | log.trace(String.format("EXEC setId(%s, %s);", id, notify));
231 |
232 | String oldCreationTimeKey = RedisSessionKeys.getCreationTimeKey(this.id);
233 | String oldLastAccessTimeKey = RedisSessionKeys.getLastAccessTimeKey(this.id);
234 | String oldExpiresAtKey = RedisSessionKeys.getExpireAtKey(this.id);
235 | String oldTimeoutKey = RedisSessionKeys.getSessionTimeoutKey(this.id);
236 | String oldAttrsKey = RedisSessionKeys.getAttrsKey(this.id);
237 |
238 | String newCreationTimeKey = RedisSessionKeys.getCreationTimeKey(id);
239 | String newLastAccessTimeKey = RedisSessionKeys.getLastAccessTimeKey(id);
240 | String newExpiresAtKey = RedisSessionKeys.getExpireAtKey(id);
241 | String newTimeoutKey = RedisSessionKeys.getSessionTimeoutKey(id);
242 | String newAttrsKey = RedisSessionKeys.getAttrsKey(id);
243 |
244 | Set attributeNames = getAttributesNames();
245 |
246 | long lastAccessTime = getLastAccessedTime();
247 |
248 | Jedis jedis = pool.getResource();
249 | try {
250 | Transaction transaction = jedis.multi();
251 |
252 | transaction.rename(oldCreationTimeKey, newCreationTimeKey);
253 | transaction.rename(oldLastAccessTimeKey, newLastAccessTimeKey);
254 | transaction.rename(oldExpiresAtKey, newExpiresAtKey);
255 | transaction.rename(oldTimeoutKey, newTimeoutKey);
256 |
257 | if (attributeNames != null && !attributeNames.isEmpty()) {
258 | for (String attributeName : attributeNames) {
259 | String oldKey = RedisSessionKeys.getAttrKey(this.id, attributeName);
260 | String newKey = RedisSessionKeys.getAttrKey(id, attributeName);
261 | transaction.rename(oldKey, newKey);
262 | }
263 |
264 | transaction.rename(oldAttrsKey, newAttrsKey);
265 | } else {
266 | transaction.del(oldAttrsKey);
267 | }
268 |
269 | transaction.zadd(RedisSessionKeys.getSessionsKey(), lastAccessTime, id);
270 | transaction.zrem(RedisSessionKeys.getSessionsKey(), this.id);
271 |
272 | transaction.exec();
273 |
274 | pool.returnResource(jedis);
275 | } catch (Throwable e) {
276 | pool.returnBrokenResource(jedis);
277 | throw new RuntimeException(e);
278 | }
279 |
280 | this.id = id;
281 |
282 | if (notify) {
283 | tellNew();
284 | }
285 | }
286 |
287 | @Override
288 | public String getInfo() {
289 | log.trace("EXEC getInfo();");
290 |
291 | return info;
292 | }
293 |
294 | private Long getLastAccessTime() {
295 | String key = RedisSessionKeys.getLastAccessTimeKey(id);
296 |
297 | String lastAccessTime;
298 | Jedis jedis = pool.getResource();
299 | try {
300 | lastAccessTime = jedis.get(key);
301 |
302 | pool.returnResource(jedis);
303 | } catch (Throwable e) {
304 | pool.returnBrokenResource(jedis);
305 | throw new RuntimeException(e);
306 | }
307 |
308 | if (lastAccessTime == null) {
309 | return null;
310 | }
311 |
312 | return Long.parseLong(lastAccessTime);
313 | }
314 |
315 | @Override
316 | public long getThisAccessedTime() {
317 | log.trace("EXEC getThisAccessedTime();");
318 |
319 | return getLastAccessedTime();
320 | }
321 |
322 | @Override
323 | public long getThisAccessedTimeInternal() {
324 | log.trace("EXEC getThisAccessedTimeInternal();");
325 |
326 | return getLastAccessedTime();
327 | }
328 |
329 | @Override
330 | public long getLastAccessedTime() {
331 | log.trace("EXEC getLastAccessedTime();");
332 |
333 | Long lastAccessTime = getLastAccessTime();
334 |
335 | if (lastAccessTime == null) {
336 | throw new IllegalStateException("Can't get last access time from redis.");
337 | }
338 |
339 | return lastAccessTime;
340 | }
341 |
342 | @Override
343 | public long getLastAccessedTimeInternal() {
344 | log.trace("EXEC getLastAccessedTimeInternal();");
345 |
346 | return getLastAccessedTime();
347 | }
348 |
349 | @Override
350 | public Manager getManager() {
351 | log.trace("EXEC getManager();");
352 |
353 | return manager;
354 | }
355 |
356 | @Override
357 | public void setManager(Manager manager) {
358 | log.trace(String.format("EXEC setId(%s);", manager));
359 |
360 | this.manager = (RedisManager) manager;
361 | this.pool = this.manager.getPool();
362 | this.servletContext = ((Context) manager.getContainer()).getServletContext();
363 | this.disableListeners = this.manager.isDisableListeners();
364 | }
365 |
366 | @Override
367 | public ServletContext getServletContext() {
368 | log.trace("EXEC getServletContext();");
369 |
370 | return servletContext;
371 | }
372 |
373 | @Override
374 | public void setMaxInactiveInterval(int interval) {
375 | log.trace(String.format("EXEC setMaxInactiveInterval(%d);", interval));
376 |
377 | String key = RedisSessionKeys.getSessionTimeoutKey(id);
378 |
379 | Jedis jedis = pool.getResource();
380 | try {
381 | jedis.set(key, Integer.toString(interval));
382 |
383 | pool.returnResource(jedis);
384 | } catch (Throwable e) {
385 | pool.returnBrokenResource(jedis);
386 | throw new RuntimeException(e);
387 | }
388 |
389 | renewAll();
390 | }
391 |
392 | @Override
393 | public void setNew(boolean isNew) {
394 | log.trace(String.format("EXEC setNew(%s);", isNew));
395 |
396 | throw new UnsupportedOperationException("Can't set new");
397 | }
398 |
399 | @Override
400 | public Principal getPrincipal() {
401 | log.trace("EXEC getPrincipal();");
402 |
403 | return principal;
404 | }
405 |
406 | @Override
407 | public void setPrincipal(Principal principal) {
408 | log.trace(String.format("EXEC setPrincipal(%s);", principal));
409 |
410 | Principal oldPrincipal = this.principal;
411 | this.principal = principal;
412 |
413 | support.firePropertyChange("principal", oldPrincipal, this.principal);
414 | }
415 |
416 | @Override
417 | public HttpSession getSession() {
418 | log.trace("EXEC getSession();");
419 |
420 | return this;
421 | }
422 |
423 | @Override
424 | public void setValid(boolean isValid) {
425 | log.trace(String.format("EXEC setValid(%s);", isValid));
426 |
427 | throw new UnsupportedOperationException("Can't set valid.");
428 | }
429 |
430 | @Override
431 | public boolean isValid() {
432 | log.trace("EXEC isValid();");
433 |
434 | return !isAnyRequiredFieldNull();
435 | }
436 |
437 | private boolean isAnyRequiredFieldNull() {
438 | try {
439 | getLastAccessedTime();
440 | getExpireAt();
441 | getMaxInactiveInterval();
442 | } catch (IllegalStateException e) {
443 | return true;
444 | }
445 |
446 | return false;
447 | }
448 |
449 | private void renewAll() {
450 | String creationTimeKey = RedisSessionKeys.getCreationTimeKey(id);
451 | String lastAccessTimeKey = RedisSessionKeys.getLastAccessTimeKey(id);
452 | String expiresAtKey = RedisSessionKeys.getExpireAtKey(id);
453 | String timeoutKey = RedisSessionKeys.getSessionTimeoutKey(id);
454 | String attrsKey = RedisSessionKeys.getAttrsKey(id);
455 |
456 | Set attributeNames = null;
457 |
458 | long currentExpireAtTime = getExpireAt();
459 | long timeout = getMaxInactiveInterval();
460 |
461 | long currentTime = System.currentTimeMillis();
462 | long expireAtTime = currentTime + (timeout * 1000);
463 | long expireAtTimeWithReserve = currentTime + (timeout * 1000 * 2);
464 |
465 | if (currentExpireAtTime < expireAtTime) {
466 | attributeNames = getAttributesNames();
467 | }
468 |
469 | Jedis jedis = pool.getResource();
470 | try {
471 | Transaction transaction = jedis.multi();
472 |
473 | if (currentExpireAtTime < expireAtTime) {
474 | transaction.set(expiresAtKey, Long.toString(expireAtTimeWithReserve));
475 | transaction.expireAt(expiresAtKey, getUnixTime(expireAtTimeWithReserve));
476 |
477 | transaction.expireAt(creationTimeKey, getUnixTime(expireAtTimeWithReserve));
478 | transaction.expireAt(timeoutKey, getUnixTime(expireAtTimeWithReserve));
479 |
480 | if (attributeNames != null && !attributeNames.isEmpty()) {
481 | for (String attributeName : attributeNames) {
482 | String key = RedisSessionKeys.getAttrKey(id, attributeName);
483 | transaction.expireAt(key, getUnixTime(expireAtTimeWithReserve));
484 | }
485 |
486 | transaction.expireAt(attrsKey, getUnixTime(expireAtTimeWithReserve));
487 | }
488 | }
489 |
490 | transaction.set(lastAccessTimeKey, Long.toString(currentTime));
491 | transaction.expireAt(lastAccessTimeKey, getUnixTime(expireAtTime));
492 |
493 | transaction.zadd(RedisSessionKeys.getSessionsKey(), currentTime, id);
494 |
495 | transaction.exec();
496 |
497 | pool.returnResource(jedis);
498 | } catch (Throwable e) {
499 | pool.returnBrokenResource(jedis);
500 | throw new RuntimeException(e);
501 | }
502 | }
503 |
504 | @Override
505 | public void access() {
506 | log.trace("EXEC access();");
507 |
508 | renewAll();
509 | }
510 |
511 | @Override
512 | public void addSessionListener(SessionListener listener) {
513 | log.trace(String.format("EXEC addSessionListener(%s);", listener));
514 |
515 | throw new UnsupportedOperationException("Listeners are not supported.");
516 | }
517 |
518 | @Override
519 | public void endAccess() {
520 | log.trace("EXEC endAccess();");
521 |
522 | isNew.set(false);
523 | }
524 |
525 | @Override
526 | public void expire() {
527 | log.trace("EXEC expire();");
528 |
529 | String creationTimeKey = RedisSessionKeys.getCreationTimeKey(id);
530 | String lastAccessTimeKey = RedisSessionKeys.getLastAccessTimeKey(id);
531 | String expiresAtKey = RedisSessionKeys.getExpireAtKey(id);
532 | String timeoutKey = RedisSessionKeys.getSessionTimeoutKey(id);
533 | String attrsKey = RedisSessionKeys.getAttrsKey(id);
534 |
535 | Set attributeNames = getAttributesNames();
536 |
537 | Jedis jedis = pool.getResource();
538 | try {
539 | Transaction transaction = jedis.multi();
540 |
541 | transaction.del(creationTimeKey, lastAccessTimeKey, expiresAtKey, timeoutKey, attrsKey);
542 |
543 | if (!attributeNames.isEmpty()) {
544 | Set keys = new HashSet();
545 | for (String attributeName : attributeNames) {
546 | String key = RedisSessionKeys.getAttrKey(id, attributeName);
547 | keys.add(key);
548 | }
549 |
550 | //noinspection ToArrayCallWithZeroLengthArrayArgument
551 | transaction.del(keys.toArray(new String[]{}));
552 | }
553 |
554 | if (!disableListeners) {
555 | RedisSessionEvent redisSessionEvent = new RedisSessionDestroyedEvent(id);
556 | byte[] bytes = RedisSerializationUtil.encode(redisSessionEvent);
557 | String message = new String(Base64Util.encode(bytes));
558 |
559 | transaction.publish(RedisSessionKeys.getSessionChannel(manager.getContainer().getName()), message);
560 | }
561 |
562 | transaction.exec();
563 |
564 | pool.returnResource(jedis);
565 | } catch (Throwable e) {
566 | pool.returnBrokenResource(jedis);
567 | throw new RuntimeException(e);
568 | }
569 | }
570 |
571 | @Override
572 | public Object getNote(String name) {
573 | log.trace(String.format("EXEC getNote(%s);", name));
574 |
575 | return notes.get(name);
576 | }
577 |
578 | @Override
579 | public Iterator getNoteNames() {
580 | log.trace("EXEC getNoteNames();");
581 |
582 | return notes.keySet().iterator();
583 | }
584 |
585 | @Override
586 | public void recycle() {
587 | log.trace("EXEC recycle();");
588 |
589 | throw new IllegalStateException("Recycle is not supported");
590 | }
591 |
592 | @Override
593 | public void removeNote(String name) {
594 | log.trace(String.format("EXEC removeNote(%s);", name));
595 |
596 | notes.remove(name);
597 | }
598 |
599 | @Override
600 | public void removeSessionListener(SessionListener listener) {
601 | log.trace(String.format("EXEC removeSessionListener(%s);", listener));
602 |
603 | throw new UnsupportedOperationException("Listeners are not supported.");
604 | }
605 |
606 | @Override
607 | public void setNote(String name, Object value) {
608 | log.trace(String.format("EXEC setNote(%s, %s);", name, value));
609 |
610 | notes.put(name, value);
611 | }
612 |
613 | @Override
614 | public int getMaxInactiveInterval() {
615 | log.trace("EXEC getMaxInactiveInterval();");
616 |
617 | String key = RedisSessionKeys.getSessionTimeoutKey(id);
618 |
619 | String sessionTimeout;
620 | Jedis jedis = pool.getResource();
621 | try {
622 | sessionTimeout = jedis.get(key);
623 |
624 | pool.returnResource(jedis);
625 | } catch (Throwable e) {
626 | pool.returnBrokenResource(jedis);
627 | throw new RuntimeException(e);
628 | }
629 |
630 | if (sessionTimeout == null) {
631 | throw new IllegalStateException("Can't get session timeout from redis.");
632 | }
633 |
634 | return Integer.parseInt(sessionTimeout);
635 | }
636 |
637 | @SuppressWarnings({"deprecation"})
638 | @Override
639 | public HttpSessionContext getSessionContext() {
640 | log.trace("EXEC getSessionContext();");
641 |
642 | return null;
643 | }
644 |
645 | @Override
646 | public Object getAttribute(String name) {
647 | log.trace(String.format("EXEC getAttribute(%s);", name));
648 |
649 | String key = RedisSessionKeys.getAttrKey(id, name);
650 |
651 | byte[] object;
652 | Jedis jedis = pool.getResource();
653 | try {
654 | object = jedis.get(key.getBytes(RedisSessionKeys.getEncoding()));
655 |
656 | pool.returnResource(jedis);
657 | } catch (Throwable e) {
658 | pool.returnBrokenResource(jedis);
659 | throw new RuntimeException(e);
660 | }
661 |
662 | if (object == null) {
663 | return null;
664 | }
665 |
666 | return RedisSerializationUtil.decode(object);
667 | }
668 |
669 | @Override
670 | public Object getValue(String name) {
671 | log.trace(String.format("EXEC getValue(%s);", name));
672 |
673 | return getAttribute(name);
674 | }
675 |
676 | private Set getAttributesNames() {
677 | String key = RedisSessionKeys.getAttrsKey(id);
678 |
679 | Set result;
680 | Jedis jedis = pool.getResource();
681 | try {
682 | result = jedis.smembers(key);
683 |
684 | pool.returnResource(jedis);
685 | } catch (Throwable e) {
686 | pool.returnBrokenResource(jedis);
687 | throw new RuntimeException(e);
688 | }
689 |
690 | if (result.isEmpty()) {
691 | return new HashSet();
692 | }
693 |
694 | return result;
695 | }
696 |
697 | public long getExpireAt() {
698 | String key = RedisSessionKeys.getExpireAtKey(id);
699 |
700 | String expireAt;
701 | Jedis jedis = pool.getResource();
702 | try {
703 | expireAt = jedis.get(key);
704 |
705 | pool.returnResource(jedis);
706 | } catch (Throwable e) {
707 | pool.returnBrokenResource(jedis);
708 | throw new RuntimeException(e);
709 | }
710 |
711 | if (expireAt == null) {
712 | throw new IllegalStateException("Can't get expireAt from redis.");
713 | }
714 |
715 | return Long.parseLong(expireAt);
716 | }
717 |
718 | @Override
719 | public Enumeration getAttributeNames() {
720 | log.trace("EXEC getAttributeNames();");
721 |
722 | return new Enumerator(getAttributesNames(), true);
723 | }
724 |
725 | @Override
726 | public String[] getValueNames() {
727 | log.trace("EXEC getValueNames();");
728 |
729 | //noinspection ToArrayCallWithZeroLengthArrayArgument
730 | return getAttributesNames().toArray(new String[]{});
731 | }
732 |
733 | @Override
734 | public void setAttribute(String name, Object value) {
735 | log.trace(String.format("EXEC setAttribute(%s, %s);", name, value));
736 |
737 | String attributeKey = RedisSessionKeys.getAttrKey(id, name);
738 | String attrsListKey = RedisSessionKeys.getAttrsKey(id);
739 |
740 | Long currentExpireAtTimeWithReserve = getExpireAt();
741 |
742 | boolean exist;
743 | Jedis jedis = pool.getResource();
744 | try {
745 | exist = jedis.exists(attributeKey);
746 |
747 | pool.returnResource(jedis);
748 | } catch (Throwable e) {
749 | pool.returnBrokenResource(jedis);
750 | throw new RuntimeException(e);
751 | }
752 |
753 | byte[] bytes = null;
754 | if (exist && !disableListeners) {
755 | jedis = pool.getResource();
756 | try {
757 | bytes = jedis.get(attributeKey.getBytes(RedisSessionKeys.getEncoding()));
758 |
759 | pool.returnResource(jedis);
760 | } catch (Throwable e) {
761 | pool.returnBrokenResource(jedis);
762 | throw new RuntimeException(e);
763 | }
764 | }
765 |
766 | jedis = pool.getResource();
767 | try {
768 | Transaction transaction = jedis.multi();
769 |
770 | byte[] object = RedisSerializationUtil.encode((Serializable) value);
771 | transaction.set(attributeKey.getBytes(RedisSessionKeys.getEncoding()), object);
772 | transaction.expireAt(attributeKey.getBytes(RedisSessionKeys.getEncoding()), getUnixTime(currentExpireAtTimeWithReserve));
773 |
774 | transaction.sadd(attrsListKey, name);
775 | transaction.expireAt(attrsListKey, getUnixTime(currentExpireAtTimeWithReserve));
776 |
777 | transaction.exec();
778 |
779 | pool.returnResource(jedis);
780 | } catch (Throwable e) {
781 | pool.returnBrokenResource(jedis);
782 | throw new RuntimeException(e);
783 | }
784 |
785 | if (!disableListeners) {
786 | RedisSessionEvent sessionEvent;
787 | if (bytes == null) {
788 | sessionEvent = new RedisSessionAddAttributeEvent(id, name, (Serializable) value);
789 | } else {
790 | Object object = RedisSerializationUtil.decode(bytes);
791 | sessionEvent = new RedisSessionReplaceAttributeEvent(id, name, (Serializable) object);
792 | }
793 |
794 | jedis = pool.getResource();
795 | try {
796 | bytes = RedisSerializationUtil.encode(sessionEvent);
797 | String message = new String(Base64Util.encode(bytes));
798 |
799 | jedis.publish(RedisSessionKeys.getSessionChannel(manager.getContainer().getName()), message);
800 |
801 | log.debug("Sended " + sessionEvent.toString());
802 |
803 | pool.returnResource(jedis);
804 | } catch (Throwable e) {
805 | pool.returnBrokenResource(jedis);
806 | throw new RuntimeException(e);
807 | }
808 | }
809 | }
810 |
811 | @Override
812 | public void putValue(String name, Object value) {
813 | log.trace(String.format("EXEC putValue(%s, %s);", name, value));
814 |
815 | setAttribute(name, value);
816 | }
817 |
818 | @Override
819 | public void removeAttribute(String name) {
820 | log.trace(String.format("EXEC removeAttribute(%s);", name));
821 |
822 | String attributeKey = RedisSessionKeys.getAttrKey(id, name);
823 | String attrsListKey = RedisSessionKeys.getAttrsKey(id);
824 |
825 | boolean exist;
826 | Jedis jedis = pool.getResource();
827 | try {
828 | exist = jedis.exists(attributeKey);
829 |
830 | pool.returnResource(jedis);
831 | } catch (Throwable e) {
832 | pool.returnBrokenResource(jedis);
833 | throw new RuntimeException(e);
834 | }
835 |
836 | if (!exist) {
837 | return;
838 | }
839 |
840 | String message = null;
841 | if (!disableListeners) {
842 | byte[] bytes;
843 |
844 | jedis = pool.getResource();
845 | try {
846 | bytes = jedis.get(attributeKey.getBytes(RedisSessionKeys.getEncoding()));
847 |
848 | pool.returnResource(jedis);
849 | } catch (Throwable e) {
850 | pool.returnBrokenResource(jedis);
851 | throw new RuntimeException(e);
852 | }
853 |
854 | Object object = RedisSerializationUtil.decode(bytes);
855 | RedisSessionEvent sessionEvent = new RedisSessionRemoveAttributeEvent(id, name, (Serializable) object);
856 |
857 | bytes = RedisSerializationUtil.encode(sessionEvent);
858 | message = new String(Base64Util.encode(bytes));
859 | }
860 |
861 | jedis = pool.getResource();
862 | try {
863 | Transaction transaction = jedis.multi();
864 |
865 | transaction.del(attributeKey.getBytes(RedisSessionKeys.getEncoding()));
866 |
867 | transaction.srem(attrsListKey, name);
868 |
869 | if (!disableListeners) {
870 | transaction.publish(RedisSessionKeys.getSessionChannel(manager.getContainer().getName()), message);
871 | }
872 |
873 | transaction.exec();
874 |
875 | pool.returnResource(jedis);
876 | } catch (Throwable e) {
877 | pool.returnBrokenResource(jedis);
878 | throw new RuntimeException(e);
879 | }
880 | }
881 |
882 | @Override
883 | public void removeValue(String name) {
884 | log.trace(String.format("EXEC removeValue(%s);", name));
885 |
886 | removeAttribute(name);
887 | }
888 |
889 | @Override
890 | public void invalidate() {
891 | log.trace("EXEC invalidate();");
892 |
893 | expire();
894 | }
895 |
896 | @Override
897 | public boolean isNew() {
898 | log.trace("EXEC isNew();");
899 |
900 | return isNew.get();
901 | }
902 | }
903 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/session/RedisManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.session;
18 |
19 | import org.apache.catalina.*;
20 | import org.apache.catalina.mbeans.MBeanUtils;
21 | import org.apache.catalina.session.Constants;
22 | import org.apache.catalina.util.LifecycleMBeanBase;
23 | import org.apache.catalina.util.SessionIdGenerator;
24 | import org.apache.juli.logging.Log;
25 | import org.apache.juli.logging.LogFactory;
26 | import org.apache.tomcat.util.res.StringManager;
27 | import redis.clients.jedis.Jedis;
28 | import redis.clients.jedis.JedisPool;
29 | import redis.clients.jedis.JedisPoolConfig;
30 | import redis.clients.jedis.Protocol;
31 |
32 | import javax.naming.InitialContext;
33 | import javax.naming.NamingException;
34 | import java.beans.PropertyChangeEvent;
35 | import java.beans.PropertyChangeListener;
36 | import java.beans.PropertyChangeSupport;
37 | import java.io.IOException;
38 | import java.util.HashSet;
39 | import java.util.Set;
40 | import java.util.concurrent.Executors;
41 |
42 | /**
43 | * Date: 28.10.11 22:16
44 | *
45 | * @author Alexander V. Zinin (mail@zinin.ru)
46 | */
47 | public class RedisManager extends LifecycleMBeanBase implements Manager, PropertyChangeListener {
48 | private final Log log = LogFactory.getLog(RedisManager.class);
49 |
50 | private static final String info = "RedisManager/1.0";
51 | private static final StringManager sm = StringManager.getManager(Constants.Package);
52 |
53 | private Container container;
54 |
55 | private int maxInactiveInterval = 30 * 60;
56 | private int sessionIdLength = 32;
57 | private int maxActiveSessions = -1;
58 |
59 | private String secureRandomClass = null;
60 | private String secureRandomAlgorithm = "SHA1PRNG";
61 | private String secureRandomProvider = null;
62 | private SessionIdGenerator sessionIdGenerator = null;
63 |
64 | private String jedisJndiName = "pool/jedis";
65 |
66 | private String redisHostname = "localhost";
67 | private int redisPort = Protocol.DEFAULT_PORT;
68 | private int redisTimeout = Protocol.DEFAULT_TIMEOUT;
69 | private String redisPassword;
70 | private JedisPool pool;
71 |
72 | private boolean disableListeners = false;
73 |
74 | private PropertyChangeSupport support = new PropertyChangeSupport(this);
75 |
76 | private final RedisEventListenerThread eventListenerThread = new RedisEventListenerThread(this);
77 |
78 | @Override
79 | public Container getContainer() {
80 | return container;
81 | }
82 |
83 | @Override
84 | public void setContainer(Container container) {
85 | log.trace(String.format("EXEC setContainer(%s);", container));
86 |
87 | // De-register from the old Container (if any)
88 | if ((this.container != null) && (this.container instanceof Context)) {
89 | this.container.removePropertyChangeListener(this);
90 | }
91 |
92 | Container oldContainer = this.container;
93 | this.container = container;
94 | support.firePropertyChange("container", oldContainer, this.container);
95 |
96 | // Register with the new Container (if any)
97 | if ((this.container != null) && (this.container instanceof Context)) {
98 | setMaxInactiveInterval(((Context) this.container).getSessionTimeout() * 60);
99 | this.container.addPropertyChangeListener(this);
100 | }
101 | }
102 |
103 | @Override
104 | public boolean getDistributable() {
105 | log.trace("EXEC getDistributable();");
106 |
107 | return true;
108 | }
109 |
110 | @Override
111 | public void setDistributable(boolean distributable) {
112 | log.trace(String.format("EXEC setDistributable(%s);", distributable));
113 |
114 | if (!distributable) {
115 | log.error("Only distributable web applications supported.");
116 | }
117 | }
118 |
119 | @Override
120 | public String getInfo() {
121 | log.trace("EXEC getInfo();");
122 |
123 | return info;
124 | }
125 |
126 | @Override
127 | public int getMaxInactiveInterval() {
128 | log.trace("EXEC getMaxInactiveInterval();");
129 |
130 | return maxInactiveInterval;
131 | }
132 |
133 | @Override
134 | public void setMaxInactiveInterval(int interval) {
135 | log.trace(String.format("EXEC setMaxInactiveInterval(%d);", interval));
136 |
137 | int oldMaxInactiveInterval = this.maxInactiveInterval;
138 | this.maxInactiveInterval = interval;
139 | support.firePropertyChange("maxInactiveInterval", Integer.valueOf(oldMaxInactiveInterval), Integer.valueOf(this.maxInactiveInterval));
140 | }
141 |
142 | @Override
143 | public int getSessionIdLength() {
144 | log.trace("EXEC getSessionIdLength();");
145 |
146 | return sessionIdLength;
147 | }
148 |
149 | @Override
150 | public void setSessionIdLength(int idLength) {
151 | log.trace(String.format("EXEC setSessionIdLength(%d);", idLength));
152 |
153 | int oldSessionIdLength = this.sessionIdLength;
154 | this.sessionIdLength = idLength;
155 | support.firePropertyChange("sessionIdLength", Integer.valueOf(oldSessionIdLength), Integer.valueOf(this.sessionIdLength));
156 | }
157 |
158 | @Override
159 | public long getSessionCounter() {
160 | log.trace("EXEC getSessionCounter();");
161 |
162 | return 0;
163 | }
164 |
165 | @Override
166 | public void setSessionCounter(long sessionCounter) {
167 | log.trace(String.format("EXEC setSessionCounter(%d);", sessionCounter));
168 | }
169 |
170 | @Override
171 | public int getMaxActive() {
172 | log.trace("EXEC getMaxActive();");
173 |
174 | return 0;
175 | }
176 |
177 | @Override
178 | public void setMaxActive(int maxActive) {
179 | log.trace(String.format("EXEC setMaxActive(%d);", maxActive));
180 | }
181 |
182 | public int getMaxActiveSessions() {
183 | log.trace("EXEC getMaxActiveSessions();");
184 |
185 | return this.maxActiveSessions;
186 | }
187 |
188 | public void setMaxActiveSessions(int max) {
189 | log.trace(String.format("EXEC setMaxActiveSessions(%d);", max));
190 |
191 | int oldMaxActiveSessions = this.maxActiveSessions;
192 | this.maxActiveSessions = max;
193 | support.firePropertyChange("maxActiveSessions", Integer.valueOf(oldMaxActiveSessions), Integer.valueOf(this.maxActiveSessions));
194 | }
195 |
196 | @Override
197 | public int getActiveSessions() {
198 | log.trace("EXEC getActiveSessions();");
199 |
200 | String key = RedisSessionKeys.getSessionsKey();
201 |
202 | Long result;
203 | Jedis jedis = pool.getResource();
204 | try {
205 | result = jedis.zcard(key);
206 | pool.returnResource(jedis);
207 | } catch (Throwable e) {
208 | pool.returnBrokenResource(jedis);
209 | throw new RuntimeException(e);
210 | }
211 |
212 | return result.intValue();
213 | }
214 |
215 | @Override
216 | public long getExpiredSessions() {
217 | log.trace("EXEC getExpiredSessions();");
218 |
219 | return 0;
220 | }
221 |
222 | @Override
223 | public void setExpiredSessions(long expiredSessions) {
224 | log.trace(String.format("EXEC setExpiredSessions(%d);", expiredSessions));
225 | }
226 |
227 | @Override
228 | public int getRejectedSessions() {
229 | log.trace("EXEC getRejectedSessions();");
230 |
231 | return 0;
232 | }
233 |
234 | @Override
235 | public int getSessionMaxAliveTime() {
236 | log.trace("EXEC getSessionMaxAliveTime();");
237 |
238 | return 0;
239 | }
240 |
241 | @Override
242 | public void setSessionMaxAliveTime(int sessionMaxAliveTime) {
243 | log.trace(String.format("EXEC setSessionMaxAliveTime(%d);", sessionMaxAliveTime));
244 | }
245 |
246 | @Override
247 | public int getSessionAverageAliveTime() {
248 | log.trace("EXEC getSessionAverageAliveTime();");
249 |
250 | return 0;
251 | }
252 |
253 | @Override
254 | public int getSessionCreateRate() {
255 | log.trace("EXEC getSessionCreateRate();");
256 |
257 | return 0;
258 | }
259 |
260 | @Override
261 | public int getSessionExpireRate() {
262 | log.trace("EXEC getSessionExpireRate();");
263 |
264 | return 0;
265 | }
266 |
267 | @Override
268 | public void add(Session session) {
269 | log.trace(String.format("EXEC add(%s);", session));
270 | }
271 |
272 | @Override
273 | public void addPropertyChangeListener(PropertyChangeListener listener) {
274 | log.trace(String.format("EXEC addPropertyChangeListener(%s);", listener));
275 |
276 | support.addPropertyChangeListener(listener);
277 | }
278 |
279 | @Override
280 | public void changeSessionId(Session session) {
281 | log.trace(String.format("EXEC changeSessionId(%s);", session));
282 |
283 | String oldId = session.getIdInternal();
284 | session.setId(generateSessionId(), false);
285 | String newId = session.getIdInternal();
286 |
287 | container.fireContainerEvent(Context.CHANGE_SESSION_ID_EVENT, new String[]{oldId, newId});
288 | }
289 |
290 | @Override
291 | public Session createEmptySession() {
292 | log.trace("EXEC createEmptySession();");
293 |
294 | throw new UnsupportedOperationException("Cannot create empty session.");
295 | }
296 |
297 | @Override
298 | public Session createSession(String sessionId) {
299 | log.trace(String.format("EXEC createSession(%s);", sessionId));
300 |
301 | if ((maxActiveSessions >= 0) && (getActiveSessions() >= maxActiveSessions)) {
302 | throw new IllegalStateException(sm.getString("managerBase.createSession.ise"));
303 | }
304 |
305 | String id = sessionId;
306 | if (id == null) {
307 | id = generateSessionId();
308 | }
309 |
310 | return new RedisHttpSession(id, this, maxInactiveInterval);
311 | }
312 |
313 | @Override
314 | public Session findSession(String id) throws IOException {
315 | log.trace(String.format("EXEC findSession(%s);", id));
316 |
317 | if (id == null) {
318 | return null;
319 | }
320 |
321 | String key = RedisSessionKeys.getLastAccessTimeKey(id);
322 |
323 | String lastAccessTime;
324 | Jedis jedis = pool.getResource();
325 | try {
326 | lastAccessTime = jedis.get(key);
327 | pool.returnResource(jedis);
328 | } catch (Throwable e) {
329 | pool.returnBrokenResource(jedis);
330 | throw new RuntimeException(e);
331 | }
332 |
333 | if (lastAccessTime == null) {
334 | return null;
335 | }
336 |
337 | return new RedisHttpSession(id, this);
338 | }
339 |
340 | @Override
341 | public Session[] findSessions() {
342 | log.trace("EXEC findSessions();");
343 |
344 | Set sessionIds;
345 |
346 | Jedis jedis = pool.getResource();
347 | try {
348 | sessionIds = jedis.zrangeByScore(RedisSessionKeys.getSessionsKey(), 0, Double.MAX_VALUE);
349 |
350 | pool.returnResource(jedis);
351 | } catch (Throwable e) {
352 | pool.returnBrokenResource(jedis);
353 | throw new RuntimeException(e);
354 | }
355 |
356 | Set result = new HashSet(sessionIds.size());
357 | for (String sessionId : sessionIds) {
358 | RedisHttpSession session = new RedisHttpSession(sessionId, this);
359 | if (session.isValid()) {
360 | result.add(session);
361 | }
362 | }
363 |
364 | //noinspection ToArrayCallWithZeroLengthArrayArgument
365 | return result.toArray(new Session[]{});
366 | }
367 |
368 | @Override
369 | public void load() throws ClassNotFoundException, IOException {
370 | log.trace("EXEC load();");
371 | }
372 |
373 | @Override
374 | public void remove(Session session) {
375 | log.trace(String.format("EXEC remove(%s);", session));
376 |
377 | remove(session, false);
378 | }
379 |
380 | @Override
381 | public void remove(Session session, boolean update) {
382 | log.trace(String.format("EXEC remove(%s, %s);", session, update));
383 | }
384 |
385 | @Override
386 | public void removePropertyChangeListener(PropertyChangeListener listener) {
387 | log.trace(String.format("EXEC removePropertyChangeListener(%s);", listener));
388 |
389 | support.removePropertyChangeListener(listener);
390 | }
391 |
392 | @Override
393 | public void unload() throws IOException {
394 | log.trace("EXEC unload();");
395 | }
396 |
397 | @Override
398 | public void backgroundProcess() {
399 | log.trace("EXEC backgroundProcess();");
400 |
401 | long max = System.currentTimeMillis() - (maxInactiveInterval * 1000);
402 | Set sessionIds;
403 |
404 | Jedis jedis = pool.getResource();
405 | try {
406 | sessionIds = jedis.zrangeByScore(RedisSessionKeys.getSessionsKey(), 0, max);
407 |
408 | pool.returnResource(jedis);
409 | } catch (Throwable e) {
410 | pool.returnBrokenResource(jedis);
411 | throw new RuntimeException(e);
412 | }
413 |
414 | log.debug(sessionIds.size() + " sessions expired.");
415 |
416 | Set removedIds = new HashSet();
417 | if (!sessionIds.isEmpty()) {
418 | jedis = pool.getResource();
419 | try {
420 | for (String sessionId : sessionIds) {
421 | if (jedis.zrem(RedisSessionKeys.getSessionsKey(), sessionId) > 0) {
422 | removedIds.add(sessionId);
423 | }
424 | }
425 |
426 | pool.returnResource(jedis);
427 | } catch (Throwable e) {
428 | pool.returnBrokenResource(jedis);
429 | throw new RuntimeException(e);
430 | }
431 |
432 | for (String sessionId : removedIds) {
433 | RedisHttpSession session = new RedisHttpSession(sessionId, this);
434 |
435 | session.expire();
436 | }
437 | }
438 | }
439 |
440 | @Override
441 | protected String getDomainInternal() {
442 | log.trace("EXEC getDomainInternal();");
443 |
444 | return MBeanUtils.getDomain(container);
445 | }
446 |
447 | @Override
448 | protected String getObjectNameKeyProperties() {
449 | log.trace("EXEC getObjectNameKeyProperties();");
450 |
451 | StringBuilder name = new StringBuilder("type=Manager");
452 |
453 | if (container instanceof Context) {
454 | name.append(",context=");
455 | String contextName = container.getName();
456 | if (!contextName.startsWith("/")) {
457 | name.append('/');
458 | }
459 | name.append(contextName);
460 |
461 | Context context = (Context) container;
462 | name.append(",host=");
463 | name.append(context.getParent().getName());
464 | } else {
465 | // Unlikely / impossible? Handle it to be safe
466 | name.append(",container=");
467 | name.append(container.getName());
468 | }
469 |
470 | return name.toString();
471 | }
472 |
473 | public String getJedisJndiName() {
474 | return jedisJndiName;
475 | }
476 |
477 | public void setJedisJndiName(String jedisJndiName) {
478 | this.jedisJndiName = jedisJndiName;
479 | }
480 |
481 | public String getRedisHostname() {
482 | return redisHostname;
483 | }
484 |
485 | public void setRedisHostname(String redisHostname) {
486 | this.redisHostname = redisHostname;
487 | }
488 |
489 | public int getRedisPort() {
490 | return redisPort;
491 | }
492 |
493 | public void setRedisPort(int redisPort) {
494 | this.redisPort = redisPort;
495 | }
496 |
497 | public int getRedisTimeout() {
498 | return redisTimeout;
499 | }
500 |
501 | public void setRedisTimeout(int redisTimeout) {
502 | this.redisTimeout = redisTimeout;
503 | }
504 |
505 | public String getRedisPassword() {
506 | return redisPassword;
507 | }
508 |
509 | public void setRedisPassword(String redisPassword) {
510 | this.redisPassword = redisPassword;
511 | }
512 |
513 | public JedisPool getPool() {
514 | return pool;
515 | }
516 |
517 | public boolean isDisableListeners() {
518 | return disableListeners;
519 | }
520 |
521 | public void setDisableListeners(boolean disableListeners) {
522 | this.disableListeners = disableListeners;
523 | }
524 |
525 | @Override
526 | protected void startInternal() throws LifecycleException {
527 | log.trace("EXEC startInternal();");
528 |
529 | sessionIdGenerator = new SessionIdGenerator();
530 | sessionIdGenerator.setJvmRoute(getJvmRoute());
531 | sessionIdGenerator.setSecureRandomAlgorithm(getSecureRandomAlgorithm());
532 | sessionIdGenerator.setSecureRandomClass(getSecureRandomClass());
533 | sessionIdGenerator.setSecureRandomProvider(getSecureRandomProvider());
534 | sessionIdGenerator.setSessionIdLength(getSessionIdLength());
535 |
536 | // Force initialization of the random number generator
537 | if (log.isDebugEnabled()) {
538 | log.debug("Force random number initialization starting");
539 | }
540 | sessionIdGenerator.generateSessionId();
541 | if (log.isDebugEnabled()) {
542 | log.debug("Force random number initialization completed");
543 | }
544 |
545 | log.debug("Trying to get jedis pool from JNDI...");
546 | InitialContext initialContext = null;
547 | try {
548 | initialContext = new InitialContext();
549 | pool = (JedisPool) initialContext.lookup("java:/comp/env/" + jedisJndiName);
550 | } catch (NamingException e) {
551 | log.warn("JedisPool not found in JNDI");
552 | }
553 |
554 | log.debug("Pool from JNDI: " + pool);
555 |
556 | if (pool == null) {
557 | JedisPoolConfig config = new JedisPoolConfig();
558 | pool = new JedisPool(config, redisHostname, redisPort, redisTimeout, redisPassword);
559 | }
560 |
561 | if (!disableListeners) {
562 | Executors.newSingleThreadExecutor().execute(eventListenerThread);
563 | }
564 |
565 | setState(LifecycleState.STARTING);
566 | }
567 |
568 | public Engine getEngine() {
569 | Engine e = null;
570 | for (Container c = getContainer(); e == null && c != null; c = c.getParent()) {
571 | if (c instanceof Engine) {
572 | e = (Engine) c;
573 | }
574 | }
575 | return e;
576 | }
577 |
578 | public String getJvmRoute() {
579 | Engine e = getEngine();
580 | return e == null ? null : e.getJvmRoute();
581 | }
582 |
583 | public String getSecureRandomClass() {
584 | return this.secureRandomClass;
585 | }
586 |
587 | public void setSecureRandomClass(String secureRandomClass) {
588 | String oldSecureRandomClass = this.secureRandomClass;
589 | this.secureRandomClass = secureRandomClass;
590 | support.firePropertyChange("secureRandomClass", oldSecureRandomClass, this.secureRandomClass);
591 | }
592 |
593 | public String getSecureRandomAlgorithm() {
594 | return secureRandomAlgorithm;
595 | }
596 |
597 | public void setSecureRandomAlgorithm(String secureRandomAlgorithm) {
598 | this.secureRandomAlgorithm = secureRandomAlgorithm;
599 | }
600 |
601 | public String getSecureRandomProvider() {
602 | return secureRandomProvider;
603 | }
604 |
605 | public void setSecureRandomProvider(String secureRandomProvider) {
606 | this.secureRandomProvider = secureRandomProvider;
607 | }
608 |
609 | @Override
610 | protected void stopInternal() throws LifecycleException {
611 | log.trace("EXEC stopInternal();");
612 |
613 | setState(LifecycleState.STOPPING);
614 |
615 | this.sessionIdGenerator = null;
616 |
617 | if (!disableListeners) {
618 | eventListenerThread.stop();
619 | }
620 |
621 | pool.destroy();
622 | }
623 |
624 | protected String generateSessionId() {
625 | String result;
626 |
627 | try {
628 | do {
629 | result = sessionIdGenerator.generateSessionId();
630 | } while (findSession(result) != null);
631 | } catch (IOException e) {
632 | throw new RuntimeException(e);
633 | }
634 |
635 | return result;
636 | }
637 |
638 | protected ClassLoader getContainerClassLoader() {
639 | if (container instanceof Context) {
640 | Context containerContext = (Context)container;
641 | Loader contextLoader = containerContext.getLoader();
642 | if (contextLoader == null) {
643 | return null;
644 | }
645 | return contextLoader.getClassLoader();
646 | }
647 | return null;
648 | }
649 |
650 | @Override
651 | public void propertyChange(PropertyChangeEvent event) {
652 | log.trace(String.format("EXEC propertyChange(%s);", event));
653 |
654 | if (!(event.getSource() instanceof Context)) {
655 | return;
656 | }
657 |
658 | if (event.getPropertyName().equals("sessionTimeout")) {
659 | try {
660 | setMaxInactiveInterval((Integer) event.getNewValue() * 60);
661 | } catch (NumberFormatException e) {
662 | log.error(sm.getString("managerBase.sessionTimeout", event.getNewValue()));
663 | }
664 | }
665 | }
666 | }
667 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/session/RedisSessionKeys.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.session;
18 |
19 | /**
20 | * Date: 28.10.11 23:58
21 | *
22 | * @author Alexander V. Zinin (mail@zinin.ru)
23 | */
24 | public class RedisSessionKeys {
25 | private static final String SESSIONS_LIST_KEY = "sessions";
26 |
27 | private static final String SESSION_KEY_PREFIX = "session";
28 |
29 | private static final String KEY_DELIM = ":";
30 |
31 | private static final String SESSION_CREATION_TIME_KEY = "creation_time";
32 | private static final String SESSION_LAST_ACCESS_TIME_KEY = "last_access_time";
33 | private static final String SESSION_EXPIRE_AT_KEY = "expire_at";
34 | private static final String SESSION_TIMEOUT_KEY = "timeout";
35 |
36 | private static final String SESSION_ATTRS_LIST_KEY = "attrs";
37 | private static final String SESSION_ATTR_KEY = "attrs";
38 |
39 | private static final String SESSION_CHANNEL = "session-channel";
40 |
41 | private static final String ENCODING = "utf-8";
42 |
43 | public static String getSessionsKey() {
44 | return SESSIONS_LIST_KEY;
45 | }
46 |
47 | public static String getCreationTimeKey(String id) {
48 | return SESSION_KEY_PREFIX + KEY_DELIM + id + KEY_DELIM + SESSION_CREATION_TIME_KEY;
49 | }
50 |
51 | public static String getLastAccessTimeKey(String id) {
52 | return SESSION_KEY_PREFIX + KEY_DELIM + id + KEY_DELIM + SESSION_LAST_ACCESS_TIME_KEY;
53 | }
54 |
55 | public static String getExpireAtKey(String id) {
56 | return SESSION_KEY_PREFIX + KEY_DELIM + id + KEY_DELIM + SESSION_EXPIRE_AT_KEY;
57 | }
58 |
59 | public static String getSessionTimeoutKey(String id) {
60 | return SESSION_KEY_PREFIX + KEY_DELIM + id + KEY_DELIM + SESSION_TIMEOUT_KEY;
61 | }
62 |
63 | public static String getAttrsKey(String id) {
64 | return SESSION_KEY_PREFIX + KEY_DELIM + id + KEY_DELIM + SESSION_ATTRS_LIST_KEY;
65 | }
66 |
67 | public static String getAttrKey(String id, String name) {
68 | return SESSION_KEY_PREFIX + KEY_DELIM + id + KEY_DELIM + SESSION_ATTR_KEY + KEY_DELIM + name;
69 | }
70 |
71 | public static String getSessionChannel(String containerName) {
72 | return SESSION_CHANNEL + KEY_DELIM + containerName;
73 | }
74 |
75 | public static String getEncoding() {
76 | return ENCODING;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/session/RedisSessionTemplate.java:
--------------------------------------------------------------------------------
1 | package ru.zinin.redis.session;
2 |
3 | import redis.clients.jedis.Jedis;
4 | import redis.clients.jedis.JedisPool;
5 |
6 | import javax.servlet.ServletContext;
7 | import java.util.HashSet;
8 | import java.util.Set;
9 |
10 | /**
11 | * Date: 12.11.11 12:08
12 | *
13 | * @author Alexander V. Zinin (mail@zinin.ru)
14 | */
15 | public class RedisSessionTemplate {
16 | private JedisPool jedisPool;
17 | private ServletContext servletContext;
18 | private boolean disableListeners = false;
19 |
20 | public RedisSessionTemplate() {
21 | }
22 |
23 | public RedisSessionTemplate(JedisPool jedisPool, ServletContext servletContext) {
24 | this.jedisPool = jedisPool;
25 | this.servletContext = servletContext;
26 | }
27 |
28 | public JedisPool getJedisPool() {
29 | return jedisPool;
30 | }
31 |
32 | public void setJedisPool(JedisPool jedisPool) {
33 | this.jedisPool = jedisPool;
34 | }
35 |
36 | public ServletContext getServletContext() {
37 | return servletContext;
38 | }
39 |
40 | public void setServletContext(ServletContext servletContext) {
41 | this.servletContext = servletContext;
42 | }
43 |
44 | public boolean isDisableListeners() {
45 | return disableListeners;
46 | }
47 |
48 | public void setDisableListeners(boolean disableListeners) {
49 | this.disableListeners = disableListeners;
50 | }
51 |
52 | private void verifyInitialization() {
53 | if (jedisPool == null) {
54 | throw new IllegalStateException("JedisPool is not initialized");
55 | }
56 | if (servletContext == null) {
57 | throw new IllegalStateException("ServletContext is not initialized");
58 | }
59 | }
60 |
61 | public Set getAllSessions() {
62 | verifyInitialization();
63 |
64 | Set sessionIds;
65 |
66 | Jedis jedis = jedisPool.getResource();
67 | try {
68 | sessionIds = jedis.zrangeByScore(RedisSessionKeys.getSessionsKey(), 0, Double.MAX_VALUE);
69 |
70 | jedisPool.returnResource(jedis);
71 | } catch (Throwable e) {
72 | jedisPool.returnBrokenResource(jedis);
73 | throw new RuntimeException(e);
74 | }
75 |
76 | Set result = new HashSet(sessionIds.size());
77 | for (String sessionId : sessionIds) {
78 | RedisHttpSession session = new RedisHttpSession(sessionId, jedisPool, servletContext, disableListeners);
79 | if (session.isValid()) {
80 | result.add(session);
81 | }
82 | }
83 |
84 | return result;
85 | }
86 |
87 | public RedisHttpSession getSession(String id) {
88 | verifyInitialization();
89 |
90 | return new RedisHttpSession(id, jedisPool, servletContext, disableListeners);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/session/event/RedisSessionAddAttributeEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.session.event;
18 |
19 | import java.io.Serializable;
20 |
21 | /**
22 | * Date: 01.11.11 22:15
23 | *
24 | * @author Alexander V. Zinin (mail@zinin.ru)
25 | */
26 | public class RedisSessionAddAttributeEvent extends RedisSessionAttributeEvent {
27 | public RedisSessionAddAttributeEvent(String id, String name, Serializable value) {
28 | super(id, name, value);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/session/event/RedisSessionAttributeEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.session.event;
18 |
19 | import java.io.Serializable;
20 |
21 | /**
22 | * Date: 01.11.11 22:14
23 | *
24 | * @author Alexander V. Zinin (mail@zinin.ru)
25 | */
26 | public abstract class RedisSessionAttributeEvent extends RedisSessionEvent {
27 | private String name;
28 | private Serializable value;
29 |
30 | protected RedisSessionAttributeEvent(String id, String name, Serializable value) {
31 | super(id);
32 | this.name = name;
33 | this.value = value;
34 | }
35 |
36 | public String getName() {
37 | return name;
38 | }
39 |
40 | public Serializable getValue() {
41 | return value;
42 | }
43 |
44 | @Override
45 | public String toString() {
46 | final StringBuilder sb = new StringBuilder();
47 | sb.append(getClass().getCanonicalName());
48 | sb.append("{id='").append(getId()).append('\'');
49 | sb.append(", name='").append(name).append('\'');
50 | sb.append(", value='").append(value).append('\'');
51 | sb.append('}');
52 | return sb.toString();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/session/event/RedisSessionCreatedEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.session.event;
18 |
19 | /**
20 | * Date: 01.11.11 20:37
21 | *
22 | * @author Alexander V. Zinin (mail@zinin.ru)
23 | */
24 | public class RedisSessionCreatedEvent extends RedisSessionEvent {
25 | public RedisSessionCreatedEvent(String id) {
26 | super(id);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/session/event/RedisSessionDestroyedEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.session.event;
18 |
19 | /**
20 | * Date: 01.11.11 20:41
21 | *
22 | * @author Alexander V. Zinin (mail@zinin.ru)
23 | */
24 | public class RedisSessionDestroyedEvent extends RedisSessionEvent {
25 | public RedisSessionDestroyedEvent(String id) {
26 | super(id);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/session/event/RedisSessionEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.session.event;
18 |
19 | import java.io.Serializable;
20 |
21 | /**
22 | * Date: 01.11.11 20:35
23 | *
24 | * @author Alexander V. Zinin (mail@zinin.ru)
25 | */
26 | public abstract class RedisSessionEvent implements Serializable {
27 | private String id;
28 |
29 | public RedisSessionEvent(String id) {
30 | this.id = id;
31 | }
32 |
33 | public String getId() {
34 | return id;
35 | }
36 |
37 | @Override
38 | public String toString() {
39 | final StringBuilder sb = new StringBuilder();
40 | sb.append(getClass().getCanonicalName());
41 | sb.append("{id='").append(id).append('\'');
42 | sb.append('}');
43 | return sb.toString();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/session/event/RedisSessionRemoveAttributeEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.session.event;
18 |
19 | import java.io.Serializable;
20 |
21 | /**
22 | * Date: 01.11.11 22:17
23 | *
24 | * @author Alexander V. Zinin (mail@zinin.ru)
25 | */
26 | public class RedisSessionRemoveAttributeEvent extends RedisSessionAttributeEvent {
27 | public RedisSessionRemoveAttributeEvent(String id, String name, Serializable value) {
28 | super(id, name, value);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/session/event/RedisSessionReplaceAttributeEvent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.session.event;
18 |
19 | import java.io.Serializable;
20 |
21 | /**
22 | * Date: 01.11.11 22:17
23 | *
24 | * @author Alexander V. Zinin (mail@zinin.ru)
25 | */
26 | public class RedisSessionReplaceAttributeEvent extends RedisSessionAttributeEvent {
27 | public RedisSessionReplaceAttributeEvent(String id, String name, Serializable value) {
28 | super(id, name, value);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/util/Base64Util.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.util;
18 |
19 | import java.io.UnsupportedEncodingException;
20 |
21 | /**
22 | * Date: 01.11.11 21:13
23 | *
24 | * @author Alexander V. Zinin (mail@zinin.ru)
25 | */
26 | public class Base64Util {
27 | private static final String DEFAULT_CHARSET = "utf-8";
28 |
29 | // Dummy constructor.
30 |
31 | private Base64Util() {
32 | }
33 |
34 | // Mapping table from 6-bit nibbles to Base64 characters.
35 | private static char[] map1 = new char[64];
36 |
37 | static {
38 | int i = 0;
39 | for (char c = 'A'; c <= 'Z'; c++) {
40 | map1[i++] = c;
41 | }
42 | for (char c = 'a'; c <= 'z'; c++) {
43 | map1[i++] = c;
44 | }
45 | for (char c = '0'; c <= '9'; c++) {
46 | map1[i++] = c;
47 | }
48 | map1[i++] = '+';
49 | map1[i++] = '/';
50 | }
51 |
52 | // Mapping table from Base64 characters to 6-bit nibbles.
53 | private static byte[] map2 = new byte[128];
54 |
55 | static {
56 | for (int i = 0; i < map2.length; i++) {
57 | map2[i] = -1;
58 | }
59 | for (int i = 0; i < 64; i++) {
60 | map2[map1[i]] = (byte) i;
61 | }
62 | }
63 |
64 | /**
65 | * Encodes a string into Base64 format.
66 | * No blanks or line breaks are inserted.
67 | *
68 | * @param s a String to be encoded.
69 | * @param charset when convert string to byte arrat use this charset
70 | *
71 | * @return A String with the Base64 encoded data.
72 | *
73 | * @throws java.io.UnsupportedEncodingException
74 | * if specified charset not supported
75 | */
76 | public static String encodeString(String s, String charset) throws UnsupportedEncodingException {
77 | return new String(encode(s.getBytes(charset)));
78 | }
79 |
80 | /**
81 | * Invoke {@link Base64Util#encodeString(String, String)} with {@link Base64Util#DEFAULT_CHARSET}
82 | *
83 | * @param s a String to be encoded.
84 | *
85 | * @return A String with the Base64 encoded data.
86 | */
87 | public static String encodeString(String s) {
88 | try {
89 | return encodeString(s, DEFAULT_CHARSET);
90 | } catch (UnsupportedEncodingException e) {
91 | throw new RuntimeException(e);
92 | }
93 | }
94 |
95 | /**
96 | * Encodes a byte array into Base64 format.
97 | * No blanks or line breaks are inserted.
98 | *
99 | * @param in an array containing the data bytes to be encoded.
100 | *
101 | * @return A character array with the Base64 encoded data.
102 | */
103 | public static char[] encode(byte[] in) {
104 | return encode(in, in.length);
105 | }
106 |
107 | /**
108 | * Encodes a byte array into Base64 format.
109 | * No blanks or line breaks are inserted.
110 | *
111 | * @param in an array containing the data bytes to be encoded.
112 | * @param iLen number of bytes to process in in
.
113 | *
114 | * @return A character array with the Base64 encoded data.
115 | */
116 | public static char[] encode(byte[] in, int iLen) {
117 | int oDataLen = (iLen * 4 + 2) / 3; // output length without padding
118 | int oLen = ((iLen + 2) / 3) * 4; // output length including padding
119 | char[] out = new char[oLen];
120 | int ip = 0;
121 | int op = 0;
122 | while (ip < iLen) {
123 | int i0 = in[ip++] & 0xff;
124 | int i1 = ip < iLen ? in[ip++] & 0xff : 0;
125 | int i2 = ip < iLen ? in[ip++] & 0xff : 0;
126 | int o0 = i0 >>> 2;
127 | int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
128 | int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
129 | int o3 = i2 & 0x3F;
130 | out[op++] = map1[o0];
131 | out[op++] = map1[o1];
132 | out[op] = op < oDataLen ? map1[o2] : '=';
133 | op++;
134 | out[op] = op < oDataLen ? map1[o3] : '=';
135 | op++;
136 | }
137 | return out;
138 | }
139 |
140 | /**
141 | * Decodes a string from Base64 format.
142 | *
143 | * @param s a Base64 String to be decoded.
144 | * @param charset when convert byte array to string use this charset
145 | *
146 | * @return A String containing the decoded data.
147 | *
148 | * @throws IllegalArgumentException if the input is not valid Base64 encoded data.
149 | * @throws java.io.UnsupportedEncodingException
150 | * if specified charset not supported
151 | */
152 | public static String decodeString(String s, String charset) throws UnsupportedEncodingException {
153 | return new String(decode(s), charset);
154 | }
155 |
156 | /**
157 | * Invoke {@link Base64Util#decodeString(String, String)} with {@link Base64Util#DEFAULT_CHARSET}
158 | *
159 | * @param s a Base64 String to be decoded.
160 | *
161 | * @return A String containing the decoded data.
162 | */
163 | public static String decodeString(String s) {
164 | try {
165 | return decodeString(s, DEFAULT_CHARSET);
166 | } catch (UnsupportedEncodingException e) {
167 | throw new RuntimeException(e);
168 | }
169 | }
170 |
171 | /**
172 | * Decodes a byte array from Base64 format.
173 | *
174 | * @param s a Base64 String to be decoded.
175 | *
176 | * @return An array containing the decoded data bytes.
177 | *
178 | * @throws IllegalArgumentException if the input is not valid Base64 encoded data.
179 | */
180 | public static byte[] decode(String s) {
181 | return decode(s.toCharArray());
182 | }
183 |
184 | /**
185 | * Decodes a byte array from Base64 format.
186 | * No blanks or line breaks are allowed within the Base64 encoded data.
187 | *
188 | * @param in a character array containing the Base64 encoded data.
189 | *
190 | * @return An array containing the decoded data bytes.
191 | *
192 | * @throws IllegalArgumentException if the input is not valid Base64 encoded data.
193 | */
194 | public static byte[] decode(char[] in) {
195 | int iLen = in.length;
196 | if (iLen % 4 != 0) {
197 | throw new IllegalArgumentException("Length of Base64 encoded input string is not a multiple of 4.");
198 | }
199 | while (iLen > 0 && in[iLen - 1] == '=') {
200 | iLen--;
201 | }
202 | int oLen = (iLen * 3) / 4;
203 | byte[] out = new byte[oLen];
204 | int ip = 0;
205 | int op = 0;
206 | while (ip < iLen) {
207 | int i0 = in[ip++];
208 | int i1 = in[ip++];
209 | int i2 = ip < iLen ? in[ip++] : 'A';
210 | int i3 = ip < iLen ? in[ip++] : 'A';
211 | if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127) {
212 | throw new IllegalArgumentException("Illegal character in Base64 encoded data.");
213 | }
214 | int b0 = map2[i0];
215 | int b1 = map2[i1];
216 | int b2 = map2[i2];
217 | int b3 = map2[i3];
218 | if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0) {
219 | throw new IllegalArgumentException("Illegal character in Base64 encoded data.");
220 | }
221 | int o0 = (b0 << 2) | (b1 >>> 4);
222 | int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2);
223 | int o2 = ((b2 & 3) << 6) | b3;
224 | out[op++] = (byte) o0;
225 | if (op < oLen) {
226 | out[op++] = (byte) o1;
227 | }
228 | if (op < oLen) {
229 | out[op++] = (byte) o2;
230 | }
231 | }
232 | return out;
233 | }
234 | }
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/util/CustomObjectInputStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.util;
18 |
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 | import java.io.ObjectInputStream;
22 | import java.io.ObjectStreamClass;
23 | import java.lang.reflect.Proxy;
24 |
25 | /**
26 | * Date: 31.10.11 21:57
27 | *
28 | * @author Alexander V. Zinin (mail@zinin.ru)
29 | */
30 | public final class CustomObjectInputStream
31 | extends ObjectInputStream {
32 |
33 |
34 | /** The class loader we will use to resolve classes. */
35 | private ClassLoader classLoader = null;
36 |
37 |
38 | /**
39 | * Construct a new instance of CustomObjectInputStream
40 | *
41 | * @param stream The input stream we will read from
42 | * @param classLoader The class loader used to instantiate objects
43 | *
44 | * @throws IOException if an input/output error occurs
45 | */
46 | public CustomObjectInputStream(InputStream stream,
47 | ClassLoader classLoader)
48 | throws IOException {
49 |
50 | super(stream);
51 | this.classLoader = classLoader;
52 | }
53 |
54 |
55 | /**
56 | * Load the local class equivalent of the specified stream class
57 | * description, by using the class loader assigned to this Context.
58 | *
59 | * @param classDesc Class description from the input stream
60 | *
61 | * @throws ClassNotFoundException if this class cannot be found
62 | * @throws IOException if an input/output error occurs
63 | */
64 | @Override
65 | public Class> resolveClass(ObjectStreamClass classDesc)
66 | throws ClassNotFoundException, IOException {
67 | try {
68 | return Class.forName(classDesc.getName(), false, classLoader);
69 | } catch (ClassNotFoundException e) {
70 | try {
71 | // Try also the superclass because of primitive types
72 | return super.resolveClass(classDesc);
73 | } catch (ClassNotFoundException e2) {
74 | // Rethrow original exception, as it can have more information
75 | // about why the class was not found. BZ 48007
76 | throw e;
77 | }
78 | }
79 | }
80 |
81 |
82 | /**
83 | * Return a proxy class that implements the interfaces named in a proxy
84 | * class descriptor. Do this using the class loader assigned to this
85 | * Context.
86 | */
87 | @Override
88 | protected Class> resolveProxyClass(String[] interfaces)
89 | throws IOException, ClassNotFoundException {
90 |
91 | Class>[] cinterfaces = new Class[interfaces.length];
92 | for (int i = 0; i < interfaces.length; i++) {
93 | cinterfaces[i] = classLoader.loadClass(interfaces[i]);
94 | }
95 |
96 | try {
97 | return Proxy.getProxyClass(classLoader, cinterfaces);
98 | } catch (IllegalArgumentException e) {
99 | throw new ClassNotFoundException(null, e);
100 | }
101 | }
102 |
103 | }
--------------------------------------------------------------------------------
/src/main/java/ru/zinin/redis/util/RedisSerializationUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Alexander V. Zinin
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package ru.zinin.redis.util;
18 |
19 | import java.io.*;
20 |
21 | /**
22 | * Date: 29.10.11 16:50
23 | *
24 | * @author Alexander V. Zinin (mail@zinin.ru)
25 | */
26 | public class RedisSerializationUtil {
27 | public static byte[] encode(T obj) {
28 | try {
29 | ByteArrayOutputStream bout = new ByteArrayOutputStream();
30 | ObjectOutputStream out = new ObjectOutputStream(bout);
31 | out.writeObject(obj);
32 | return bout.toByteArray();
33 | } catch (IOException e) {
34 | throw new RuntimeException("Error serializing object" + obj + " => " + e);
35 | }
36 | }
37 |
38 | @SuppressWarnings({"unchecked", "ThrowFromFinallyBlock"})
39 | public static T decode(byte[] bytes) {
40 | return decode(bytes, null);
41 | }
42 |
43 | @SuppressWarnings({"unchecked", "ThrowFromFinallyBlock"})
44 | public static T decode(byte[] bytes, ClassLoader classLoader) {
45 | if (classLoader == null) {
46 | classLoader = Thread.currentThread().getContextClassLoader();
47 | }
48 | T t = null;
49 | Exception thrown = null;
50 | try {
51 | CustomObjectInputStream oin = new CustomObjectInputStream(new ByteArrayInputStream(bytes), classLoader);
52 | t = (T) oin.readObject();
53 | } catch (IOException e) {
54 | e.printStackTrace();
55 | thrown = e;
56 | } catch (ClassNotFoundException e) {
57 | e.printStackTrace();
58 | thrown = e;
59 | } catch (ClassCastException e) {
60 | e.printStackTrace();
61 | thrown = e;
62 | } finally {
63 | if (null != thrown) {
64 | throw new RuntimeException(
65 | "Error decoding byte[] data to instantiate java object - " +
66 | "data at key may not have been of this type or even an object", thrown
67 | );
68 | }
69 | }
70 | return t;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/LICENCE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/NOTICE:
--------------------------------------------------------------------------------
1 | ================================================================================
2 | tomcat-redis-session
3 | Copyright 2011 Alexander V. Zinin
4 |
5 | This product includes software developed by Alexander V. Zinin
6 |
7 | The original software is available from
8 | http://java.net/projects/tomcat-redis-session
9 |
10 | ================================================================================
11 | Apache Tomcat
12 | Copyright 1999-2011 The Apache Software Foundation
13 |
14 | This product includes software developed by
15 | The Apache Software Foundation (http://www.apache.org/).
16 |
17 | ================================================================================
18 |
19 | Apache log4j
20 | Copyright 2007 The Apache Software Foundation
21 |
22 | This product includes software developed at
23 | The Apache Software Foundation (http://www.apache.org/).
24 |
25 | ================================================================================
26 |
--------------------------------------------------------------------------------