View Javadoc

1   // ========================================================================
2   // Copyright 1996-2005 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty.servlet;
16  
17  import java.io.DataInputStream;
18  import java.io.DataOutputStream;
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.io.ObjectInputStream;
24  import java.io.ObjectOutputStream;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.Map;
30  import java.util.Timer;
31  import java.util.TimerTask;
32  
33  import javax.servlet.http.HttpServletRequest;
34  
35  import org.mortbay.log.Log;
36  import org.mortbay.util.LazyList;
37  
38  
39  /* ------------------------------------------------------------ */
40  /** An in-memory implementation of SessionManager.
41   *
42   * @author Greg Wilkins (gregw)
43   */
44  public class HashSessionManager extends AbstractSessionManager
45  {
46      private Timer _timer;
47      private TimerTask _task;
48      private int _scavengePeriodMs=30000;
49      private int _savePeriodMs=0; //don't do period saves by default
50      private TimerTask _saveTask;
51      protected Map _sessions;
52      private File _storeDir;
53      private boolean _lazyLoad=false;
54      private boolean _sessionsLoaded=false;
55      
56      /* ------------------------------------------------------------ */
57      public HashSessionManager()
58      {
59          super();
60      }
61  
62      /* ------------------------------------------------------------ */
63      /* (non-Javadoc)
64       * @see org.mortbay.jetty.servlet.AbstractSessionManager#doStart()
65       */
66      public void doStart() throws Exception
67      {
68          _sessions=new HashMap();
69          super.doStart();
70  
71          _timer=new Timer(true);
72          
73          setScavengePeriod(getScavengePeriod());
74  
75          if (_storeDir!=null)
76          {
77              if (!_storeDir.exists())
78                  _storeDir.mkdir();
79  
80              if (!_lazyLoad)
81                  restoreSessions();
82          }
83   
84          setSavePeriod(getSavePeriod());
85      }
86  
87      /* ------------------------------------------------------------ */
88      /* (non-Javadoc)
89       * @see org.mortbay.jetty.servlet.AbstractSessionManager#doStop()
90       */
91      public void doStop() throws Exception
92      {
93          
94          if (_storeDir != null)
95              saveSessions();
96          
97          super.doStop();
98   
99          _sessions.clear();
100         _sessions=null;
101 
102         // stop the scavenger
103         synchronized(this)
104         {
105             if (_saveTask!=null)
106                 _saveTask.cancel();
107             if (_task!=null)
108                 _task.cancel();
109             if (_timer!=null)
110                 _timer.cancel();
111             _timer=null;
112         }
113     }
114 
115     /* ------------------------------------------------------------ */
116     /**
117      * @return seconds
118      */
119     public int getScavengePeriod()
120     {
121         return _scavengePeriodMs/1000;
122     }
123 
124     
125     /* ------------------------------------------------------------ */
126     public Map getSessionMap()
127     {
128         return Collections.unmodifiableMap(_sessions);
129     }
130 
131 
132     /* ------------------------------------------------------------ */
133     public int getSessions()
134     {
135         return _sessions.size();
136     }
137 
138 
139     /* ------------------------------------------------------------ */
140     public void setMaxInactiveInterval(int seconds)
141     {
142         super.setMaxInactiveInterval(seconds);
143         if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000)
144             setScavengePeriod((_dftMaxIdleSecs+9)/10);
145     }
146 
147     /* ------------------------------------------------------------ */
148     public void setSavePeriod (int seconds)
149     {
150         int oldSavePeriod = _savePeriodMs;
151         int period = (seconds * 1000);
152         if (period < 0)
153             period=0;
154         _savePeriodMs=period;
155         
156         if (_timer!=null)
157         {
158             synchronized (this)
159             {
160                 if (_saveTask!=null)
161                     _saveTask.cancel();
162                 if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
163                 {
164                     _saveTask = new TimerTask()
165                     {
166                         public void run()
167                         {
168                             try
169                             {
170                                 saveSessions();
171                             }
172                             catch (Exception e)
173                             {
174                                 Log.warn(e);
175                             }
176                         }   
177                     };
178                     _timer.schedule(_saveTask,_savePeriodMs,_savePeriodMs);
179                 }
180             }
181         }
182     }
183 
184     /* ------------------------------------------------------------ */
185     public int getSavePeriod ()
186     {
187         if (_savePeriodMs<=0)
188             return 0;
189         
190         return _savePeriodMs/1000;
191     }
192     /* ------------------------------------------------------------ */
193     /**
194      * @param seconds
195      */
196     public void setScavengePeriod(int seconds)
197     {
198         if (seconds==0)
199             seconds=60;
200 
201         int old_period=_scavengePeriodMs;
202         int period=seconds*1000;
203         if (period>60000)
204             period=60000;
205         if (period<1000)
206             period=1000;
207 
208         _scavengePeriodMs=period;
209         if (_timer!=null && (period!=old_period || _task==null))
210         {
211             synchronized (this)
212             {
213                 if (_task!=null)
214                     _task.cancel();
215                 _task = new TimerTask()
216                 {
217                     public void run()
218                     {
219                         scavenge();
220                     }   
221                 };
222                 _timer.schedule(_task,_scavengePeriodMs,_scavengePeriodMs);
223             }
224         }
225     }
226     
227     /* -------------------------------------------------------------- */
228     /**
229      * Find sessions that have timed out and invalidate them. This runs in the
230      * SessionScavenger thread.
231      */
232     private void scavenge()
233     {
234         //don't attempt to scavenge if we are shutting down
235         if (isStopping() || isStopped())
236             return;
237         
238         Thread thread=Thread.currentThread();
239         ClassLoader old_loader=thread.getContextClassLoader();
240         try
241         {
242             if (_loader!=null)
243                 thread.setContextClassLoader(_loader);
244 
245             long now=System.currentTimeMillis();
246 
247             try
248             {
249                 if (!_sessionsLoaded && _lazyLoad)
250                     restoreSessions();
251             }
252             catch(Exception e)
253             {
254                 Log.debug(e);
255             }
256             
257             // Since Hashtable enumeration is not safe over deletes,
258             // we build a list of stale sessions, then go back and invalidate
259             // them
260             Object stale=null;
261 
262             synchronized (HashSessionManager.this)
263             {
264                 // For each session
265                 for (Iterator i=_sessions.values().iterator(); i.hasNext();)
266                 {
267                     Session session=(Session)i.next();
268                     long idleTime=session._maxIdleMs;
269                     if (idleTime>0&&session._accessed+idleTime<now)
270                     {
271                         // Found a stale session, add it to the list
272                         stale=LazyList.add(stale,session);
273                     }
274                 }
275             }
276 
277             // Remove the stale sessions
278             for (int i=LazyList.size(stale); i-->0;)
279             {
280                 // check it has not been accessed in the meantime
281                 Session session=(Session)LazyList.get(stale,i);
282                 long idleTime=session._maxIdleMs;
283                 if (idleTime>0&&session._accessed+idleTime<System.currentTimeMillis())
284                 {
285                     ((Session)session).timeout();
286                     int nbsess=this._sessions.size();
287                     if (nbsess<this._minSessions)
288                         this._minSessions=nbsess;
289                 }
290             }
291         }
292         catch (Throwable t)
293         {
294             if (t instanceof ThreadDeath)
295                 throw ((ThreadDeath)t);
296             else
297                 Log.warn("Problem scavenging sessions", t);
298         }
299         finally
300         {
301             thread.setContextClassLoader(old_loader);
302         }
303     }
304     
305     /* ------------------------------------------------------------ */
306     protected void addSession(AbstractSessionManager.Session session)
307     {
308         _sessions.put(session.getClusterId(),session);
309     }
310     
311     /* ------------------------------------------------------------ */
312     public AbstractSessionManager.Session getSession(String idInCluster)
313     {
314         try
315         {
316             if (!_sessionsLoaded && _lazyLoad)
317                 restoreSessions();
318         }
319         catch(Exception e)
320         {
321             Log.warn(e);
322         }
323         
324         if (_sessions==null)
325             return null;
326 
327         return (Session)_sessions.get(idInCluster);
328     }
329 
330     /* ------------------------------------------------------------ */
331     protected void invalidateSessions()
332     {
333         // Invalidate all sessions to cause unbind events
334         ArrayList sessions=new ArrayList(_sessions.values());
335         for (Iterator i=sessions.iterator(); i.hasNext();)
336         {
337             Session session=(Session)i.next();
338             session.invalidate();
339         }
340         _sessions.clear();
341         
342     }
343 
344     /* ------------------------------------------------------------ */
345     protected AbstractSessionManager.Session newSession(HttpServletRequest request)
346     {
347         return new Session(request);
348     }
349     
350     /* ------------------------------------------------------------ */
351     protected void removeSession(String clusterId)
352     {
353         _sessions.remove(clusterId);
354     }
355     
356     /* ------------------------------------------------------------ */
357     public void setStoreDirectory (File dir)
358     {
359         _storeDir=dir;
360     }
361     
362     /* ------------------------------------------------------------ */
363     public File getStoreDirectory ()
364     {
365         return _storeDir;
366     }
367     
368     /* ------------------------------------------------------------ */
369     public void setLazyLoad(boolean lazyLoad)
370     {
371         _lazyLoad = lazyLoad;
372     }
373 
374     /* ------------------------------------------------------------ */
375     public boolean isLazyLoad()
376     {
377         return _lazyLoad;
378     }
379 
380     /* ------------------------------------------------------------ */
381     public void restoreSessions () throws Exception
382     {
383         if (_storeDir==null || !_storeDir.exists())
384         {
385             return;
386         }
387 
388         if (!_storeDir.canRead())
389         {
390             Log.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
391             return;
392         }
393 
394         File[] files = _storeDir.listFiles();
395         for (int i=0;files!=null&&i<files.length;i++)
396         {
397             try
398             {
399                 FileInputStream in = new FileInputStream(files[i]);           
400                 Session session = restoreSession(in);
401                 in.close();          
402                 addSession(session, false);
403                 files[i].delete();
404             }
405             catch (Exception e)
406             {
407                 Log.warn("Problem restoring session "+files[i].getName(), e);
408             }
409         }
410         
411         _sessionsLoaded = true;
412     }
413 
414     /* ------------------------------------------------------------ */
415     public void saveSessions () throws Exception
416     {
417         if (_storeDir==null || !_storeDir.exists())
418         {
419             return;
420         }
421         
422         if (!_storeDir.canWrite())
423         {
424             Log.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
425             return;
426         }
427  
428         synchronized (this)
429         {
430             Iterator itor = _sessions.entrySet().iterator();
431             while (itor.hasNext())
432             {
433                 Map.Entry entry = (Map.Entry)itor.next();
434                 String id = (String)entry.getKey();
435                 Session session = (Session)entry.getValue();
436                 try
437                 {
438                     File file = new File (_storeDir, id);
439                     if (file.exists())
440                         file.delete();
441                     file.createNewFile();
442                     FileOutputStream fos = new FileOutputStream (file);
443                     session.save(fos);
444                     fos.close();
445                 }
446                 catch (Exception e)
447                 {
448                     Log.warn("Problem persisting session "+id, e);
449                 }
450             }
451         }
452     }
453 
454     /* ------------------------------------------------------------ */
455     public Session restoreSession (FileInputStream fis) 
456     throws Exception
457     {
458 
459         /*
460          * Take care of this class's fields first by calling 
461          * defaultReadObject
462          */
463         
464         DataInputStream in = new DataInputStream(fis);
465         String clusterId = in.readUTF();
466         String nodeId = in.readUTF();
467         boolean idChanged = in.readBoolean();
468         long created = in.readLong();
469         long cookieSet = in.readLong();
470         long accessed = in.readLong();
471         long lastAccessed = in.readLong();
472         //boolean invalid = in.readBoolean();
473         //boolean invalidate = in.readBoolean();
474         //long maxIdle = in.readLong();
475         //boolean isNew = in.readBoolean();
476         int requests = in.readInt();
477         
478         Session session = new Session (created, clusterId);
479         session._cookieSet = cookieSet;
480         session._lastAccessed = lastAccessed;
481         
482         int size = in.readInt();
483         if (size > 0)
484         {
485             ArrayList keys = new ArrayList();
486             for (int i=0; i<size; i++)
487             {
488                 String key = in.readUTF();
489                 keys.add(key);
490             }
491             ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(in);
492             for (int i=0;i<size;i++)
493             {
494                 Object value = ois.readObject();
495                 session.setAttribute((String)keys.get(i),value);
496             }
497             ois.close();
498         }
499         else
500             session.initValues();
501         in.close();
502         return session;
503     }
504 
505     
506     /* ------------------------------------------------------------ */
507     /* ------------------------------------------------------------ */
508     /* ------------------------------------------------------------ */
509     protected class Session extends AbstractSessionManager.Session
510     {
511         /* ------------------------------------------------------------ */
512         private static final long serialVersionUID=-2134521374206116367L;
513         
514    
515 
516         /* ------------------------------------------------------------- */
517         protected Session(HttpServletRequest request)
518         {
519             super(request);
520         }
521         
522         protected Session(long created, String clusterId)
523         {
524             super(created, clusterId);
525         }
526         /* ------------------------------------------------------------- */
527         public void setMaxInactiveInterval(int secs)
528         {
529             super.setMaxInactiveInterval(secs);
530             if (_maxIdleMs>0&&(_maxIdleMs/10)<_scavengePeriodMs)
531                 HashSessionManager.this.setScavengePeriod((secs+9)/10);
532         }
533         
534         /* ------------------------------------------------------------ */
535         protected Map newAttributeMap()
536         {
537             return new HashMap(3);
538         }
539         
540  
541         public void invalidate ()
542         throws IllegalStateException
543         {
544             super.invalidate();
545             remove(getId());
546         }
547         
548         public void remove (String id)
549         {
550             if (id==null)
551                 return;
552             
553             //all sessions are invalidated when jetty is stopped, make sure we don't
554             //remove all the sessions in this case
555             if (isStopping() || isStopped())
556                 return;
557             
558             if (_storeDir==null || !_storeDir.exists())
559             {
560                 return;
561             }
562             
563             File f = new File(_storeDir, id);
564             f.delete();
565         }
566         
567         public void save(FileOutputStream fos)  throws IOException 
568         {
569             DataOutputStream out = new DataOutputStream(fos);
570             out.writeUTF(_clusterId);
571             out.writeUTF(_nodeId);
572             out.writeBoolean(_idChanged);
573             out.writeLong( _created);
574             out.writeLong(_cookieSet);
575             out.writeLong(_accessed);
576             out.writeLong(_lastAccessed);
577             /* Don't write these out, as they don't make sense to store because they
578              * either they cannot be true or their value will be restored in the 
579              * Session constructor.
580              */
581             //out.writeBoolean(_invalid);
582             //out.writeBoolean(_doInvalidate);
583             //out.writeLong(_maxIdleMs);
584             //out.writeBoolean( _newSession);
585             out.writeInt(_requests);
586             if (_values != null)
587             {
588                 out.writeInt(_values.size());
589                 Iterator itor = _values.keySet().iterator();
590                 while (itor.hasNext())
591                 {
592                     String key = (String)itor.next();
593                     out.writeUTF(key);
594                 }
595                 itor = _values.values().iterator();
596                 ObjectOutputStream oos = new ObjectOutputStream(out);
597                 while (itor.hasNext())
598                 {
599                     oos.writeObject(itor.next());
600                 }
601                 oos.close();
602             }
603             else
604                 out.writeInt(0);
605             out.close();
606         }
607         
608     }
609     
610     protected class ClassLoadingObjectInputStream extends ObjectInputStream
611     {
612         public ClassLoadingObjectInputStream(java.io.InputStream in)
613         throws IOException
614         {
615             super(in);
616         }
617         
618         public ClassLoadingObjectInputStream ()
619         throws IOException
620         {
621             super();
622         }
623         
624         public Class resolveClass (java.io.ObjectStreamClass cl)
625         throws IOException, ClassNotFoundException
626         {
627             Class clazz;
628             try
629             {
630                 return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
631             }
632             catch (ClassNotFoundException e)
633             {
634                 return super.resolveClass(cl);
635             }
636         }
637     }
638 
639     
640 }