1 //
2 // ========================================================================
3 // Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.session.infinispan;
20
21 import java.util.Random;
22 import java.util.concurrent.TimeUnit;
23
24 import javax.servlet.http.HttpServletRequest;
25 import javax.servlet.http.HttpSession;
26
27 import org.eclipse.jetty.server.Handler;
28 import org.eclipse.jetty.server.Server;
29 import org.eclipse.jetty.server.SessionManager;
30 import org.eclipse.jetty.server.handler.ContextHandler;
31 import org.eclipse.jetty.server.session.AbstractSession;
32 import org.eclipse.jetty.server.session.AbstractSessionIdManager;
33 import org.eclipse.jetty.server.session.SessionHandler;
34 import org.eclipse.jetty.util.log.Log;
35 import org.eclipse.jetty.util.log.Logger;
36 import org.infinispan.commons.api.BasicCache;
37
38
39
40
41 /**
42 * InfinispanSessionIdManager
43 *
44 * Maintain a set of in-use session ids. This session id manager does NOT locally store
45 * a list of in-use sesssion ids, but rather stores them in the cluster cache. Thus,
46 * all operations to this session manager involve interaction with a possibly remote
47 * cache.
48 *
49 * For each session id that is in-use, an entry of the following form is put into
50 * the cluster cache:
51 * <pre>
52 * ("__o.e.j.s.infinispanIdMgr__"+[id], [id])
53 * </pre>
54 * where [id] is the id of the session.
55 *
56 * If the first session to be added is not immortal (ie it has a timeout on it) then
57 * the corresponding session id is entered into infinispan with an idle expiry timeout
58 * equivalent to double the session's timeout (the multiplier is configurable).
59 *
60 *
61 * Having one entry per in-use session id means that there is no contention on
62 * cache entries (as would be the case if a single entry was kept containing a
63 * list of in-use session ids).
64 *
65 *
66 */
67 public class InfinispanSessionIdManager extends AbstractSessionIdManager
68 {
69 private final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
70 public final static String ID_KEY = "__o.e.j.s.infinispanIdMgr__";
71 public static final int DEFAULT_IDLE_EXPIRY_MULTIPLE = 2;
72 protected BasicCache<String,Object> _cache;
73 private Server _server;
74 private int _idleExpiryMultiple = DEFAULT_IDLE_EXPIRY_MULTIPLE;
75
76
77
78
79
80 public InfinispanSessionIdManager(Server server)
81 {
82 super();
83 _server = server;
84 }
85
86 public InfinispanSessionIdManager(Server server, Random random)
87 {
88 super(random);
89 _server = server;
90 }
91
92
93
94 /**
95 * Start the id manager.
96 * @see org.eclipse.jetty.server.session.AbstractSessionIdManager#doStart()
97 */
98 @Override
99 protected void doStart() throws Exception
100 {
101 super.doStart();
102 }
103
104
105
106 /**
107 * Stop the id manager
108 * @see org.eclipse.jetty.server.session.AbstractSessionIdManager#doStop()
109 */
110 @Override
111 protected void doStop() throws Exception
112 {
113 super.doStop();
114 }
115
116
117
118
119
120 /**
121 * Check to see if the given session id is being
122 * used by a session in any context.
123 *
124 * This method will consult the cluster.
125 *
126 * @see org.eclipse.jetty.server.SessionIdManager#idInUse(java.lang.String)
127 */
128 @Override
129 public boolean idInUse(String id)
130 {
131 if (id == null)
132 return false;
133
134 String clusterId = getClusterId(id);
135
136 //ask the cluster - this should also tickle the idle expiration timer on the sessionid entry
137 //keeping it valid
138 try
139 {
140 return exists(clusterId);
141 }
142 catch (Exception e)
143 {
144 LOG.warn("Problem checking inUse for id="+clusterId, e);
145 return false;
146 }
147
148 }
149
150 /**
151 * Remember a new in-use session id.
152 *
153 * This will save the in-use session id to the cluster.
154 *
155 * @see org.eclipse.jetty.server.SessionIdManager#addSession(javax.servlet.http.HttpSession)
156 */
157 @Override
158 public void addSession(HttpSession session)
159 {
160 if (session == null)
161 return;
162
163 //insert into the cache and set an idle expiry on the entry that
164 //is based off the max idle time configured for the session. If the
165 //session is immortal, then there is no idle expiry on the corresponding
166 //session id
167 if (session.getMaxInactiveInterval() == 0)
168 insert (((AbstractSession)session).getClusterId());
169 else
170 insert (((AbstractSession)session).getClusterId(), session.getMaxInactiveInterval() * getIdleExpiryMultiple());
171 }
172
173
174 public void setIdleExpiryMultiple (int multiplier)
175 {
176 if (multiplier <= 1)
177 {
178 LOG.warn("Idle expiry multiple of {} for session ids set to less than minimum. Using value of {} instead.", multiplier, DEFAULT_IDLE_EXPIRY_MULTIPLE);
179 }
180 _idleExpiryMultiple = multiplier;
181 }
182
183 public int getIdleExpiryMultiple ()
184 {
185 return _idleExpiryMultiple;
186 }
187
188
189 /**
190 * Remove a session id from the list of in-use ids.
191 *
192 * This will remvove the corresponding session id from the cluster.
193 *
194 * @see org.eclipse.jetty.server.SessionIdManager#removeSession(javax.servlet.http.HttpSession)
195 */
196 @Override
197 public void removeSession(HttpSession session)
198 {
199 if (session == null)
200 return;
201
202 //delete from the cache
203 delete (((AbstractSession)session).getClusterId());
204 }
205
206 /**
207 * Remove a session id. This compels all other contexts who have a session
208 * with the same id to also remove it.
209 *
210 * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
211 */
212 @Override
213 public void invalidateAll(String id)
214 {
215 //delete the session id from list of in-use sessions
216 delete (id);
217
218
219 //tell all contexts that may have a session object with this id to
220 //get rid of them
221 Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
222 for (int i=0; contexts!=null && i<contexts.length; i++)
223 {
224 SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
225 if (sessionHandler != null)
226 {
227 SessionManager manager = sessionHandler.getSessionManager();
228
229 if (manager != null && manager instanceof InfinispanSessionManager)
230 {
231 ((InfinispanSessionManager)manager).invalidateSession(id);
232 }
233 }
234 }
235
236 }
237
238 /**
239 * Change a session id.
240 *
241 * Typically this occurs when a previously existing session has passed through authentication.
242 *
243 * @see org.eclipse.jetty.server.session.AbstractSessionIdManager#renewSessionId(java.lang.String, java.lang.String, javax.servlet.http.HttpServletRequest)
244 */
245 @Override
246 public void renewSessionId(String oldClusterId, String oldNodeId, HttpServletRequest request)
247 {
248 //generate a new id
249 String newClusterId = newSessionId(request.hashCode());
250
251 delete(oldClusterId);
252 insert(newClusterId);
253
254
255 //tell all contexts to update the id
256 Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
257 for (int i=0; contexts!=null && i<contexts.length; i++)
258 {
259 SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
260 if (sessionHandler != null)
261 {
262 SessionManager manager = sessionHandler.getSessionManager();
263
264 if (manager != null && manager instanceof InfinispanSessionManager)
265 {
266 ((InfinispanSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
267 }
268 }
269 }
270
271 }
272
273 /**
274 * Get the cache.
275 * @return the cache
276 */
277 public BasicCache<String,Object> getCache()
278 {
279 return _cache;
280 }
281
282 /**
283 * Set the cache.
284 * @param cache the cache
285 */
286 public void setCache(BasicCache<String,Object> cache)
287 {
288 this._cache = cache;
289 }
290
291
292
293 /**
294 * Do any operation to the session id in the cache to
295 * ensure its idle expiry time moves forward
296 * @param id the session id
297 */
298 public void touch (String id)
299 {
300 exists(id);
301 }
302
303
304
305 /**
306 * Ask the cluster if a particular id exists.
307 *
308 * @param id the session id
309 * @return true if exists
310 */
311 protected boolean exists (String id)
312 {
313 if (_cache == null)
314 throw new IllegalStateException ("No cache");
315
316 return _cache.containsKey(makeKey(id));
317 }
318
319
320 /**
321 * Put a session id into the cluster.
322 *
323 * @param id the session id
324 */
325 protected void insert (String id)
326 {
327 if (_cache == null)
328 throw new IllegalStateException ("No cache");
329
330 _cache.putIfAbsent(makeKey(id), id);
331 }
332
333
334 /**
335 * Put a session id into the cluster with an idle expiry.
336 *
337 * @param id the session id
338 * @param idleTimeOutSec idle timeout in seconds
339 */
340 protected void insert (String id, long idleTimeOutSec)
341 {
342 if (_cache == null)
343 throw new IllegalStateException ("No cache");
344
345 _cache.putIfAbsent(makeKey(id),id,-1L, TimeUnit.SECONDS, idleTimeOutSec, TimeUnit.SECONDS);
346 }
347
348
349 /**
350 * Remove a session id from the cluster.
351 *
352 * @param id the session id
353 */
354 protected void delete (String id)
355 {
356 if (_cache == null)
357 throw new IllegalStateException ("No cache");
358
359 _cache.remove(makeKey(id));
360 }
361
362
363
364 /**
365 * Generate a unique cache key from the session id.
366 *
367 * @param id the session id
368 * @return unique cache id
369 */
370 protected String makeKey (String id)
371 {
372 return ID_KEY+id;
373 }
374 }