comparison src/org/eclipse/jetty/server/session/HashSessionManager.java @ 802:3428c60d7cfc

replace jetty jars with source
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 07 Sep 2016 21:15:48 -0600
parents
children
comparison
equal deleted inserted replaced
801:6a21393191c1 802:3428c60d7cfc
1 //
2 // ========================================================================
3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.server.session;
20
21 import java.io.DataInputStream;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.ObjectInputStream;
27 import java.net.URI;
28 import java.util.ArrayList;
29 import java.util.Iterator;
30 import java.util.Map;
31 import java.util.Set;
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.ServletContext;
38 import javax.servlet.http.HttpServletRequest;
39
40 import org.eclipse.jetty.server.handler.ContextHandler;
41 import org.eclipse.jetty.util.IO;
42 import org.eclipse.jetty.util.log.Logger;
43
44
45 /* ------------------------------------------------------------ */
46 /**
47 * HashSessionManager
48 *
49 * An in-memory implementation of SessionManager.
50 * <p>
51 * This manager supports saving sessions to disk, either periodically or at shutdown.
52 * Sessions can also have their content idle saved to disk to reduce the memory overheads of large idle sessions.
53 * <p>
54 * This manager will create it's own Timer instance to scavenge threads, unless it discovers a shared Timer instance
55 * set as the "org.eclipse.jetty.server.session.timer" attribute of the ContextHandler.
56 *
57 */
58 public class HashSessionManager extends AbstractSessionManager
59 {
60 final static Logger __log = SessionHandler.LOG;
61
62 protected final ConcurrentMap<String,HashedSession> _sessions=new ConcurrentHashMap<String,HashedSession>();
63 private static int __id;
64 private Timer _timer;
65 private boolean _timerStop=false;
66 private TimerTask _task;
67 long _scavengePeriodMs=30000;
68 long _savePeriodMs=0; //don't do period saves by default
69 long _idleSavePeriodMs = 0; // don't idle save sessions by default.
70 private TimerTask _saveTask;
71 File _storeDir;
72 private boolean _lazyLoad=false;
73 private volatile boolean _sessionsLoaded=false;
74 private boolean _deleteUnrestorableSessions=false;
75
76
77
78
79 /* ------------------------------------------------------------ */
80 public HashSessionManager()
81 {
82 super();
83 }
84
85 /* ------------------------------------------------------------ */
86 /**
87 * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStart()
88 */
89 @Override
90 public void doStart() throws Exception
91 {
92 super.doStart();
93
94 _timerStop=false;
95 ServletContext context = ContextHandler.getCurrentContext();
96 if (context!=null)
97 _timer=(Timer)context.getAttribute("org.eclipse.jetty.server.session.timer");
98 if (_timer==null)
99 {
100 _timerStop=true;
101 _timer=new Timer("HashSessionScavenger-"+__id++, true);
102 }
103
104 setScavengePeriod(getScavengePeriod());
105
106 if (_storeDir!=null)
107 {
108 if (!_storeDir.exists())
109 _storeDir.mkdirs();
110
111 if (!_lazyLoad)
112 restoreSessions();
113 }
114
115 setSavePeriod(getSavePeriod());
116 }
117
118 /* ------------------------------------------------------------ */
119 /**
120 * @see org.eclipse.jetty.servlet.AbstractSessionManager#doStop()
121 */
122 @Override
123 public void doStop() throws Exception
124 {
125 // stop the scavengers
126 synchronized(this)
127 {
128 if (_saveTask!=null)
129 _saveTask.cancel();
130 _saveTask=null;
131 if (_task!=null)
132 _task.cancel();
133 _task=null;
134 if (_timer!=null && _timerStop)
135 _timer.cancel();
136 _timer=null;
137 }
138
139 // This will callback invalidate sessions - where we decide if we will save
140 super.doStop();
141
142 _sessions.clear();
143
144 }
145
146 /* ------------------------------------------------------------ */
147 /**
148 * @return the period in seconds at which a check is made for sessions to be invalidated.
149 */
150 public int getScavengePeriod()
151 {
152 return (int)(_scavengePeriodMs/1000);
153 }
154
155
156 /* ------------------------------------------------------------ */
157 @Override
158 public int getSessions()
159 {
160 int sessions=super.getSessions();
161 if (__log.isDebugEnabled())
162 {
163 if (_sessions.size()!=sessions)
164 __log.warn("sessions: "+_sessions.size()+"!="+sessions);
165 }
166 return sessions;
167 }
168
169 /* ------------------------------------------------------------ */
170 /**
171 * @return seconds Idle period after which a session is saved
172 */
173 public int getIdleSavePeriod()
174 {
175 if (_idleSavePeriodMs <= 0)
176 return 0;
177
178 return (int)(_idleSavePeriodMs / 1000);
179 }
180
181 /* ------------------------------------------------------------ */
182 /**
183 * Configures the period in seconds after which a session is deemed idle and saved
184 * to save on session memory.
185 *
186 * The session is persisted, the values attribute map is cleared and the session set to idled.
187 *
188 * @param seconds Idle period after which a session is saved
189 */
190 public void setIdleSavePeriod(int seconds)
191 {
192 _idleSavePeriodMs = seconds * 1000L;
193 }
194
195 /* ------------------------------------------------------------ */
196 @Override
197 public void setMaxInactiveInterval(int seconds)
198 {
199 super.setMaxInactiveInterval(seconds);
200 if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000L)
201 setScavengePeriod((_dftMaxIdleSecs+9)/10);
202 }
203
204 /* ------------------------------------------------------------ */
205 /**
206 * @param seconds the period is seconds at which sessions are periodically saved to disk
207 */
208 public void setSavePeriod (int seconds)
209 {
210 long period = (seconds * 1000L);
211 if (period < 0)
212 period=0;
213 _savePeriodMs=period;
214
215 if (_timer!=null)
216 {
217 synchronized (this)
218 {
219 if (_saveTask!=null)
220 _saveTask.cancel();
221 if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
222 {
223 _saveTask = new TimerTask()
224 {
225 @Override
226 public void run()
227 {
228 try
229 {
230 saveSessions(true);
231 }
232 catch (Exception e)
233 {
234 __log.warn(e);
235 }
236 }
237 };
238 _timer.schedule(_saveTask,_savePeriodMs,_savePeriodMs);
239 }
240 }
241 }
242 }
243
244 /* ------------------------------------------------------------ */
245 /**
246 * @return the period in seconds at which sessions are periodically saved to disk
247 */
248 public int getSavePeriod ()
249 {
250 if (_savePeriodMs<=0)
251 return 0;
252
253 return (int)(_savePeriodMs/1000);
254 }
255
256 /* ------------------------------------------------------------ */
257 /**
258 * @param seconds the period in seconds at which a check is made for sessions to be invalidated.
259 */
260 public void setScavengePeriod(int seconds)
261 {
262 if (seconds==0)
263 seconds=60;
264
265 long old_period=_scavengePeriodMs;
266 long period=seconds*1000L;
267 if (period>60000)
268 period=60000;
269 if (period<1000)
270 period=1000;
271
272 _scavengePeriodMs=period;
273
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 * Find sessions that have timed out and invalidate them. This runs in the
296 * SessionScavenger thread.
297 */
298 protected void scavenge()
299 {
300 //don't attempt to scavenge if we are shutting down
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 // For each session
312 long now=System.currentTimeMillis();
313
314 for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();)
315 {
316 HashedSession session=i.next();
317 long idleTime=session.getMaxInactiveInterval()*1000L;
318 if (idleTime>0&&session.getAccessed()+idleTime<now)
319 {
320 // Found a stale session, add it to the list
321 try
322 {
323 session.timeout();
324 }
325 catch (Exception e)
326 {
327 __log.warn("Problem scavenging sessions", e);
328 }
329 }
330 else if (_idleSavePeriodMs > 0 && session.getAccessed()+_idleSavePeriodMs < now)
331 {
332 try
333 {
334 session.idle();
335 }
336 catch (Exception e)
337 {
338 __log.warn("Problem idling session "+ session.getId(), e);
339 }
340 }
341 }
342 }
343 finally
344 {
345 thread.setContextClassLoader(old_loader);
346 }
347 }
348
349 /* ------------------------------------------------------------ */
350 @Override
351 protected void addSession(AbstractSession session)
352 {
353 if (isRunning())
354 _sessions.put(session.getClusterId(),(HashedSession)session);
355 }
356
357 /* ------------------------------------------------------------ */
358 @Override
359 public AbstractSession getSession(String idInCluster)
360 {
361 if ( _lazyLoad && !_sessionsLoaded)
362 {
363 try
364 {
365 restoreSessions();
366 }
367 catch(Exception e)
368 {
369 __log.warn(e);
370 }
371 }
372
373 Map<String,HashedSession> sessions=_sessions;
374 if (sessions==null)
375 return null;
376
377 HashedSession session = sessions.get(idInCluster);
378
379 if (session == null && _lazyLoad)
380 session=restoreSession(idInCluster);
381 if (session == null)
382 return null;
383
384 if (_idleSavePeriodMs!=0)
385 session.deIdle();
386
387 return session;
388 }
389
390 /* ------------------------------------------------------------ */
391 @Override
392 protected void invalidateSessions() throws Exception
393 {
394 // Invalidate all sessions to cause unbind events
395 ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
396 int loop=100;
397 while (sessions.size()>0 && loop-->0)
398 {
399 // If we are called from doStop
400 if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite())
401 {
402 // Then we only save and remove the session - it is not invalidated.
403 for (HashedSession session : sessions)
404 {
405 session.save(false);
406 removeSession(session,false);
407 }
408 }
409 else
410 {
411 for (HashedSession session : sessions)
412 session.invalidate();
413 }
414
415 // check that no new sessions were created while we were iterating
416 sessions=new ArrayList<HashedSession>(_sessions.values());
417 }
418 }
419
420 /* ------------------------------------------------------------ */
421 @Override
422 protected AbstractSession newSession(HttpServletRequest request)
423 {
424 return new HashedSession(this, request);
425 }
426
427 /* ------------------------------------------------------------ */
428 protected AbstractSession newSession(long created, long accessed, String clusterId)
429 {
430 return new HashedSession(this, created,accessed, clusterId);
431 }
432
433 /* ------------------------------------------------------------ */
434 @Override
435 protected boolean removeSession(String clusterId)
436 {
437 return _sessions.remove(clusterId)!=null;
438 }
439
440 /* ------------------------------------------------------------ */
441 public void setStoreDirectory (File dir) throws IOException
442 {
443 // CanonicalFile is used to capture the base store directory in a way that will
444 // work on Windows. Case differences may through off later checks using this directory.
445 _storeDir=dir.getCanonicalFile();
446 }
447
448 /* ------------------------------------------------------------ */
449 public File getStoreDirectory ()
450 {
451 return _storeDir;
452 }
453
454 /* ------------------------------------------------------------ */
455 public void setLazyLoad(boolean lazyLoad)
456 {
457 _lazyLoad = lazyLoad;
458 }
459
460 /* ------------------------------------------------------------ */
461 public boolean isLazyLoad()
462 {
463 return _lazyLoad;
464 }
465
466 /* ------------------------------------------------------------ */
467 public boolean isDeleteUnrestorableSessions()
468 {
469 return _deleteUnrestorableSessions;
470 }
471
472 /* ------------------------------------------------------------ */
473 public void setDeleteUnrestorableSessions(boolean deleteUnrestorableSessions)
474 {
475 _deleteUnrestorableSessions = deleteUnrestorableSessions;
476 }
477
478 /* ------------------------------------------------------------ */
479 public void restoreSessions () throws Exception
480 {
481 _sessionsLoaded = true;
482
483 if (_storeDir==null || !_storeDir.exists())
484 {
485 return;
486 }
487
488 if (!_storeDir.canRead())
489 {
490 __log.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
491 return;
492 }
493
494 String[] files = _storeDir.list();
495 for (int i=0;files!=null&&i<files.length;i++)
496 {
497 restoreSession(files[i]);
498 }
499 }
500
501 /* ------------------------------------------------------------ */
502 protected synchronized HashedSession restoreSession(String idInCuster)
503 {
504 File file = new File(_storeDir,idInCuster);
505
506 FileInputStream in = null;
507 Exception error = null;
508 try
509 {
510 if (file.exists())
511 {
512 in = new FileInputStream(file);
513 HashedSession session = restoreSession(in, null);
514 addSession(session, false);
515 session.didActivate();
516 return session;
517 }
518 }
519 catch (Exception e)
520 {
521 error = e;
522 }
523 finally
524 {
525 if (in != null) IO.close(in);
526
527 if (error != null)
528 {
529 if (isDeleteUnrestorableSessions() && file.exists() && file.getParentFile().equals(_storeDir) )
530 {
531 file.delete();
532 __log.warn("Deleting file for unrestorable session "+idInCuster, error);
533 }
534 else
535 {
536 __log.warn("Problem restoring session "+idInCuster, error);
537 }
538 }
539 else
540 file.delete(); //delete successfully restored file
541
542 }
543 return null;
544 }
545
546 /* ------------------------------------------------------------ */
547 public void saveSessions(boolean reactivate) throws Exception
548 {
549 if (_storeDir==null || !_storeDir.exists())
550 {
551 return;
552 }
553
554 if (!_storeDir.canWrite())
555 {
556 __log.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
557 return;
558 }
559
560 for (HashedSession session : _sessions.values())
561 session.save(true);
562 }
563
564 /* ------------------------------------------------------------ */
565 public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception
566 {
567 /*
568 * Take care of this class's fields first by calling
569 * defaultReadObject
570 */
571 DataInputStream in = new DataInputStream(is);
572 try
573 {
574 String clusterId = in.readUTF();
575 in.readUTF(); // nodeId
576 long created = in.readLong();
577 long accessed = in.readLong();
578 int requests = in.readInt();
579
580 if (session == null)
581 session = (HashedSession)newSession(created, accessed, clusterId);
582 session.setRequests(requests);
583 int size = in.readInt();
584 if (size>0)
585 {
586 ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(in);
587 try
588 {
589 for (int i=0; i<size;i++)
590 {
591 String key = ois.readUTF();
592 Object value = ois.readObject();
593 session.setAttribute(key,value);
594 }
595 }
596 finally
597 {
598 IO.close(ois);
599 }
600 }
601 return session;
602 }
603 finally
604 {
605 IO.close(in);
606 }
607 }
608
609
610 /* ------------------------------------------------------------ */
611 /* ------------------------------------------------------------ */
612 protected class ClassLoadingObjectInputStream extends ObjectInputStream
613 {
614 /* ------------------------------------------------------------ */
615 public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
616 {
617 super(in);
618 }
619
620 /* ------------------------------------------------------------ */
621 public ClassLoadingObjectInputStream () throws IOException
622 {
623 super();
624 }
625
626 /* ------------------------------------------------------------ */
627 @Override
628 public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
629 {
630 try
631 {
632 return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
633 }
634 catch (ClassNotFoundException e)
635 {
636 return super.resolveClass(cl);
637 }
638 }
639 }
640 }