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