View Javadoc

1   // ========================================================================
2   // Copyright (c) 1996-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses.
12  // ========================================================================
13  
14  package org.eclipse.jetty.server.session;
15  
16  import java.io.DataInputStream;
17  import java.io.File;
18  import java.io.FileInputStream;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.ObjectInputStream;
22  import java.util.ArrayList;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.Timer;
26  import java.util.TimerTask;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.ConcurrentMap;
29  
30  import javax.servlet.ServletContext;
31  import javax.servlet.http.HttpServletRequest;
32  
33  import org.eclipse.jetty.server.handler.ContextHandler;
34  import org.eclipse.jetty.util.log.Logger;
35  
36  
37  /* ------------------------------------------------------------ */
38  /** An in-memory implementation of SessionManager.
39   * <p>
40   * This manager supports saving sessions to disk, either periodically or at shutdown.
41   * Sessions can also have their content idle saved to disk to reduce the memory overheads of large idle sessions.
42   * <p>
43   * This manager will create it's own Timer instance to scavenge threads, unless it discovers a shared Timer instance
44   * set as the "org.eclipse.jetty.server.session.timer" attribute of the ContextHandler.
45   *
46   */
47  public class HashSessionManager extends AbstractSessionManager
48  {
49      final static Logger __log = SessionHandler.LOG;
50  
51      protected final ConcurrentMap<String,HashedSession> _sessions=new ConcurrentHashMap<String,HashedSession>();
52      private static int __id;
53      private Timer _timer;
54      private boolean _timerStop=false;
55      private TimerTask _task;
56      int _scavengePeriodMs=30000;
57      int _savePeriodMs=0; //don't do period saves by default
58      int _idleSavePeriodMs = 0; // don't idle save sessions by default.
59      private TimerTask _saveTask;
60      File _storeDir;
61      private boolean _lazyLoad=false;
62      private volatile boolean _sessionsLoaded=false;
63  
64      /* ------------------------------------------------------------ */
65      public HashSessionManager()
66      {
67          super();
68      }
69  
70      /* ------------------------------------------------------------ */
71      /* (non-Javadoc)
72       * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStart()
73       */
74      @Override
75      public void doStart() throws Exception
76      {
77          super.doStart();
78  
79          _timerStop=false;
80          ServletContext context = ContextHandler.getCurrentContext();
81          if (context!=null)
82              _timer=(Timer)context.getAttribute("org.eclipse.jetty.server.session.timer");
83          if (_timer==null)
84          {
85              _timerStop=true;
86              _timer=new Timer("HashSessionScavenger-"+__id++, true);
87          }
88  
89          setScavengePeriod(getScavengePeriod());
90  
91          if (_storeDir!=null)
92          {
93              if (!_storeDir.exists())
94                  _storeDir.mkdirs();
95  
96              if (!_lazyLoad)
97                  restoreSessions();
98          }
99  
100         setSavePeriod(getSavePeriod());
101     }
102 
103     /* ------------------------------------------------------------ */
104     /* (non-Javadoc)
105      * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStop()
106      */
107     @Override
108     public void doStop() throws Exception
109     {
110         // stop the scavengers
111         synchronized(this)
112         {
113             if (_saveTask!=null)
114                 _saveTask.cancel();
115             _saveTask=null;
116             if (_task!=null)
117                 _task.cancel();
118             _task=null;
119             if (_timer!=null && _timerStop)
120                 _timer.cancel();
121             _timer=null;
122         }
123 
124         // This will callback invalidate sessions - where we decide if we will save
125         super.doStop();
126 
127         _sessions.clear();
128 
129     }
130 
131     /* ------------------------------------------------------------ */
132     /**
133      * @return the period in seconds at which a check is made for sessions to be invalidated.
134      */
135     public int getScavengePeriod()
136     {
137         return _scavengePeriodMs/1000;
138     }
139 
140 
141     /* ------------------------------------------------------------ */
142     @Override
143     public int getSessions()
144     {
145         int sessions=super.getSessions();
146         if (__log.isDebugEnabled())
147         {
148             if (_sessions.size()!=sessions)
149                 __log.warn("sessions: "+_sessions.size()+"!="+sessions);
150         }
151         return sessions;
152     }
153 
154     /* ------------------------------------------------------------ */
155     /**
156      * @return seconds Idle period after which a session is saved
157      */
158     public int getIdleSavePeriod()
159     {
160       if (_idleSavePeriodMs <= 0)
161         return 0;
162 
163       return _idleSavePeriodMs / 1000;
164     }
165 
166     /* ------------------------------------------------------------ */
167     /**
168      * Configures the period in seconds after which a session is deemed idle and saved
169      * to save on session memory.
170      *
171      * The session is persisted, the values attribute map is cleared and the session set to idled.
172      *
173      * @param seconds Idle period after which a session is saved
174      */
175     public void setIdleSavePeriod(int seconds)
176     {
177       _idleSavePeriodMs = seconds * 1000;
178     }
179 
180     /* ------------------------------------------------------------ */
181     @Override
182     public void setMaxInactiveInterval(int seconds)
183     {
184         super.setMaxInactiveInterval(seconds);
185         if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000)
186             setScavengePeriod((_dftMaxIdleSecs+9)/10);
187     }
188 
189     /* ------------------------------------------------------------ */
190     /**
191      * @param seconds the period is seconds at which sessions are periodically saved to disk
192      */
193     public void setSavePeriod (int seconds)
194     {
195         int period = (seconds * 1000);
196         if (period < 0)
197             period=0;
198         _savePeriodMs=period;
199 
200         if (_timer!=null)
201         {
202             synchronized (this)
203             {
204                 if (_saveTask!=null)
205                     _saveTask.cancel();
206                 if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
207                 {
208                     _saveTask = new TimerTask()
209                     {
210                         @Override
211                         public void run()
212                         {
213                             try
214                             {
215                                 saveSessions(true);
216                             }
217                             catch (Exception e)
218                             {
219                                 __log.warn(e);
220                             }
221                         }
222                     };
223                     _timer.schedule(_saveTask,_savePeriodMs,_savePeriodMs);
224                 }
225             }
226         }
227     }
228 
229     /* ------------------------------------------------------------ */
230     /**
231      * @return the period in seconds at which sessions are periodically saved to disk
232      */
233     public int getSavePeriod ()
234     {
235         if (_savePeriodMs<=0)
236             return 0;
237 
238         return _savePeriodMs/1000;
239     }
240 
241     /* ------------------------------------------------------------ */
242     /**
243      * @param seconds the period in seconds at which a check is made for sessions to be invalidated.
244      */
245     public void setScavengePeriod(int seconds)
246     {
247         if (seconds==0)
248             seconds=60;
249 
250         int old_period=_scavengePeriodMs;
251         int period=seconds*1000;
252         if (period>60000)
253             period=60000;
254         if (period<1000)
255             period=1000;
256 
257         _scavengePeriodMs=period;
258         if (_timer!=null && (period!=old_period || _task==null))
259         {
260             synchronized (this)
261             {
262                 if (_task!=null)
263                     _task.cancel();
264                 _task = new TimerTask()
265                 {
266                     @Override
267                     public void run()
268                     {
269                         scavenge();
270                     }
271                 };
272                 _timer.schedule(_task,_scavengePeriodMs,_scavengePeriodMs);
273             }
274         }
275     }
276 
277     /* -------------------------------------------------------------- */
278     /**
279      * Find sessions that have timed out and invalidate them. This runs in the
280      * SessionScavenger thread.
281      */
282     protected void scavenge()
283     {
284         //don't attempt to scavenge if we are shutting down
285         if (isStopping() || isStopped())
286             return;
287 
288         Thread thread=Thread.currentThread();
289         ClassLoader old_loader=thread.getContextClassLoader();
290         try
291         {
292             if (_loader!=null)
293                 thread.setContextClassLoader(_loader);
294 
295             // For each session
296             long now=System.currentTimeMillis();
297             for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();)
298             {
299                 HashedSession session=i.next();
300                 long idleTime=session.getMaxInactiveInterval()*1000;
301                 if (idleTime>0&&session.getAccessed()+idleTime<now)
302                 {
303                     // Found a stale session, add it to the list
304                     session.timeout();
305                 }
306                 else if (_idleSavePeriodMs>0&&session.getAccessed()+_idleSavePeriodMs<now)
307                 {
308                     session.idle();
309                 }
310             }
311         }
312         catch (Throwable t)
313         {
314             __log.warn("Problem scavenging sessions", t);
315         }
316         finally
317         {
318             thread.setContextClassLoader(old_loader);
319         }
320     }
321 
322     /* ------------------------------------------------------------ */
323     @Override
324     protected void addSession(AbstractSession session)
325     {
326         if (isRunning())
327             _sessions.put(session.getClusterId(),(HashedSession)session);
328     }
329 
330     /* ------------------------------------------------------------ */
331     @Override
332     public AbstractSession getSession(String idInCluster)
333     {
334         if ( _lazyLoad && !_sessionsLoaded)
335         {
336             try
337             {
338                 restoreSessions();
339             }
340             catch(Exception e)
341             {
342                 __log.warn(e);
343             }
344         }
345 
346         Map<String,HashedSession> sessions=_sessions;
347         if (sessions==null)
348             return null;
349 
350         HashedSession session = sessions.get(idInCluster);
351 
352         if (session == null && _lazyLoad)
353             session=restoreSession(idInCluster);
354         if (session == null)
355             return null;
356 
357         if (_idleSavePeriodMs!=0)
358             session.deIdle();
359 
360         return session;
361     }
362 
363     /* ------------------------------------------------------------ */
364     @Override
365     protected void invalidateSessions() throws Exception
366     {
367         // Invalidate all sessions to cause unbind events
368         ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
369         int loop=100;
370         while (sessions.size()>0 && loop-->0)
371         {
372             // If we are called from doStop
373             if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite())
374             {
375                 // Then we only save and remove the session - it is not invalidated.
376                 for (HashedSession session : sessions)
377                 {
378                     session.save(false);
379                     removeSession(session,false);
380                 }
381             }
382             else
383             {
384                 for (HashedSession session : sessions)
385                     session.invalidate();
386             }
387 
388             // check that no new sessions were created while we were iterating
389             sessions=new ArrayList<HashedSession>(_sessions.values());
390         }
391     }
392 
393     /* ------------------------------------------------------------ */
394     @Override
395     protected AbstractSession newSession(HttpServletRequest request)
396     {
397         return new HashedSession(this, request);
398     }
399 
400     /* ------------------------------------------------------------ */
401     protected AbstractSession newSession(long created, long accessed, String clusterId)
402     {
403         return new HashedSession(this, created,accessed, clusterId);
404     }
405 
406     /* ------------------------------------------------------------ */
407     @Override
408     protected boolean removeSession(String clusterId)
409     {
410         return _sessions.remove(clusterId)!=null;
411     }
412 
413     /* ------------------------------------------------------------ */
414     public void setStoreDirectory (File dir)
415     {
416         _storeDir=dir;
417     }
418 
419     /* ------------------------------------------------------------ */
420     public File getStoreDirectory ()
421     {
422         return _storeDir;
423     }
424 
425     /* ------------------------------------------------------------ */
426     public void setLazyLoad(boolean lazyLoad)
427     {
428         _lazyLoad = lazyLoad;
429     }
430 
431     /* ------------------------------------------------------------ */
432     public boolean isLazyLoad()
433     {
434         return _lazyLoad;
435     }
436 
437     /* ------------------------------------------------------------ */
438     public void restoreSessions () throws Exception
439     {
440         _sessionsLoaded = true;
441 
442         if (_storeDir==null || !_storeDir.exists())
443         {
444             return;
445         }
446 
447         if (!_storeDir.canRead())
448         {
449             __log.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
450             return;
451         }
452 
453         String[] files = _storeDir.list();
454         for (int i=0;files!=null&&i<files.length;i++)
455         {
456             restoreSession(files[i]);
457         }
458     }
459 
460     /* ------------------------------------------------------------ */
461     protected synchronized HashedSession restoreSession(String idInCuster)
462     {
463         try
464         {
465             File file = new File(_storeDir,idInCuster);
466             if (file.exists())
467             {
468                 FileInputStream in = new FileInputStream(file);
469                 HashedSession session = restoreSession(in, null);
470                 in.close();
471                 addSession(session, false);
472                 session.didActivate();
473                 file.delete();
474                 return session;
475             }
476         }
477         catch (Exception e)
478         {
479             __log.warn("Problem restoring session "+idInCuster, e);
480         }
481         return null;
482     }
483 
484     /* ------------------------------------------------------------ */
485     public void saveSessions(boolean reactivate) throws Exception
486     {
487         if (_storeDir==null || !_storeDir.exists())
488         {
489             return;
490         }
491 
492         if (!_storeDir.canWrite())
493         {
494             __log.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
495             return;
496         }
497 
498         for (HashedSession session : _sessions.values())
499             session.save(true);
500     }
501 
502     /* ------------------------------------------------------------ */
503     public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception
504     {
505         /*
506          * Take care of this class's fields first by calling
507          * defaultReadObject
508          */
509         DataInputStream in = new DataInputStream(is);
510         String clusterId = in.readUTF();
511         in.readUTF(); // nodeId
512         long created = in.readLong();
513         long accessed = in.readLong();
514         int requests = in.readInt();
515 
516         if (session == null)
517             session = (HashedSession)newSession(created, accessed, clusterId);
518         session.setRequests(requests);
519         int size = in.readInt();
520         if (size>0)
521         {
522             ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(in);
523             for (int i=0; i<size;i++)
524             {
525                 String key = ois.readUTF();
526                 Object value = ois.readObject();
527                 session.setAttribute(key,value);
528             }
529             ois.close();
530         }
531         else
532             in.close();
533         return session;
534     }
535 
536 
537     /* ------------------------------------------------------------ */
538     /* ------------------------------------------------------------ */
539     protected class ClassLoadingObjectInputStream extends ObjectInputStream
540     {
541         /* ------------------------------------------------------------ */
542         public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
543         {
544             super(in);
545         }
546 
547         /* ------------------------------------------------------------ */
548         public ClassLoadingObjectInputStream () throws IOException
549         {
550             super();
551         }
552 
553         /* ------------------------------------------------------------ */
554         @Override
555         public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
556         {
557             try
558             {
559                 return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
560             }
561             catch (ClassNotFoundException e)
562             {
563                 return super.resolveClass(cl);
564             }
565         }
566     }
567 }