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     public void setSavePeriod (int seconds)
148     {
149         int oldSavePeriod = _savePeriodMs;
150         int period = (seconds * 1000);
151         if (period < 0)
152             period=0;
153         _savePeriodMs=period;
154         
155         if (_timer!=null)
156         {
157             synchronized (this)
158             {
159                 if (_saveTask!=null)
160                     _saveTask.cancel();
161                 if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
162                 {
163                     _saveTask = new TimerTask()
164                     {
165                         public void run()
166                         {
167                             try
168                             {
169                                 saveSessions();
170                             }
171                             catch (Exception e)
172                             {
173                                 Log.warn(e);
174                             }
175                         }   
176                     };
177                     _timer.schedule(_saveTask,_savePeriodMs,_savePeriodMs);
178                 }
179             }
180         }
181     }
182     
183     public int getSavePeriod ()
184     {
185         if (_savePeriodMs<=0)
186             return 0;
187         
188         return _savePeriodMs/1000;
189     }
190     /* ------------------------------------------------------------ */
191     /**
192      * @param seconds
193      */
194     public void setScavengePeriod(int seconds)
195     {
196         if (seconds==0)
197             seconds=60;
198 
199         int old_period=_scavengePeriodMs;
200         int period=seconds*1000;
201         if (period>60000)
202             period=60000;
203         if (period<1000)
204             period=1000;
205 
206         _scavengePeriodMs=period;
207         if (_timer!=null && (period!=old_period || _task==null))
208         {
209             synchronized (this)
210             {
211                 if (_task!=null)
212                     _task.cancel();
213                 _task = new TimerTask()
214                 {
215                     public void run()
216                     {
217                         scavenge();
218                     }   
219                 };
220                 _timer.schedule(_task,_scavengePeriodMs,_scavengePeriodMs);
221             }
222         }
223     }
224     
225     /* -------------------------------------------------------------- */
226     /**
227      * Find sessions that have timed out and invalidate them. This runs in the
228      * SessionScavenger thread.
229      */
230     private void scavenge()
231     {
232         //don't attempt to scavenge if we are shutting down
233         if (isStopping() || isStopped())
234             return;
235         
236         Thread thread=Thread.currentThread();
237         ClassLoader old_loader=thread.getContextClassLoader();
238         try
239         {
240             if (_loader!=null)
241                 thread.setContextClassLoader(_loader);
242 
243             long now=System.currentTimeMillis();
244 
245             try
246             {
247                 if (!_sessionsLoaded && _lazyLoad)
248                     restoreSessions();
249             }
250             catch(Exception e)
251             {
252                 Log.debug(e);
253             }
254             
255             // Since Hashtable enumeration is not safe over deletes,
256             // we build a list of stale sessions, then go back and invalidate
257             // them
258             Object stale=null;
259 
260             synchronized (HashSessionManager.this)
261             {
262                 // For each session
263                 for (Iterator i=_sessions.values().iterator(); i.hasNext();)
264                 {
265                     Session session=(Session)i.next();
266                     long idleTime=session._maxIdleMs;
267                     if (idleTime>0&&session._accessed+idleTime<now)
268                     {
269                         // Found a stale session, add it to the list
270                         stale=LazyList.add(stale,session);
271                     }
272                 }
273             }
274 
275             // Remove the stale sessions
276             for (int i=LazyList.size(stale); i-->0;)
277             {
278                 // check it has not been accessed in the meantime
279                 Session session=(Session)LazyList.get(stale,i);
280                 long idleTime=session._maxIdleMs;
281                 if (idleTime>0&&session._accessed+idleTime<System.currentTimeMillis())
282                 {
283                     ((Session)session).timeout();
284                     int nbsess=this._sessions.size();
285                     if (nbsess<this._minSessions)
286                         this._minSessions=nbsess;
287                 }
288             }
289         }
290         catch (Throwable t)
291         {
292             if (t instanceof ThreadDeath)
293                 throw ((ThreadDeath)t);
294             else
295                 Log.warn("Problem scavenging sessions", t);
296         }
297         finally
298         {
299             thread.setContextClassLoader(old_loader);
300         }
301     }
302     
303     /* ------------------------------------------------------------ */
304     protected void addSession(AbstractSessionManager.Session session)
305     {
306         _sessions.put(session.getClusterId(),session);
307     }
308     
309     /* ------------------------------------------------------------ */
310     public AbstractSessionManager.Session getSession(String idInCluster)
311     {
312         try
313         {
314             if (!_sessionsLoaded && _lazyLoad)
315                 restoreSessions();
316         }
317         catch(Exception e)
318         {
319             Log.warn(e);
320         }
321         
322         if (_sessions==null)
323             return null;
324 
325         return (Session)_sessions.get(idInCluster);
326     }
327 
328     /* ------------------------------------------------------------ */
329     protected void invalidateSessions()
330     {
331         // Invalidate all sessions to cause unbind events
332         ArrayList sessions=new ArrayList(_sessions.values());
333         for (Iterator i=sessions.iterator(); i.hasNext();)
334         {
335             Session session=(Session)i.next();
336             session.invalidate();
337         }
338         _sessions.clear();
339         
340     }
341 
342     /* ------------------------------------------------------------ */
343     protected AbstractSessionManager.Session newSession(HttpServletRequest request)
344     {
345         return new Session(request);
346     }
347     
348     /* ------------------------------------------------------------ */
349     protected void removeSession(String clusterId)
350     {
351         _sessions.remove(clusterId);
352     }
353     
354     
355     public void setStoreDirectory (File dir)
356     {
357         _storeDir=dir;
358     }
359     
360     public File getStoreDirectory ()
361     {
362         return _storeDir;
363     }
364 
365     public void setLazyLoad(boolean lazyLoad)
366     {
367         _lazyLoad = lazyLoad;
368     }
369     
370     public boolean isLazyLoad()
371     {
372         return _lazyLoad;
373     }
374     
375     public void restoreSessions () throws Exception
376     {
377         if (_storeDir==null || !_storeDir.exists())
378         {
379             return;
380         }
381 
382         if (!_storeDir.canRead())
383         {
384             Log.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
385             return;
386         }
387 
388         File[] files = _storeDir.listFiles();
389         for (int i=0;files!=null&&i<files.length;i++)
390         {
391             try
392             {
393                 FileInputStream in = new FileInputStream(files[i]);           
394                 Session session = restoreSession(in);
395                 in.close();          
396                 addSession(session, false);
397                 files[i].delete();
398             }
399             catch (Exception e)
400             {
401                 Log.warn("Problem restoring session "+files[i].getName(), e);
402             }
403         }
404         
405         _sessionsLoaded = true;
406     }
407     
408     public void saveSessions () throws Exception
409     {
410         if (_storeDir==null || !_storeDir.exists())
411         {
412             return;
413         }
414         
415         if (!_storeDir.canWrite())
416         {
417             Log.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
418             return;
419         }
420  
421         synchronized (this)
422         {
423             Iterator itor = _sessions.entrySet().iterator();
424             while (itor.hasNext())
425             {
426                 Map.Entry entry = (Map.Entry)itor.next();
427                 String id = (String)entry.getKey();
428                 Session session = (Session)entry.getValue();
429                 try
430                 {
431                     File file = new File (_storeDir, id);
432                     if (file.exists())
433                         file.delete();
434                     file.createNewFile();
435                     FileOutputStream fos = new FileOutputStream (file);
436                     session.save(fos);
437                     fos.close();
438                 }
439                 catch (Exception e)
440                 {
441                     Log.warn("Problem persisting session "+id, e);
442                 }
443             }
444         }
445     }
446         
447     public Session restoreSession (FileInputStream fis) 
448     throws Exception
449     {
450 
451         /*
452          * Take care of this class's fields first by calling 
453          * defaultReadObject
454          */
455         
456         DataInputStream in = new DataInputStream(fis);
457         String clusterId = in.readUTF();
458         String nodeId = in.readUTF();
459         boolean idChanged = in.readBoolean();
460         long created = in.readLong();
461         long cookieSet = in.readLong();
462         long accessed = in.readLong();
463         long lastAccessed = in.readLong();
464         //boolean invalid = in.readBoolean();
465         //boolean invalidate = in.readBoolean();
466         //long maxIdle = in.readLong();
467         //boolean isNew = in.readBoolean();
468         int requests = in.readInt();
469         
470         Session session = new Session (created, clusterId);
471         session._cookieSet = cookieSet;
472         session._lastAccessed = lastAccessed;
473         
474         int size = in.readInt();
475         if (size > 0)
476         {
477             ArrayList keys = new ArrayList();
478             for (int i=0; i<size; i++)
479             {
480                 String key = in.readUTF();
481                 keys.add(key);
482             }
483             ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(in);
484             for (int i=0;i<size;i++)
485             {
486                 Object value = ois.readObject();
487                 session.setAttribute((String)keys.get(i),value);
488             }
489             ois.close();
490         }
491         else
492             session.initValues();
493         in.close();
494         return session;
495     }
496 
497     
498     /* ------------------------------------------------------------ */
499     /* ------------------------------------------------------------ */
500     /* ------------------------------------------------------------ */
501     protected class Session extends AbstractSessionManager.Session
502     {
503         /* ------------------------------------------------------------ */
504         private static final long serialVersionUID=-2134521374206116367L;
505         
506    
507 
508         /* ------------------------------------------------------------- */
509         protected Session(HttpServletRequest request)
510         {
511             super(request);
512         }
513         
514         protected Session(long created, String clusterId)
515         {
516             super(created, clusterId);
517         }
518         /* ------------------------------------------------------------- */
519         public void setMaxInactiveInterval(int secs)
520         {
521             super.setMaxInactiveInterval(secs);
522             if (_maxIdleMs>0&&(_maxIdleMs/10)<_scavengePeriodMs)
523                 HashSessionManager.this.setScavengePeriod((secs+9)/10);
524         }
525         
526         /* ------------------------------------------------------------ */
527         protected Map newAttributeMap()
528         {
529             return new HashMap(3);
530         }
531         
532  
533         public void invalidate ()
534         throws IllegalStateException
535         {
536             super.invalidate();
537             remove(getId());
538         }
539         
540         public void remove (String id)
541         {
542             if (id==null)
543                 return;
544             
545             //all sessions are invalidated when jetty is stopped, make sure we don't
546             //remove all the sessions in this case
547             if (isStopping() || isStopped())
548                 return;
549             
550             if (_storeDir==null || !_storeDir.exists())
551             {
552                 return;
553             }
554             
555             File f = new File(_storeDir, id);
556             f.delete();
557         }
558         
559         public void save(FileOutputStream fos)  throws IOException 
560         {
561             DataOutputStream out = new DataOutputStream(fos);
562             out.writeUTF(_clusterId);
563             out.writeUTF(_nodeId);
564             out.writeBoolean(_idChanged);
565             out.writeLong( _created);
566             out.writeLong(_cookieSet);
567             out.writeLong(_accessed);
568             out.writeLong(_lastAccessed);
569             /* Don't write these out, as they don't make sense to store because they
570              * either they cannot be true or their value will be restored in the 
571              * Session constructor.
572              */
573             //out.writeBoolean(_invalid);
574             //out.writeBoolean(_doInvalidate);
575             //out.writeLong(_maxIdleMs);
576             //out.writeBoolean( _newSession);
577             out.writeInt(_requests);
578             if (_values != null)
579             {
580                 out.writeInt(_values.size());
581                 Iterator itor = _values.keySet().iterator();
582                 while (itor.hasNext())
583                 {
584                     String key = (String)itor.next();
585                     out.writeUTF(key);
586                 }
587                 itor = _values.values().iterator();
588                 ObjectOutputStream oos = new ObjectOutputStream(out);
589                 while (itor.hasNext())
590                 {
591                     oos.writeObject(itor.next());
592                 }
593                 oos.close();
594             }
595             else
596                 out.writeInt(0);
597             out.close();
598         }
599         
600     }
601     
602     protected class ClassLoadingObjectInputStream extends ObjectInputStream
603     {
604         public ClassLoadingObjectInputStream(java.io.InputStream in)
605         throws IOException
606         {
607             super(in);
608         }
609         
610         public ClassLoadingObjectInputStream ()
611         throws IOException
612         {
613             super();
614         }
615         
616         public Class resolveClass (java.io.ObjectStreamClass cl)
617         throws IOException, ClassNotFoundException
618         {
619             Class clazz;
620             try
621             {
622                 return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
623             }
624             catch (ClassNotFoundException e)
625             {
626                 return super.resolveClass(cl);
627             }
628         }
629     }
630 
631     
632 }