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