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