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