View Javadoc

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