View Javadoc

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 }