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