1
2
3
4
5
6
7
8
9
10
11
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.FileNotFoundException;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.ObjectInputStream;
25 import java.io.ObjectOutputStream;
26 import java.io.OutputStream;
27 import java.util.ArrayList;
28 import java.util.Collections;
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 import java.util.concurrent.ConcurrentMap;
35
36 import javax.servlet.ServletContext;
37 import javax.servlet.http.HttpServletRequest;
38
39 import org.eclipse.jetty.server.handler.ContextHandler;
40 import org.eclipse.jetty.util.IO;
41 import org.eclipse.jetty.util.log.Log;
42
43
44
45
46
47
48
49
50
51
52
53
54 public class HashSessionManager extends AbstractSessionManager
55 {
56 private static int __id;
57 private Timer _timer;
58 private boolean _timerStop=false;
59 private TimerTask _task;
60 private int _scavengePeriodMs=30000;
61 private int _savePeriodMs=0;
62 private int _idleSavePeriodMs = 0;
63 private TimerTask _saveTask;
64 protected ConcurrentMap<String,HashedSession> _sessions;
65 private File _storeDir;
66 private boolean _lazyLoad=false;
67 private volatile boolean _sessionsLoaded=false;
68
69
70 public HashSessionManager()
71 {
72 super();
73 }
74
75
76
77
78
79 @Override
80 public void doStart() throws Exception
81 {
82 _sessions=new ConcurrentHashMap<String,HashedSession>();
83 super.doStart();
84
85 _timerStop=false;
86 ServletContext context = ContextHandler.getCurrentContext();
87 if (context!=null)
88 _timer=(Timer)context.getAttribute("org.eclipse.jetty.server.session.timer");
89 if (_timer==null)
90 {
91 _timerStop=true;
92 _timer=new Timer("HashSessionScavenger-"+__id++, true);
93 }
94
95 setScavengePeriod(getScavengePeriod());
96
97 if (_storeDir!=null)
98 {
99 if (!_storeDir.exists())
100 _storeDir.mkdirs();
101
102 if (!_lazyLoad)
103 restoreSessions();
104 }
105
106 setSavePeriod(getSavePeriod());
107 }
108
109
110
111
112
113 @Override
114 public void doStop() throws Exception
115 {
116 if (_storeDir != null)
117 saveSessions();
118
119 super.doStop();
120
121 _sessions.clear();
122 _sessions=null;
123
124
125 synchronized(this)
126 {
127 if (_saveTask!=null)
128 _saveTask.cancel();
129 _saveTask=null;
130 if (_task!=null)
131 _task.cancel();
132 _task=null;
133 if (_timer!=null && _timerStop)
134 _timer.cancel();
135 _timer=null;
136 }
137 }
138
139
140
141
142
143 public int getScavengePeriod()
144 {
145 return _scavengePeriodMs/1000;
146 }
147
148
149
150 @Override
151 public Map getSessionMap()
152 {
153 return Collections.unmodifiableMap(_sessions);
154 }
155
156
157
158 @Override
159 public int getSessions()
160 {
161 int sessions=super.getSessions();
162 if (Log.isDebugEnabled())
163 {
164 if (_sessions.size()!=sessions)
165 Log.warn("sessions: "+_sessions.size()+"!="+sessions);
166 }
167 return sessions;
168 }
169
170
171
172
173
174 public int getIdleSavePeriod()
175 {
176 if (_idleSavePeriodMs <= 0)
177 return 0;
178
179 return _idleSavePeriodMs / 1000;
180 }
181
182
183
184
185
186
187
188
189
190
191 public void setIdleSavePeriod(int seconds)
192 {
193 _idleSavePeriodMs = seconds * 1000;
194 }
195
196
197 @Override
198 public void setMaxInactiveInterval(int seconds)
199 {
200 super.setMaxInactiveInterval(seconds);
201 if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000)
202 setScavengePeriod((_dftMaxIdleSecs+9)/10);
203 }
204
205
206
207
208
209 public void setSavePeriod (int seconds)
210 {
211 int period = (seconds * 1000);
212 if (period < 0)
213 period=0;
214 _savePeriodMs=period;
215
216 if (_timer!=null)
217 {
218 synchronized (this)
219 {
220 if (_saveTask!=null)
221 _saveTask.cancel();
222 if (_savePeriodMs > 0 && _storeDir!=null)
223 {
224 _saveTask = new TimerTask()
225 {
226 @Override
227 public void run()
228 {
229 try
230 {
231 saveSessions();
232 }
233 catch (Exception e)
234 {
235 Log.warn(e);
236 }
237 }
238 };
239 _timer.schedule(_saveTask,_savePeriodMs,_savePeriodMs);
240 }
241 }
242 }
243 }
244
245
246
247
248
249 public int getSavePeriod ()
250 {
251 if (_savePeriodMs<=0)
252 return 0;
253
254 return _savePeriodMs/1000;
255 }
256
257
258
259
260
261 public void setScavengePeriod(int seconds)
262 {
263 if (seconds==0)
264 seconds=60;
265
266 int old_period=_scavengePeriodMs;
267 int period=seconds*1000;
268 if (period>60000)
269 period=60000;
270 if (period<1000)
271 period=1000;
272
273 _scavengePeriodMs=period;
274 if (_timer!=null && (period!=old_period || _task==null))
275 {
276 synchronized (this)
277 {
278 if (_task!=null)
279 _task.cancel();
280 _task = new TimerTask()
281 {
282 @Override
283 public void run()
284 {
285 scavenge();
286 }
287 };
288 _timer.schedule(_task,_scavengePeriodMs,_scavengePeriodMs);
289 }
290 }
291 }
292
293
294
295
296
297
298 protected void scavenge()
299 {
300
301 if (isStopping() || isStopped())
302 return;
303
304 Thread thread=Thread.currentThread();
305 ClassLoader old_loader=thread.getContextClassLoader();
306 try
307 {
308 if (_loader!=null)
309 thread.setContextClassLoader(_loader);
310
311
312 long now=System.currentTimeMillis();
313 for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();)
314 {
315 HashedSession session=i.next();
316 long idleTime=session._maxIdleMs;
317 if (idleTime>0&&session._accessed+idleTime<now)
318 {
319
320 session.timeout();
321 }
322 else if (_idleSavePeriodMs>0&&session._accessed+_idleSavePeriodMs<now)
323 {
324 session.idle();
325 }
326 }
327 }
328 catch (Throwable t)
329 {
330 if (t instanceof ThreadDeath)
331 throw ((ThreadDeath)t);
332 else
333 Log.warn("Problem scavenging sessions", t);
334 }
335 finally
336 {
337 thread.setContextClassLoader(old_loader);
338 }
339 }
340
341
342 @Override
343 protected void addSession(AbstractSessionManager.Session session)
344 {
345 if (isRunning())
346 _sessions.put(session.getClusterId(),(HashedSession)session);
347 }
348
349
350 @Override
351 public AbstractSessionManager.Session getSession(String idInCluster)
352 {
353 if ( _lazyLoad && !_sessionsLoaded)
354 {
355 try
356 {
357 restoreSessions();
358 }
359 catch(Exception e)
360 {
361 Log.warn(e);
362 }
363 }
364
365 Map<String,HashedSession> sessions=_sessions;
366 if (sessions==null)
367 return null;
368
369 HashedSession session = sessions.get(idInCluster);
370
371 if (session == null && _lazyLoad)
372 session=restoreSession(idInCluster);
373 if (session == null)
374 return null;
375
376 if (_idleSavePeriodMs!=0)
377 session.deIdle();
378
379 return session;
380 }
381
382
383 @Override
384 protected void invalidateSessions()
385 {
386
387 ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
388 for (Iterator<HashedSession> i=sessions.iterator(); i.hasNext();)
389 {
390 HashedSession session=i.next();
391 session.invalidate();
392 }
393 _sessions.clear();
394 }
395
396
397 @Override
398 protected AbstractSessionManager.Session newSession(HttpServletRequest request)
399 {
400 return new HashedSession(request);
401 }
402
403
404 protected AbstractSessionManager.Session newSession(long created, long accessed, String clusterId)
405 {
406 return new HashedSession(created,accessed, clusterId);
407 }
408
409
410 @Override
411 protected boolean removeSession(String clusterId)
412 {
413 return _sessions.remove(clusterId)!=null;
414 }
415
416
417
418 public void setStoreDirectory (File dir)
419 {
420 _storeDir=dir;
421 }
422
423
424 public File getStoreDirectory ()
425 {
426 return _storeDir;
427 }
428
429
430 public void setLazyLoad(boolean lazyLoad)
431 {
432 _lazyLoad = lazyLoad;
433 }
434
435
436 public boolean isLazyLoad()
437 {
438 return _lazyLoad;
439 }
440
441
442 public void restoreSessions () throws Exception
443 {
444 _sessionsLoaded = true;
445
446 if (_storeDir==null || !_storeDir.exists())
447 {
448 return;
449 }
450
451 if (!_storeDir.canRead())
452 {
453 Log.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
454 return;
455 }
456
457 String[] files = _storeDir.list();
458 for (int i=0;files!=null&&i<files.length;i++)
459 {
460 restoreSession(files[i]);
461 }
462 }
463
464
465 protected synchronized HashedSession restoreSession(String idInCuster)
466 {
467 try
468 {
469 File file = new File(_storeDir,idInCuster);
470 if (file.exists())
471 {
472 FileInputStream in = new FileInputStream(file);
473 HashedSession session = restoreSession(in, null);
474 in.close();
475 addSession(session, false);
476 session.didActivate();
477 file.delete();
478 return session;
479 }
480 }
481 catch (Exception e)
482 {
483 Log.warn("Problem restoring session "+idInCuster, e);
484 }
485 return null;
486 }
487
488
489
490
491 public void saveSessions() throws Exception
492 {
493 if (_storeDir==null || !_storeDir.exists())
494 {
495 return;
496 }
497
498 if (!_storeDir.canWrite())
499 {
500 Log.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
501 return;
502 }
503
504 Iterator<Map.Entry<String, HashedSession>> itor = _sessions.entrySet().iterator();
505 while (itor.hasNext())
506 {
507 Map.Entry<String,HashedSession> entry = itor.next();
508 String id = entry.getKey();
509 HashedSession session = entry.getValue();
510 synchronized(session)
511 {
512
513 if (!session.isIdled() && !session.isSaveFailed())
514 {
515 File file = null;
516 FileOutputStream fos = null;
517 try
518 {
519 file = new File (_storeDir, id);
520 if (file.exists())
521 file.delete();
522 file.createNewFile();
523 fos = new FileOutputStream (file);
524 session.willPassivate();
525 session.save(fos);
526 session.didActivate();
527 fos.close();
528 }
529 catch (Exception e)
530 {
531 session.saveFailed();
532
533 Log.warn("Problem persisting session "+id, e);
534
535 if (fos != null)
536 {
537
538 IO.close(fos);
539
540 file.delete();
541 }
542 }
543 }
544 }
545 }
546 }
547
548
549 public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception
550 {
551
552
553
554
555 DataInputStream in = new DataInputStream(is);
556 String clusterId = in.readUTF();
557 String nodeId = in.readUTF();
558 boolean idChanged = in.readBoolean();
559 long created = in.readLong();
560 long cookieSet = in.readLong();
561 long accessed = in.readLong();
562 long lastAccessed = in.readLong();
563
564
565
566
567 int requests = in.readInt();
568
569 if (session == null)
570 session = (HashedSession)newSession(created, System.currentTimeMillis(), clusterId);
571
572 session._cookieSet = cookieSet;
573 session._lastAccessed = lastAccessed;
574
575 int size = in.readInt();
576 if (size>0)
577 {
578 ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(in);
579 for (int i=0; i<size;i++)
580 {
581 String key = ois.readUTF();
582 Object value = ois.readObject();
583 session.setAttribute(key,value);
584 }
585 ois.close();
586 }
587 else
588 in.close();
589 return session;
590 }
591
592
593
594
595
596 protected class HashedSession extends Session
597 {
598
599 private static final long serialVersionUID=-2134521374206116367L;
600
601
602
603 private transient boolean _idled = false;
604
605
606
607
608
609
610 private transient boolean _saveFailed = false;
611
612
613 protected HashedSession(HttpServletRequest request)
614 {
615 super(request);
616 }
617
618
619 protected HashedSession(long created, long accessed, String clusterId)
620 {
621 super(created, accessed, clusterId);
622 }
623
624
625 protected boolean isNotAvailable()
626 {
627 if (_idleSavePeriodMs!=0)
628 deIdle();
629 return _invalid;
630 }
631
632
633 @Override
634 public void setMaxInactiveInterval(int secs)
635 {
636 super.setMaxInactiveInterval(secs);
637 if (_maxIdleMs>0&&(_maxIdleMs/10)<_scavengePeriodMs)
638 HashSessionManager.this.setScavengePeriod((secs+9)/10);
639 }
640
641
642 @Override
643 public void invalidate ()
644 throws IllegalStateException
645 {
646 if (isRunning())
647 {
648 super.invalidate();
649 remove();
650 }
651 }
652
653
654 public void remove()
655 {
656 String id=getId();
657 if (id==null)
658 return;
659
660
661
662 if (isStopping() || isStopped())
663 return;
664
665 if (_storeDir==null || !_storeDir.exists())
666 {
667 return;
668 }
669
670 File f = new File(_storeDir, id);
671 f.delete();
672 }
673
674
675 public synchronized void save(OutputStream os) throws IOException
676 {
677 DataOutputStream out = new DataOutputStream(os);
678 out.writeUTF(_clusterId);
679 out.writeUTF(_nodeId);
680 out.writeBoolean(_idChanged);
681 out.writeLong( _created);
682 out.writeLong(_cookieSet);
683 out.writeLong(_accessed);
684 out.writeLong(_lastAccessed);
685
686
687
688
689
690
691
692
693
694 out.writeInt(_requests);
695 if (_attributes != null)
696 {
697 out.writeInt(_attributes.size());
698 ObjectOutputStream oos = new ObjectOutputStream(out);
699 for (Map.Entry<String,Object> entry: _attributes.entrySet())
700 {
701 oos.writeUTF(entry.getKey());
702 oos.writeObject(entry.getValue());
703 }
704 oos.close();
705 }
706 else
707 {
708 out.writeInt(0);
709 out.close();
710 }
711 }
712
713
714 public synchronized void deIdle()
715 {
716 if (isIdled())
717 {
718
719 access(System.currentTimeMillis());
720
721
722 if (Log.isDebugEnabled())
723 {
724 Log.debug("Deidling " + super.getId());
725 }
726
727 FileInputStream fis = null;
728
729 try
730 {
731 File file = new File(_storeDir, super.getId());
732 if (!file.exists() || !file.canRead())
733 throw new FileNotFoundException(file.getName());
734
735 fis = new FileInputStream(file);
736 _idled = false;
737 restoreSession(fis, this);
738
739 didActivate();
740
741
742 if (_savePeriodMs == 0)
743 file.delete();
744 }
745 catch (Exception e)
746 {
747 Log.warn("Problem deidling session " + super.getId(), e);
748 IO.close(fis);
749 invalidate();
750 }
751 }
752 }
753
754
755
756
757
758
759
760
761 public synchronized void idle()
762 {
763
764 if (!isIdled() && !_saveFailed)
765 {
766 if (Log.isDebugEnabled())
767 Log.debug("Idling " + super.getId());
768
769 File file = null;
770 FileOutputStream fos = null;
771
772 try
773 {
774 file = new File(_storeDir, super.getId());
775
776 if (file.exists())
777 file.delete();
778 file.createNewFile();
779 fos = new FileOutputStream(file);
780 willPassivate();
781 save(fos);
782
783 _attributes.clear();
784
785 _idled = true;
786 }
787 catch (Exception e)
788 {
789 saveFailed();
790
791 Log.warn("Problem idling session " + super.getId(), e);
792
793 if (fos != null)
794 {
795
796 IO.close(fos);
797
798 file.delete();
799 _idled=false;
800 }
801 }
802 }
803 }
804
805
806 public boolean isIdled()
807 {
808 return _idled;
809 }
810
811
812 public boolean isSaveFailed()
813 {
814 return _saveFailed;
815 }
816
817
818 public void saveFailed()
819 {
820 _saveFailed = true;
821 }
822
823 }
824
825
826
827
828 protected class ClassLoadingObjectInputStream extends ObjectInputStream
829 {
830
831 public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
832 {
833 super(in);
834 }
835
836
837 public ClassLoadingObjectInputStream () throws IOException
838 {
839 super();
840 }
841
842
843 @Override
844 public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
845 {
846 try
847 {
848 return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
849 }
850 catch (ClassNotFoundException e)
851 {
852 return super.resolveClass(cl);
853 }
854 }
855 }
856 }