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             if (t instanceof ThreadDeath)
315                 throw ((ThreadDeath)t);
316             else
317                 __log.warn("Problem scavenging sessions", t);
318         }
319         finally
320         {
321             thread.setContextClassLoader(old_loader);
322         }
323     }
324     
325     /* ------------------------------------------------------------ */
326     @Override
327     protected void addSession(AbstractSession session)
328     {
329         if (isRunning())
330             _sessions.put(session.getClusterId(),(HashedSession)session);
331     }
332     
333     /* ------------------------------------------------------------ */
334     @Override
335     public AbstractSession getSession(String idInCluster)
336     {
337         if ( _lazyLoad && !_sessionsLoaded)
338         {
339             try
340             {
341                 restoreSessions();
342             }
343             catch(Exception e)
344             {
345                 __log.warn(e);
346             }
347         }
348 
349         Map<String,HashedSession> sessions=_sessions;
350         if (sessions==null)
351             return null;
352         
353         HashedSession session = sessions.get(idInCluster);
354 
355         if (session == null && _lazyLoad)
356             session=restoreSession(idInCluster);
357         if (session == null)
358             return null;
359 
360         if (_idleSavePeriodMs!=0)
361             session.deIdle();
362         
363         return session;
364     }
365 
366     /* ------------------------------------------------------------ */
367     @Override
368     protected void invalidateSessions() throws Exception
369     {
370         // Invalidate all sessions to cause unbind events
371         ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
372         int loop=100;
373         while (sessions.size()>0 && loop-->0)
374         {
375             // If we are called from doStop
376             if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite())
377             {
378                 // Then we only save and remove the session - it is not invalidated.
379                 for (HashedSession session : sessions)
380                 {
381                     session.save(false);
382                     removeSession(session,false);
383                 }
384             }
385             else
386             {
387                 for (HashedSession session : sessions)
388                     session.invalidate();
389             }
390             
391             // check that no new sessions were created while we were iterating
392             sessions=new ArrayList<HashedSession>(_sessions.values());
393         }
394     }
395 
396     /* ------------------------------------------------------------ */
397     @Override
398     protected AbstractSession newSession(HttpServletRequest request)
399     {
400         return new HashedSession(this, request);
401     }
402     
403     /* ------------------------------------------------------------ */
404     protected AbstractSession newSession(long created, long accessed, String clusterId)
405     {
406         return new HashedSession(this, created,accessed, clusterId);
407     }
408     
409     /* ------------------------------------------------------------ */
410     @Override
411     protected boolean removeSession(String clusterId)
412     {
413         return _sessions.remove(clusterId)!=null;
414     }
415 
416     /* ------------------------------------------------------------ */
417     public void setStoreDirectory (File dir)
418     {
419         _storeDir=dir;
420     }
421 
422     /* ------------------------------------------------------------ */
423     public File getStoreDirectory ()
424     {
425         return _storeDir;
426     }
427 
428     /* ------------------------------------------------------------ */
429     public void setLazyLoad(boolean lazyLoad)
430     {
431         _lazyLoad = lazyLoad;
432     }
433     
434     /* ------------------------------------------------------------ */
435     public boolean isLazyLoad()
436     {
437         return _lazyLoad;
438     }
439 
440     /* ------------------------------------------------------------ */
441     public void restoreSessions () throws Exception
442     {
443         _sessionsLoaded = true;
444         
445         if (_storeDir==null || !_storeDir.exists())
446         {
447             return;
448         }
449 
450         if (!_storeDir.canRead())
451         {
452             __log.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
453             return;
454         }
455 
456         String[] files = _storeDir.list();
457         for (int i=0;files!=null&&i<files.length;i++)
458         {
459             restoreSession(files[i]);
460         }
461     }
462 
463     /* ------------------------------------------------------------ */
464     protected synchronized HashedSession restoreSession(String idInCuster)
465     {
466         try
467         {
468             File file = new File(_storeDir,idInCuster);
469             if (file.exists())
470             {
471                 FileInputStream in = new FileInputStream(file);           
472                 HashedSession session = restoreSession(in, null);
473                 in.close();          
474                 addSession(session, false);
475                 session.didActivate();
476                 file.delete();
477                 return session;
478             }
479         }
480         catch (Exception e)
481         {
482             __log.warn("Problem restoring session "+idInCuster, e);
483         }
484         return null;
485     }
486     
487     /* ------------------------------------------------------------ */
488     public void saveSessions(boolean reactivate) throws Exception
489     {
490         if (_storeDir==null || !_storeDir.exists())
491         {
492             return;
493         }
494         
495         if (!_storeDir.canWrite())
496         {
497             __log.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
498             return;
499         }
500 
501         for (HashedSession session : _sessions.values())
502             session.save(true);
503     }
504 
505     /* ------------------------------------------------------------ */
506     public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception
507     {
508         /*
509          * Take care of this class's fields first by calling 
510          * defaultReadObject
511          */
512         DataInputStream in = new DataInputStream(is);
513         String clusterId = in.readUTF();
514         in.readUTF(); // nodeId
515         long created = in.readLong();
516         long accessed = in.readLong();
517         int requests = in.readInt();
518       
519         if (session == null)
520             session = (HashedSession)newSession(created, accessed, clusterId);
521         session.setRequests(requests);
522         int size = in.readInt();
523         if (size>0)
524         {
525             ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(in);
526             for (int i=0; i<size;i++)
527             {
528                 String key = ois.readUTF();
529                 Object value = ois.readObject();
530                 session.setAttribute(key,value);
531             }
532             ois.close();
533         }
534         else
535             in.close();
536         return session;
537     }
538 
539     
540     /* ------------------------------------------------------------ */
541     /* ------------------------------------------------------------ */
542     protected class ClassLoadingObjectInputStream extends ObjectInputStream
543     {
544         /* ------------------------------------------------------------ */
545         public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
546         {
547             super(in);
548         }
549 
550         /* ------------------------------------------------------------ */
551         public ClassLoadingObjectInputStream () throws IOException
552         {
553             super();
554         }
555 
556         /* ------------------------------------------------------------ */
557         @Override
558         public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
559         {
560             try
561             {
562                 return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
563             }
564             catch (ClassNotFoundException e)
565             {
566                 return super.resolveClass(cl);
567             }
568         }
569     }
570 }