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