1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.eclipse.jetty.server.session;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.ObjectInputStream;
27 import java.io.ObjectOutputStream;
28 import java.sql.Connection;
29 import java.sql.PreparedStatement;
30 import java.sql.ResultSet;
31 import java.sql.SQLException;
32 import java.sql.Statement;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.ListIterator;
36 import java.util.Map;
37 import java.util.concurrent.ConcurrentHashMap;
38 import java.util.concurrent.atomic.AtomicReference;
39
40 import javax.servlet.http.HttpServletRequest;
41 import javax.servlet.http.HttpSessionEvent;
42 import javax.servlet.http.HttpSessionListener;
43
44 import org.eclipse.jetty.server.SessionIdManager;
45 import org.eclipse.jetty.server.handler.ContextHandler;
46 import org.eclipse.jetty.util.log.Log;
47 import org.eclipse.jetty.util.log.Logger;
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 public class JDBCSessionManager extends AbstractSessionManager
74 {
75 private static final Logger LOG = Log.getLogger(JDBCSessionManager.class);
76
77 private ConcurrentHashMap<String, AbstractSession> _sessions;
78 protected JDBCSessionIdManager _jdbcSessionIdMgr = null;
79 protected long _saveIntervalSec = 60;
80
81
82
83
84
85
86
87
88
89 public class Session extends AbstractSession
90 {
91 private static final long serialVersionUID = 5208464051134226143L;
92
93
94
95
96 private boolean _dirty=false;
97
98
99
100
101
102 private long _cookieSet;
103
104
105
106
107
108 private long _expiryTime;
109
110
111
112
113
114 private long _lastSaved;
115
116
117
118
119
120 private String _lastNode;
121
122
123
124
125
126 private String _virtualHost;
127
128
129
130
131
132 private String _rowId;
133
134
135
136
137
138 private String _canonicalContext;
139
140
141
142
143
144
145
146 protected Session (HttpServletRequest request)
147 {
148 super(JDBCSessionManager.this,request);
149 int maxInterval=getMaxInactiveInterval();
150 _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
151 _virtualHost = JDBCSessionManager.getVirtualHost(_context);
152 _canonicalContext = canonicalize(_context.getContextPath());
153 _lastNode = getSessionIdManager().getWorkerName();
154 }
155
156
157
158
159
160
161
162
163
164 protected Session (String sessionId, String rowId, long created, long accessed)
165 {
166 super(JDBCSessionManager.this, created, accessed, sessionId);
167 _rowId = rowId;
168 }
169
170
171 protected synchronized String getRowId()
172 {
173 return _rowId;
174 }
175
176 protected synchronized void setRowId(String rowId)
177 {
178 _rowId = rowId;
179 }
180
181 public synchronized void setVirtualHost (String vhost)
182 {
183 _virtualHost=vhost;
184 }
185
186 public synchronized String getVirtualHost ()
187 {
188 return _virtualHost;
189 }
190
191 public synchronized long getLastSaved ()
192 {
193 return _lastSaved;
194 }
195
196 public synchronized void setLastSaved (long time)
197 {
198 _lastSaved=time;
199 }
200
201 public synchronized void setExpiryTime (long time)
202 {
203 _expiryTime=time;
204 }
205
206 public synchronized long getExpiryTime ()
207 {
208 return _expiryTime;
209 }
210
211
212 public synchronized void setCanonicalContext(String str)
213 {
214 _canonicalContext=str;
215 }
216
217 public synchronized String getCanonicalContext ()
218 {
219 return _canonicalContext;
220 }
221
222 public void setCookieSet (long ms)
223 {
224 _cookieSet = ms;
225 }
226
227 public synchronized long getCookieSet ()
228 {
229 return _cookieSet;
230 }
231
232 public synchronized void setLastNode (String node)
233 {
234 _lastNode=node;
235 }
236
237 public synchronized String getLastNode ()
238 {
239 return _lastNode;
240 }
241
242 @Override
243 public void setAttribute (String name, Object value)
244 {
245 super.setAttribute(name, value);
246 _dirty=true;
247 }
248
249 @Override
250 public void removeAttribute (String name)
251 {
252 super.removeAttribute(name);
253 _dirty=true;
254 }
255
256 @Override
257 protected void cookieSet()
258 {
259 _cookieSet = getAccessed();
260 }
261
262
263
264
265
266
267
268 @Override
269 protected boolean access(long time)
270 {
271 synchronized (this)
272 {
273 if (super.access(time))
274 {
275 int maxInterval=getMaxInactiveInterval();
276 _expiryTime = (maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
277 return true;
278 }
279 return false;
280 }
281 }
282
283
284
285
286
287
288
289 @Override
290 protected void complete()
291 {
292 synchronized (this)
293 {
294 super.complete();
295 try
296 {
297 if (isValid())
298 {
299 if (_dirty)
300 {
301
302
303 willPassivate();
304 updateSession(this);
305 didActivate();
306 }
307 else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L))
308 {
309 updateSessionAccessTime(this);
310 }
311 }
312 }
313 catch (Exception e)
314 {
315 LOG.warn("Problem persisting changed session data id="+getId(), e);
316 }
317 finally
318 {
319 _dirty=false;
320 }
321 }
322 }
323
324 @Override
325 protected void timeout() throws IllegalStateException
326 {
327 if (LOG.isDebugEnabled())
328 LOG.debug("Timing out session id="+getClusterId());
329 super.timeout();
330 }
331
332 @Override
333 public String toString ()
334 {
335 return "Session rowId="+_rowId+",id="+getId()+",lastNode="+_lastNode+
336 ",created="+getCreationTime()+",accessed="+getAccessed()+
337 ",lastAccessed="+getLastAccessedTime()+",cookieSet="+_cookieSet+
338 ",lastSaved="+_lastSaved+",expiry="+_expiryTime;
339 }
340 }
341
342
343
344
345
346
347
348
349
350 protected class ClassLoadingObjectInputStream extends ObjectInputStream
351 {
352 public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
353 {
354 super(in);
355 }
356
357 public ClassLoadingObjectInputStream () throws IOException
358 {
359 super();
360 }
361
362 @Override
363 public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
364 {
365 try
366 {
367 return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
368 }
369 catch (ClassNotFoundException e)
370 {
371 return super.resolveClass(cl);
372 }
373 }
374 }
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395 public void setSaveInterval (long sec)
396 {
397 _saveIntervalSec=sec;
398 }
399
400 public long getSaveInterval ()
401 {
402 return _saveIntervalSec;
403 }
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418 public void cacheInvalidate (Session session)
419 {
420
421 }
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440 @Override
441 public Session getSession(String idInCluster)
442 {
443 Session session = null;
444 Session memSession = (Session)_sessions.get(idInCluster);
445
446 synchronized (this)
447 {
448
449
450
451
452
453
454
455
456
457 long now = System.currentTimeMillis();
458 if (LOG.isDebugEnabled())
459 {
460 if (memSession==null)
461 LOG.debug("getSession("+idInCluster+"): not in session map,"+
462 " now="+now+
463 " lastSaved="+(memSession==null?0:memSession._lastSaved)+
464 " interval="+(_saveIntervalSec * 1000L));
465 else
466 LOG.debug("getSession("+idInCluster+"): in session map, "+
467 " now="+now+
468 " lastSaved="+(memSession==null?0:memSession._lastSaved)+
469 " interval="+(_saveIntervalSec * 1000L)+
470 " lastNode="+memSession._lastNode+
471 " thisNode="+getSessionIdManager().getWorkerName()+
472 " difference="+(now - memSession._lastSaved));
473 }
474
475 try
476 {
477 if (memSession==null)
478 {
479 LOG.debug("getSession("+idInCluster+"): no session in session map. Reloading session data from db.");
480 session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
481 }
482 else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L))
483 {
484 LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db.");
485 session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
486 }
487 else
488 {
489 LOG.debug("getSession("+idInCluster+"): session in session map");
490 session = memSession;
491 }
492 }
493 catch (Exception e)
494 {
495 LOG.warn("Unable to load session "+idInCluster, e);
496 return null;
497 }
498
499
500
501 if (session != null)
502 {
503
504 if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null)
505 {
506
507 if (session._expiryTime <= 0 || session._expiryTime > now)
508 {
509 if (LOG.isDebugEnabled())
510 LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName());
511
512 session.setLastNode(getSessionIdManager().getWorkerName());
513 _sessions.put(idInCluster, session);
514
515
516 try
517 {
518 updateSessionNode(session);
519 session.didActivate();
520 }
521 catch (Exception e)
522 {
523 LOG.warn("Unable to update freshly loaded session "+idInCluster, e);
524 return null;
525 }
526 }
527 else
528 {
529 LOG.debug("getSession ({}): Session has expired", idInCluster);
530 session=null;
531 }
532
533 }
534 else
535 LOG.debug("getSession({}): Session not stale {}", idInCluster,session);
536 }
537 else
538 {
539
540 LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster);
541 }
542
543 return session;
544 }
545 }
546
547
548
549
550
551
552 @Override
553 public int getSessions()
554 {
555 int size = 0;
556 synchronized (this)
557 {
558 size = _sessions.size();
559 }
560 return size;
561 }
562
563
564
565
566
567
568
569 @Override
570 public void doStart() throws Exception
571 {
572 if (_sessionIdManager==null)
573 throw new IllegalStateException("No session id manager defined");
574
575 _jdbcSessionIdMgr = (JDBCSessionIdManager)_sessionIdManager;
576
577 _sessions = new ConcurrentHashMap<String, AbstractSession>();
578
579 super.doStart();
580 }
581
582
583
584
585
586
587
588 @Override
589 public void doStop() throws Exception
590 {
591 _sessions.clear();
592 _sessions = null;
593
594 super.doStop();
595 }
596
597 @Override
598 protected void invalidateSessions()
599 {
600
601
602
603
604
605
606 }
607
608
609
610
611
612
613
614 protected void invalidateSession (String idInCluster)
615 {
616 Session session = null;
617 synchronized (this)
618 {
619 session = (Session)_sessions.get(idInCluster);
620 }
621
622 if (session != null)
623 {
624 session.invalidate();
625 }
626 }
627
628
629
630
631
632
633
634 @Override
635 protected boolean removeSession(String idInCluster)
636 {
637 synchronized (this)
638 {
639 Session session = (Session)_sessions.remove(idInCluster);
640 try
641 {
642 if (session != null)
643 deleteSession(session);
644 }
645 catch (Exception e)
646 {
647 LOG.warn("Problem deleting session id="+idInCluster, e);
648 }
649 return session!=null;
650 }
651 }
652
653
654
655
656
657
658
659 @Override
660 protected void addSession(AbstractSession session)
661 {
662 if (session==null)
663 return;
664
665 synchronized (this)
666 {
667 _sessions.put(session.getClusterId(), session);
668 }
669
670
671
672 try
673 {
674 synchronized (session)
675 {
676 session.willPassivate();
677 storeSession(((JDBCSessionManager.Session)session));
678 session.didActivate();
679 }
680 }
681 catch (Exception e)
682 {
683 LOG.warn("Unable to store new session id="+session.getId() , e);
684 }
685 }
686
687
688
689
690
691
692
693 @Override
694 protected AbstractSession newSession(HttpServletRequest request)
695 {
696 return new Session(request);
697 }
698
699
700
701
702
703
704
705 @Override
706 public void removeSession(AbstractSession session, boolean invalidate)
707 {
708
709 boolean removed = false;
710
711 synchronized (this)
712 {
713
714 if (getSession(session.getClusterId()) != null)
715 {
716 removed = true;
717 removeSession(session.getClusterId());
718 }
719 }
720
721 if (removed)
722 {
723
724 _sessionIdManager.removeSession(session);
725
726 if (invalidate)
727 _sessionIdManager.invalidateAll(session.getClusterId());
728
729 if (invalidate && !_sessionListeners.isEmpty())
730 {
731 HttpSessionEvent event=new HttpSessionEvent(session);
732 for (HttpSessionListener l : _sessionListeners)
733 l.sessionDestroyed(event);
734 }
735 if (!invalidate)
736 {
737 session.willPassivate();
738 }
739 }
740 }
741
742
743
744
745
746
747
748
749 protected void expire (List<?> sessionIds)
750 {
751
752 if (isStopping() || isStopped())
753 return;
754
755
756 Thread thread=Thread.currentThread();
757 ClassLoader old_loader=thread.getContextClassLoader();
758 ListIterator<?> itor = sessionIds.listIterator();
759
760 try
761 {
762 while (itor.hasNext())
763 {
764 String sessionId = (String)itor.next();
765 if (LOG.isDebugEnabled())
766 LOG.debug("Expiring session id "+sessionId);
767
768 Session session = (Session)_sessions.get(sessionId);
769 if (session != null)
770 {
771 session.timeout();
772 itor.remove();
773 }
774 else
775 {
776 if (LOG.isDebugEnabled())
777 LOG.debug("Unrecognized session id="+sessionId);
778 }
779 }
780 }
781 catch (Throwable t)
782 {
783 LOG.warn("Problem expiring sessions", t);
784 }
785 finally
786 {
787 thread.setContextClassLoader(old_loader);
788 }
789 }
790
791
792
793
794
795
796
797
798 protected Session loadSession (final String id, final String canonicalContextPath, final String vhost)
799 throws Exception
800 {
801 final AtomicReference<Session> _reference = new AtomicReference<Session>();
802 final AtomicReference<Exception> _exception = new AtomicReference<Exception>();
803 Runnable load = new Runnable()
804 {
805 @SuppressWarnings("unchecked")
806 public void run()
807 {
808 Session session = null;
809 Connection connection=null;
810 PreparedStatement statement = null;
811 try
812 {
813 connection = getConnection();
814 statement = _jdbcSessionIdMgr._dbAdaptor.getLoadStatement(connection, id, canonicalContextPath, vhost);
815 ResultSet result = statement.executeQuery();
816 if (result.next())
817 {
818 session = new Session(id, result.getString(_jdbcSessionIdMgr._sessionTableRowId), result.getLong("createTime"), result.getLong("accessTime"));
819 session.setCookieSet(result.getLong("cookieTime"));
820 session.setLastAccessedTime(result.getLong("lastAccessTime"));
821 session.setLastNode(result.getString("lastNode"));
822 session.setLastSaved(result.getLong("lastSavedTime"));
823 session.setExpiryTime(result.getLong("expiryTime"));
824 session.setCanonicalContext(result.getString("contextPath"));
825 session.setVirtualHost(result.getString("virtualHost"));
826
827 InputStream is = ((JDBCSessionIdManager)getSessionIdManager())._dbAdaptor.getBlobInputStream(result, "map");
828 ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream (is);
829 Object o = ois.readObject();
830 session.addAttributes((Map<String,Object>)o);
831 ois.close();
832
833 if (LOG.isDebugEnabled())
834 LOG.debug("LOADED session "+session);
835 }
836 _reference.set(session);
837 }
838 catch (Exception e)
839 {
840 _exception.set(e);
841 }
842 finally
843 {
844 if (connection!=null)
845 {
846 try { connection.close();}
847 catch(Exception e) { LOG.warn(e); }
848 }
849 }
850 }
851 };
852
853 if (_context==null)
854 load.run();
855 else
856 _context.getContextHandler().handle(load);
857
858 if (_exception.get()!=null)
859 {
860
861
862 _jdbcSessionIdMgr.removeSession(id);
863 throw _exception.get();
864 }
865
866 return _reference.get();
867 }
868
869
870
871
872
873
874
875 protected void storeSession (Session session)
876 throws Exception
877 {
878 if (session==null)
879 return;
880
881
882 Connection connection = getConnection();
883 PreparedStatement statement = null;
884 try
885 {
886 String rowId = calculateRowId(session);
887
888 long now = System.currentTimeMillis();
889 connection.setAutoCommit(true);
890 statement = connection.prepareStatement(_jdbcSessionIdMgr._insertSession);
891 statement.setString(1, rowId);
892 statement.setString(2, session.getId());
893 statement.setString(3, session.getCanonicalContext());
894 statement.setString(4, session.getVirtualHost());
895 statement.setString(5, getSessionIdManager().getWorkerName());
896 statement.setLong(6, session.getAccessed());
897 statement.setLong(7, session.getLastAccessedTime());
898 statement.setLong(8, session.getCreationTime());
899 statement.setLong(9, session.getCookieSet());
900 statement.setLong(10, now);
901 statement.setLong(11, session.getExpiryTime());
902
903 ByteArrayOutputStream baos = new ByteArrayOutputStream();
904 ObjectOutputStream oos = new ObjectOutputStream(baos);
905 oos.writeObject(session.getAttributeMap());
906 byte[] bytes = baos.toByteArray();
907
908 ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
909 statement.setBinaryStream(12, bais, bytes.length);
910
911 statement.executeUpdate();
912 session.setRowId(rowId);
913 session.setLastSaved(now);
914
915
916 if (LOG.isDebugEnabled())
917 LOG.debug("Stored session "+session);
918 }
919 finally
920 {
921 if (connection!=null)
922 connection.close();
923 }
924 }
925
926
927
928
929
930
931
932
933 protected void updateSession (Session data)
934 throws Exception
935 {
936 if (data==null)
937 return;
938
939 Connection connection = getConnection();
940 PreparedStatement statement = null;
941 try
942 {
943 long now = System.currentTimeMillis();
944 connection.setAutoCommit(true);
945 statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSession);
946 statement.setString(1, getSessionIdManager().getWorkerName());
947 statement.setLong(2, data.getAccessed());
948 statement.setLong(3, data.getLastAccessedTime());
949 statement.setLong(4, now);
950 statement.setLong(5, data.getExpiryTime());
951
952 ByteArrayOutputStream baos = new ByteArrayOutputStream();
953 ObjectOutputStream oos = new ObjectOutputStream(baos);
954 oos.writeObject(data.getAttributeMap());
955 byte[] bytes = baos.toByteArray();
956 ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
957
958 statement.setBinaryStream(6, bais, bytes.length);
959 statement.setString(7, data.getRowId());
960 statement.executeUpdate();
961
962 data.setLastSaved(now);
963 if (LOG.isDebugEnabled())
964 LOG.debug("Updated session "+data);
965 }
966 finally
967 {
968 if (connection!=null)
969 connection.close();
970 }
971 }
972
973
974
975
976
977
978
979
980 protected void updateSessionNode (Session data)
981 throws Exception
982 {
983 String nodeId = getSessionIdManager().getWorkerName();
984 Connection connection = getConnection();
985 PreparedStatement statement = null;
986 try
987 {
988 connection.setAutoCommit(true);
989 statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionNode);
990 statement.setString(1, nodeId);
991 statement.setString(2, data.getRowId());
992 statement.executeUpdate();
993 statement.close();
994 if (LOG.isDebugEnabled())
995 LOG.debug("Updated last node for session id="+data.getId()+", lastNode = "+nodeId);
996 }
997 finally
998 {
999 if (connection!=null)
1000 connection.close();
1001 }
1002 }
1003
1004
1005
1006
1007
1008
1009
1010 private void updateSessionAccessTime (Session data)
1011 throws Exception
1012 {
1013 Connection connection = getConnection();
1014 PreparedStatement statement = null;
1015 try
1016 {
1017 long now = System.currentTimeMillis();
1018 connection.setAutoCommit(true);
1019 statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionAccessTime);
1020 statement.setString(1, getSessionIdManager().getWorkerName());
1021 statement.setLong(2, data.getAccessed());
1022 statement.setLong(3, data.getLastAccessedTime());
1023 statement.setLong(4, now);
1024 statement.setLong(5, data.getExpiryTime());
1025 statement.setString(6, data.getRowId());
1026 statement.executeUpdate();
1027 data.setLastSaved(now);
1028 statement.close();
1029 if (LOG.isDebugEnabled())
1030 LOG.debug("Updated access time session id="+data.getId());
1031 }
1032 finally
1033 {
1034 if (connection!=null)
1035 connection.close();
1036 }
1037 }
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049 protected void deleteSession (Session data)
1050 throws Exception
1051 {
1052 Connection connection = getConnection();
1053 PreparedStatement statement = null;
1054 try
1055 {
1056 connection.setAutoCommit(true);
1057 statement = connection.prepareStatement(_jdbcSessionIdMgr._deleteSession);
1058 statement.setString(1, data.getRowId());
1059 statement.executeUpdate();
1060 if (LOG.isDebugEnabled())
1061 LOG.debug("Deleted Session "+data);
1062 }
1063 finally
1064 {
1065 if (connection!=null)
1066 connection.close();
1067 }
1068 }
1069
1070
1071
1072
1073
1074
1075
1076
1077 private Connection getConnection ()
1078 throws SQLException
1079 {
1080 return ((JDBCSessionIdManager)getSessionIdManager()).getConnection();
1081 }
1082
1083
1084
1085
1086
1087
1088
1089
1090 private String calculateRowId (Session data)
1091 {
1092 String rowId = canonicalize(_context.getContextPath());
1093 rowId = rowId + "_" + getVirtualHost(_context);
1094 rowId = rowId+"_"+data.getId();
1095 return rowId;
1096 }
1097
1098
1099
1100
1101
1102
1103
1104
1105 private static String getVirtualHost (ContextHandler.Context context)
1106 {
1107 String vhost = "0.0.0.0";
1108
1109 if (context==null)
1110 return vhost;
1111
1112 String [] vhosts = context.getContextHandler().getVirtualHosts();
1113 if (vhosts==null || vhosts.length==0 || vhosts[0]==null)
1114 return vhost;
1115
1116 return vhosts[0];
1117 }
1118
1119
1120
1121
1122
1123
1124
1125 private static String canonicalize (String path)
1126 {
1127 if (path==null)
1128 return "";
1129
1130 return path.replace('/', '_').replace('.','_').replace('\\','_');
1131 }
1132 }