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