1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.server.session;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.InputStream;
23 import java.sql.Blob;
24 import java.sql.Connection;
25 import java.sql.DatabaseMetaData;
26 import java.sql.Driver;
27 import java.sql.DriverManager;
28 import java.sql.PreparedStatement;
29 import java.sql.ResultSet;
30 import java.sql.SQLException;
31 import java.sql.Statement;
32 import java.util.HashSet;
33 import java.util.Locale;
34 import java.util.Random;
35 import java.util.Set;
36 import java.util.Timer;
37 import java.util.TimerTask;
38 import java.util.concurrent.TimeUnit;
39
40 import javax.naming.InitialContext;
41 import javax.servlet.http.HttpServletRequest;
42 import javax.servlet.http.HttpSession;
43 import javax.sql.DataSource;
44
45 import org.eclipse.jetty.server.Handler;
46 import org.eclipse.jetty.server.Server;
47 import org.eclipse.jetty.server.SessionManager;
48 import org.eclipse.jetty.server.handler.ContextHandler;
49 import org.eclipse.jetty.util.log.Logger;
50 import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
51 import org.eclipse.jetty.util.thread.Scheduler;
52
53
54
55
56
57
58
59
60
61
62 public class JDBCSessionIdManager extends AbstractSessionIdManager
63 {
64 final static Logger LOG = SessionHandler.LOG;
65 public final static int MAX_INTERVAL_NOT_SET = -999;
66
67 protected final HashSet<String> _sessionIds = new HashSet<String>();
68 protected Server _server;
69 protected Driver _driver;
70 protected String _driverClassName;
71 protected String _connectionUrl;
72 protected DataSource _datasource;
73 protected String _jndiName;
74
75 protected int _deleteBlockSize = 10;
76
77 protected Scheduler.Task _task;
78 protected Scheduler _scheduler;
79 protected Scavenger _scavenger;
80 protected boolean _ownScheduler;
81 protected long _lastScavengeTime;
82 protected long _scavengeIntervalMs = 1000L * 60 * 10;
83
84
85 protected String _createSessionIdTable;
86 protected String _createSessionTable;
87
88 protected String _selectBoundedExpiredSessions;
89 private String _selectExpiredSessions;
90
91 protected String _insertId;
92 protected String _deleteId;
93 protected String _queryId;
94
95 protected String _insertSession;
96 protected String _deleteSession;
97 protected String _updateSession;
98 protected String _updateSessionNode;
99 protected String _updateSessionAccessTime;
100
101 protected DatabaseAdaptor _dbAdaptor = new DatabaseAdaptor();
102 protected SessionIdTableSchema _sessionIdTableSchema = new SessionIdTableSchema();
103 protected SessionTableSchema _sessionTableSchema = new SessionTableSchema();
104
105
106
107
108
109
110
111
112 public static class SessionTableSchema
113 {
114 protected DatabaseAdaptor _dbAdaptor;
115 protected String _tableName = "JettySessions";
116 protected String _rowIdColumn = "rowId";
117 protected String _idColumn = "sessionId";
118 protected String _contextPathColumn = "contextPath";
119 protected String _virtualHostColumn = "virtualHost";
120 protected String _lastNodeColumn = "lastNode";
121 protected String _accessTimeColumn = "accessTime";
122 protected String _lastAccessTimeColumn = "lastAccessTime";
123 protected String _createTimeColumn = "createTime";
124 protected String _cookieTimeColumn = "cookieTime";
125 protected String _lastSavedTimeColumn = "lastSavedTime";
126 protected String _expiryTimeColumn = "expiryTime";
127 protected String _maxIntervalColumn = "maxInterval";
128 protected String _mapColumn = "map";
129
130
131 protected void setDatabaseAdaptor(DatabaseAdaptor dbadaptor)
132 {
133 _dbAdaptor = dbadaptor;
134 }
135
136
137 public String getTableName()
138 {
139 return _tableName;
140 }
141 public void setTableName(String tableName)
142 {
143 checkNotNull(tableName);
144 _tableName = tableName;
145 }
146 public String getRowIdColumn()
147 {
148 if ("rowId".equals(_rowIdColumn) && _dbAdaptor.isRowIdReserved())
149 _rowIdColumn = "srowId";
150 return _rowIdColumn;
151 }
152 public void setRowIdColumn(String rowIdColumn)
153 {
154 checkNotNull(rowIdColumn);
155 if (_dbAdaptor == null)
156 throw new IllegalStateException ("DbAdaptor is null");
157
158 if (_dbAdaptor.isRowIdReserved() && "rowId".equals(rowIdColumn))
159 throw new IllegalArgumentException("rowId is reserved word for Oracle");
160
161 _rowIdColumn = rowIdColumn;
162 }
163 public String getIdColumn()
164 {
165 return _idColumn;
166 }
167 public void setIdColumn(String idColumn)
168 {
169 checkNotNull(idColumn);
170 _idColumn = idColumn;
171 }
172 public String getContextPathColumn()
173 {
174 return _contextPathColumn;
175 }
176 public void setContextPathColumn(String contextPathColumn)
177 {
178 checkNotNull(contextPathColumn);
179 _contextPathColumn = contextPathColumn;
180 }
181 public String getVirtualHostColumn()
182 {
183 return _virtualHostColumn;
184 }
185 public void setVirtualHostColumn(String virtualHostColumn)
186 {
187 checkNotNull(virtualHostColumn);
188 _virtualHostColumn = virtualHostColumn;
189 }
190 public String getLastNodeColumn()
191 {
192 return _lastNodeColumn;
193 }
194 public void setLastNodeColumn(String lastNodeColumn)
195 {
196 checkNotNull(lastNodeColumn);
197 _lastNodeColumn = lastNodeColumn;
198 }
199 public String getAccessTimeColumn()
200 {
201 return _accessTimeColumn;
202 }
203 public void setAccessTimeColumn(String accessTimeColumn)
204 {
205 checkNotNull(accessTimeColumn);
206 _accessTimeColumn = accessTimeColumn;
207 }
208 public String getLastAccessTimeColumn()
209 {
210 return _lastAccessTimeColumn;
211 }
212 public void setLastAccessTimeColumn(String lastAccessTimeColumn)
213 {
214 checkNotNull(lastAccessTimeColumn);
215 _lastAccessTimeColumn = lastAccessTimeColumn;
216 }
217 public String getCreateTimeColumn()
218 {
219 return _createTimeColumn;
220 }
221 public void setCreateTimeColumn(String createTimeColumn)
222 {
223 checkNotNull(createTimeColumn);
224 _createTimeColumn = createTimeColumn;
225 }
226 public String getCookieTimeColumn()
227 {
228 return _cookieTimeColumn;
229 }
230 public void setCookieTimeColumn(String cookieTimeColumn)
231 {
232 checkNotNull(cookieTimeColumn);
233 _cookieTimeColumn = cookieTimeColumn;
234 }
235 public String getLastSavedTimeColumn()
236 {
237 return _lastSavedTimeColumn;
238 }
239 public void setLastSavedTimeColumn(String lastSavedTimeColumn)
240 {
241 checkNotNull(lastSavedTimeColumn);
242 _lastSavedTimeColumn = lastSavedTimeColumn;
243 }
244 public String getExpiryTimeColumn()
245 {
246 return _expiryTimeColumn;
247 }
248 public void setExpiryTimeColumn(String expiryTimeColumn)
249 {
250 checkNotNull(expiryTimeColumn);
251 _expiryTimeColumn = expiryTimeColumn;
252 }
253 public String getMaxIntervalColumn()
254 {
255 return _maxIntervalColumn;
256 }
257 public void setMaxIntervalColumn(String maxIntervalColumn)
258 {
259 checkNotNull(maxIntervalColumn);
260 _maxIntervalColumn = maxIntervalColumn;
261 }
262 public String getMapColumn()
263 {
264 return _mapColumn;
265 }
266 public void setMapColumn(String mapColumn)
267 {
268 checkNotNull(mapColumn);
269 _mapColumn = mapColumn;
270 }
271
272 public String getCreateStatementAsString ()
273 {
274 if (_dbAdaptor == null)
275 throw new IllegalStateException ("No DBAdaptor");
276
277 String blobType = _dbAdaptor.getBlobType();
278 String longType = _dbAdaptor.getLongType();
279
280 return "create table "+_tableName+" ("+getRowIdColumn()+" varchar(120), "+_idColumn+" varchar(120), "+
281 _contextPathColumn+" varchar(60), "+_virtualHostColumn+" varchar(60), "+_lastNodeColumn+" varchar(60), "+_accessTimeColumn+" "+longType+", "+
282 _lastAccessTimeColumn+" "+longType+", "+_createTimeColumn+" "+longType+", "+_cookieTimeColumn+" "+longType+", "+
283 _lastSavedTimeColumn+" "+longType+", "+_expiryTimeColumn+" "+longType+", "+_maxIntervalColumn+" "+longType+", "+
284 _mapColumn+" "+blobType+", primary key("+getRowIdColumn()+"))";
285 }
286
287 public String getCreateIndexOverExpiryStatementAsString (String indexName)
288 {
289 return "create index "+indexName+" on "+getTableName()+" ("+getExpiryTimeColumn()+")";
290 }
291
292 public String getCreateIndexOverSessionStatementAsString (String indexName)
293 {
294 return "create index "+indexName+" on "+getTableName()+" ("+getIdColumn()+", "+getContextPathColumn()+")";
295 }
296
297 public String getAlterTableForMaxIntervalAsString ()
298 {
299 if (_dbAdaptor == null)
300 throw new IllegalStateException ("No DBAdaptor");
301 String longType = _dbAdaptor.getLongType();
302 return "alter table "+getTableName()+" add "+longType+" not null default "+MAX_INTERVAL_NOT_SET;
303 }
304
305 private void checkNotNull(String s)
306 {
307 if (s == null)
308 throw new IllegalArgumentException(s);
309 }
310 public String getInsertSessionStatementAsString()
311 {
312 return "insert into "+getTableName()+
313 " ("+getRowIdColumn()+", "+getIdColumn()+", "+getContextPathColumn()+", "+getVirtualHostColumn()+", "+getLastNodeColumn()+
314 ", "+getAccessTimeColumn()+", "+getLastAccessTimeColumn()+", "+getCreateTimeColumn()+", "+getCookieTimeColumn()+
315 ", "+getLastSavedTimeColumn()+", "+getExpiryTimeColumn()+", "+getMaxIntervalColumn()+", "+getMapColumn()+") "+
316 " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
317 }
318 public String getDeleteSessionStatementAsString()
319 {
320 return "delete from "+getTableName()+
321 " where "+getRowIdColumn()+" = ?";
322 }
323 public String getUpdateSessionStatementAsString()
324 {
325 return "update "+getTableName()+
326 " set "+getIdColumn()+" = ?, "+getLastNodeColumn()+" = ?, "+getAccessTimeColumn()+" = ?, "+
327 getLastAccessTimeColumn()+" = ?, "+getLastSavedTimeColumn()+" = ?, "+getExpiryTimeColumn()+" = ?, "+
328 getMaxIntervalColumn()+" = ?, "+getMapColumn()+" = ? where "+getRowIdColumn()+" = ?";
329 }
330 public String getUpdateSessionNodeStatementAsString()
331 {
332 return "update "+getTableName()+
333 " set "+getLastNodeColumn()+" = ? where "+getRowIdColumn()+" = ?";
334 }
335 public String getUpdateSessionAccessTimeStatementAsString()
336 {
337 return "update "+getTableName()+
338 " set "+getLastNodeColumn()+" = ?, "+getAccessTimeColumn()+" = ?, "+getLastAccessTimeColumn()+" = ?, "+
339 getLastSavedTimeColumn()+" = ?, "+getExpiryTimeColumn()+" = ?, "+getMaxIntervalColumn()+" = ? where "+getRowIdColumn()+" = ?";
340 }
341
342 public String getBoundedExpiredSessionsStatementAsString()
343 {
344 return "select * from "+getTableName()+" where "+getLastNodeColumn()+" = ? and "+getExpiryTimeColumn()+" >= ? and "+getExpiryTimeColumn()+" <= ?";
345 }
346
347 public String getSelectExpiredSessionsStatementAsString()
348 {
349 return "select * from "+getTableName()+" where "+getExpiryTimeColumn()+" >0 and "+getExpiryTimeColumn()+" <= ?";
350 }
351
352 public PreparedStatement getLoadStatement (Connection connection, String rowId, String contextPath, String virtualHosts)
353 throws SQLException
354 {
355 if (_dbAdaptor == null)
356 throw new IllegalStateException("No DB adaptor");
357
358
359 if (contextPath == null || "".equals(contextPath))
360 {
361 if (_dbAdaptor.isEmptyStringNull())
362 {
363 PreparedStatement statement = connection.prepareStatement("select * from "+getTableName()+
364 " where "+getIdColumn()+" = ? and "+
365 getContextPathColumn()+" is null and "+
366 getVirtualHostColumn()+" = ?");
367 statement.setString(1, rowId);
368 statement.setString(2, virtualHosts);
369
370 return statement;
371 }
372 }
373
374 PreparedStatement statement = connection.prepareStatement("select * from "+getTableName()+
375 " where "+getIdColumn()+" = ? and "+getContextPathColumn()+
376 " = ? and "+getVirtualHostColumn()+" = ?");
377 statement.setString(1, rowId);
378 statement.setString(2, contextPath);
379 statement.setString(3, virtualHosts);
380
381 return statement;
382 }
383 }
384
385
386
387
388
389
390
391 public static class SessionIdTableSchema
392 {
393 protected DatabaseAdaptor _dbAdaptor;
394 protected String _tableName = "JettySessionIds";
395 protected String _idColumn = "id";
396
397 public void setDatabaseAdaptor(DatabaseAdaptor dbAdaptor)
398 {
399 _dbAdaptor = dbAdaptor;
400 }
401 public String getIdColumn()
402 {
403 return _idColumn;
404 }
405
406 public void setIdColumn(String idColumn)
407 {
408 checkNotNull(idColumn);
409 _idColumn = idColumn;
410 }
411
412 public String getTableName()
413 {
414 return _tableName;
415 }
416
417 public void setTableName(String tableName)
418 {
419 checkNotNull(tableName);
420 _tableName = tableName;
421 }
422
423 public String getInsertStatementAsString ()
424 {
425 return "insert into "+_tableName+" ("+_idColumn+") values (?)";
426 }
427
428 public String getDeleteStatementAsString ()
429 {
430 return "delete from "+_tableName+" where "+_idColumn+" = ?";
431 }
432
433 public String getSelectStatementAsString ()
434 {
435 return "select * from "+_tableName+" where "+_idColumn+" = ?";
436 }
437
438 public String getCreateStatementAsString ()
439 {
440 return "create table "+_tableName+" ("+_idColumn+" varchar(120), primary key("+_idColumn+"))";
441 }
442
443 private void checkNotNull(String s)
444 {
445 if (s == null)
446 throw new IllegalArgumentException(s);
447 }
448 }
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463 public static class DatabaseAdaptor
464 {
465 String _dbName;
466 boolean _isLower;
467 boolean _isUpper;
468
469 protected String _blobType;
470 protected String _longType;
471
472
473 public DatabaseAdaptor ()
474 {
475 }
476
477
478 public void adaptTo(DatabaseMetaData dbMeta)
479 throws SQLException
480 {
481 _dbName = dbMeta.getDatabaseProductName().toLowerCase(Locale.ENGLISH);
482 LOG.debug ("Using database {}",_dbName);
483 _isLower = dbMeta.storesLowerCaseIdentifiers();
484 _isUpper = dbMeta.storesUpperCaseIdentifiers();
485 }
486
487
488 public void setBlobType(String blobType)
489 {
490 _blobType = blobType;
491 }
492
493 public String getBlobType ()
494 {
495 if (_blobType != null)
496 return _blobType;
497
498 if (_dbName.startsWith("postgres"))
499 return "bytea";
500
501 return "blob";
502 }
503
504
505 public void setLongType(String longType)
506 {
507 _longType = longType;
508 }
509
510
511 public String getLongType ()
512 {
513 if (_longType != null)
514 return _longType;
515
516 if (_dbName == null)
517 throw new IllegalStateException ("DbAdaptor missing metadata");
518
519 if (_dbName.startsWith("oracle"))
520 return "number(20)";
521
522 return "bigint";
523 }
524
525
526
527
528
529
530
531
532
533 public String convertIdentifier (String identifier)
534 {
535 if (_dbName == null)
536 throw new IllegalStateException ("DbAdaptor missing metadata");
537
538 if (_isLower)
539 return identifier.toLowerCase(Locale.ENGLISH);
540 if (_isUpper)
541 return identifier.toUpperCase(Locale.ENGLISH);
542
543 return identifier;
544 }
545
546 public String getDBName ()
547 {
548 return _dbName;
549 }
550
551
552 public InputStream getBlobInputStream (ResultSet result, String columnName)
553 throws SQLException
554 {
555 if (_dbName == null)
556 throw new IllegalStateException ("DbAdaptor missing metadata");
557
558 if (_dbName.startsWith("postgres"))
559 {
560 byte[] bytes = result.getBytes(columnName);
561 return new ByteArrayInputStream(bytes);
562 }
563
564 Blob blob = result.getBlob(columnName);
565 return blob.getBinaryStream();
566 }
567
568
569 public boolean isEmptyStringNull ()
570 {
571 if (_dbName == null)
572 throw new IllegalStateException ("DbAdaptor missing metadata");
573
574 return (_dbName.startsWith("oracle"));
575 }
576
577
578
579
580
581 public boolean isRowIdReserved ()
582 {
583 if (_dbName == null)
584 throw new IllegalStateException ("DbAdaptor missing metadata");
585
586 return (_dbName != null && _dbName.startsWith("oracle"));
587 }
588 }
589
590
591
592
593
594
595 protected class Scavenger implements Runnable
596 {
597
598 @Override
599 public void run()
600 {
601 try
602 {
603 scavenge();
604 }
605 finally
606 {
607 if (_scheduler != null && _scheduler.isRunning())
608 _scheduler.schedule(this, _scavengeIntervalMs, TimeUnit.MILLISECONDS);
609 }
610 }
611 }
612
613
614 public JDBCSessionIdManager(Server server)
615 {
616 super();
617 _server=server;
618 }
619
620 public JDBCSessionIdManager(Server server, Random random)
621 {
622 super(random);
623 _server=server;
624 }
625
626
627
628
629
630
631
632 public void setDriverInfo (String driverClassName, String connectionUrl)
633 {
634 _driverClassName=driverClassName;
635 _connectionUrl=connectionUrl;
636 }
637
638
639
640
641
642
643
644 public void setDriverInfo (Driver driverClass, String connectionUrl)
645 {
646 _driver=driverClass;
647 _connectionUrl=connectionUrl;
648 }
649
650
651 public void setDatasource (DataSource ds)
652 {
653 _datasource = ds;
654 }
655
656 public DataSource getDataSource ()
657 {
658 return _datasource;
659 }
660
661 public String getDriverClassName()
662 {
663 return _driverClassName;
664 }
665
666 public String getConnectionUrl ()
667 {
668 return _connectionUrl;
669 }
670
671 public void setDatasourceName (String jndi)
672 {
673 _jndiName=jndi;
674 }
675
676 public String getDatasourceName ()
677 {
678 return _jndiName;
679 }
680
681
682
683
684
685 public void setBlobType (String name)
686 {
687 _dbAdaptor.setBlobType(name);
688 }
689
690 public DatabaseAdaptor getDbAdaptor()
691 {
692 return _dbAdaptor;
693 }
694
695 public void setDbAdaptor(DatabaseAdaptor dbAdaptor)
696 {
697 if (dbAdaptor == null)
698 throw new IllegalStateException ("DbAdaptor cannot be null");
699
700 _dbAdaptor = dbAdaptor;
701 }
702
703
704
705
706
707 public String getBlobType ()
708 {
709 return _dbAdaptor.getBlobType();
710 }
711
712
713
714
715
716 public String getLongType()
717 {
718 return _dbAdaptor.getLongType();
719 }
720
721
722
723
724
725 public void setLongType(String longType)
726 {
727 _dbAdaptor.setLongType(longType);
728 }
729
730 public SessionIdTableSchema getSessionIdTableSchema()
731 {
732 return _sessionIdTableSchema;
733 }
734
735 public void setSessionIdTableSchema(SessionIdTableSchema sessionIdTableSchema)
736 {
737 if (sessionIdTableSchema == null)
738 throw new IllegalArgumentException("Null SessionIdTableSchema");
739
740 _sessionIdTableSchema = sessionIdTableSchema;
741 }
742
743 public SessionTableSchema getSessionTableSchema()
744 {
745 return _sessionTableSchema;
746 }
747
748 public void setSessionTableSchema(SessionTableSchema sessionTableSchema)
749 {
750 _sessionTableSchema = sessionTableSchema;
751 }
752
753 public void setDeleteBlockSize (int bsize)
754 {
755 this._deleteBlockSize = bsize;
756 }
757
758 public int getDeleteBlockSize ()
759 {
760 return this._deleteBlockSize;
761 }
762
763 public void setScavengeInterval (long sec)
764 {
765 if (sec<=0)
766 sec=60;
767
768 long old_period=_scavengeIntervalMs;
769 long period=sec*1000L;
770
771 _scavengeIntervalMs=period;
772
773
774
775 long tenPercent = _scavengeIntervalMs/10;
776 if ((System.currentTimeMillis()%2) == 0)
777 _scavengeIntervalMs += tenPercent;
778
779 if (LOG.isDebugEnabled())
780 LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms");
781
782
783 if (_scheduler != null && (period!=old_period || _task==null))
784 {
785 synchronized (this)
786 {
787 if (_task!=null)
788 _task.cancel();
789 if (_scavenger == null)
790 _scavenger = new Scavenger();
791 _task = _scheduler.schedule(_scavenger,_scavengeIntervalMs,TimeUnit.MILLISECONDS);
792 }
793 }
794 }
795
796 public long getScavengeInterval ()
797 {
798 return _scavengeIntervalMs/1000;
799 }
800
801
802 @Override
803 public void addSession(HttpSession session)
804 {
805 if (session == null)
806 return;
807
808 synchronized (_sessionIds)
809 {
810 String id = ((JDBCSessionManager.Session)session).getClusterId();
811 try
812 {
813 insert(id);
814 _sessionIds.add(id);
815 }
816 catch (Exception e)
817 {
818 LOG.warn("Problem storing session id="+id, e);
819 }
820 }
821 }
822
823
824 public void addSession(String id)
825 {
826 if (id == null)
827 return;
828
829 synchronized (_sessionIds)
830 {
831 try
832 {
833 insert(id);
834 _sessionIds.add(id);
835 }
836 catch (Exception e)
837 {
838 LOG.warn("Problem storing session id="+id, e);
839 }
840 }
841 }
842
843
844
845 @Override
846 public void removeSession(HttpSession session)
847 {
848 if (session == null)
849 return;
850
851 removeSession(((JDBCSessionManager.Session)session).getClusterId());
852 }
853
854
855
856 public void removeSession (String id)
857 {
858
859 if (id == null)
860 return;
861
862 synchronized (_sessionIds)
863 {
864 if (LOG.isDebugEnabled())
865 LOG.debug("Removing sessionid="+id);
866 try
867 {
868 _sessionIds.remove(id);
869 delete(id);
870 }
871 catch (Exception e)
872 {
873 LOG.warn("Problem removing session id="+id, e);
874 }
875 }
876
877 }
878
879
880 @Override
881 public boolean idInUse(String id)
882 {
883 if (id == null)
884 return false;
885
886 String clusterId = getClusterId(id);
887 boolean inUse = false;
888 synchronized (_sessionIds)
889 {
890 inUse = _sessionIds.contains(clusterId);
891 }
892
893
894 if (inUse)
895 return true;
896
897
898 try
899 {
900 return exists(clusterId);
901 }
902 catch (Exception e)
903 {
904 LOG.warn("Problem checking inUse for id="+clusterId, e);
905 return false;
906 }
907 }
908
909
910
911
912
913
914 @Override
915 public void invalidateAll(String id)
916 {
917
918 removeSession(id);
919
920 synchronized (_sessionIds)
921 {
922
923
924 Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
925 for (int i=0; contexts!=null && i<contexts.length; i++)
926 {
927 SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
928 if (sessionHandler != null)
929 {
930 SessionManager manager = sessionHandler.getSessionManager();
931
932 if (manager != null && manager instanceof JDBCSessionManager)
933 {
934 ((JDBCSessionManager)manager).invalidateSession(id);
935 }
936 }
937 }
938 }
939 }
940
941
942 @Override
943 public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
944 {
945
946 String newClusterId = newSessionId(request.hashCode());
947
948 synchronized (_sessionIds)
949 {
950 removeSession(oldClusterId);
951 addSession(newClusterId);
952
953
954 Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
955 for (int i=0; contexts!=null && i<contexts.length; i++)
956 {
957 SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
958 if (sessionHandler != null)
959 {
960 SessionManager manager = sessionHandler.getSessionManager();
961
962 if (manager != null && manager instanceof JDBCSessionManager)
963 {
964 ((JDBCSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
965 }
966 }
967 }
968 }
969 }
970
971
972
973
974
975
976
977
978 @Override
979 public void doStart()
980 throws Exception
981 {
982 initializeDatabase();
983 prepareTables();
984 super.doStart();
985 if (LOG.isDebugEnabled())
986 LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec");
987
988
989 _scheduler =_server.getBean(Scheduler.class);
990 if (_scheduler == null)
991 {
992 _scheduler = new ScheduledExecutorScheduler();
993 _ownScheduler = true;
994 _scheduler.start();
995 }
996
997 setScavengeInterval(getScavengeInterval());
998 }
999
1000
1001
1002
1003 @Override
1004 public void doStop ()
1005 throws Exception
1006 {
1007 synchronized(this)
1008 {
1009 if (_task!=null)
1010 _task.cancel();
1011 _task=null;
1012 if (_ownScheduler && _scheduler !=null)
1013 _scheduler.stop();
1014 _scheduler=null;
1015 }
1016 _sessionIds.clear();
1017 super.doStop();
1018 }
1019
1020
1021
1022
1023
1024
1025
1026 protected Connection getConnection ()
1027 throws SQLException
1028 {
1029 if (_datasource != null)
1030 return _datasource.getConnection();
1031 else
1032 return DriverManager.getConnection(_connectionUrl);
1033 }
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047 private void prepareTables()
1048 throws SQLException
1049 {
1050 if (_sessionIdTableSchema == null)
1051 throw new IllegalStateException ("No SessionIdTableSchema");
1052
1053 if (_sessionTableSchema == null)
1054 throw new IllegalStateException ("No SessionTableSchema");
1055
1056 try (Connection connection = getConnection();
1057 Statement statement = connection.createStatement())
1058 {
1059
1060 connection.setAutoCommit(true);
1061 DatabaseMetaData metaData = connection.getMetaData();
1062 _dbAdaptor.adaptTo(metaData);
1063 _sessionTableSchema.setDatabaseAdaptor(_dbAdaptor);
1064 _sessionIdTableSchema.setDatabaseAdaptor(_dbAdaptor);
1065
1066 _createSessionIdTable = _sessionIdTableSchema.getCreateStatementAsString();
1067 _insertId = _sessionIdTableSchema.getInsertStatementAsString();
1068 _deleteId = _sessionIdTableSchema.getDeleteStatementAsString();
1069 _queryId = _sessionIdTableSchema.getSelectStatementAsString();
1070
1071
1072 String tableName = _dbAdaptor.convertIdentifier(_sessionIdTableSchema.getTableName());
1073 try (ResultSet result = metaData.getTables(null, null, tableName, null))
1074 {
1075 if (!result.next())
1076 {
1077
1078 statement.executeUpdate(_createSessionIdTable);
1079 }
1080 }
1081
1082
1083 tableName = _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName());
1084 try (ResultSet result = metaData.getTables(null, null, tableName, null))
1085 {
1086 if (!result.next())
1087 {
1088
1089 _createSessionTable = _sessionTableSchema.getCreateStatementAsString();
1090 statement.executeUpdate(_createSessionTable);
1091 }
1092 else
1093 {
1094
1095 ResultSet colResult = null;
1096 try
1097 {
1098 colResult = metaData.getColumns(null, null,
1099 _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName()),
1100 _dbAdaptor.convertIdentifier(_sessionTableSchema.getMaxIntervalColumn()));
1101 }
1102 catch (SQLException s)
1103 {
1104 LOG.warn("Problem checking if "+_sessionTableSchema.getTableName()+
1105 " table contains "+_sessionTableSchema.getMaxIntervalColumn()+" column. Ensure table contains column definition: \""
1106 +_sessionTableSchema.getMaxIntervalColumn()+" long not null default -999\"");
1107 throw s;
1108 }
1109 try
1110 {
1111 if (!colResult.next())
1112 {
1113 try
1114 {
1115
1116 statement.executeUpdate(_sessionTableSchema.getAlterTableForMaxIntervalAsString());
1117 }
1118 catch (SQLException s)
1119 {
1120 LOG.warn("Problem adding "+_sessionTableSchema.getMaxIntervalColumn()+
1121 " column. Ensure table contains column definition: \""+_sessionTableSchema.getMaxIntervalColumn()+
1122 " long not null default -999\"");
1123 throw s;
1124 }
1125 }
1126 }
1127 finally
1128 {
1129 colResult.close();
1130 }
1131 }
1132 }
1133
1134 String index1 = "idx_"+_sessionTableSchema.getTableName()+"_expiry";
1135 String index2 = "idx_"+_sessionTableSchema.getTableName()+"_session";
1136
1137 boolean index1Exists = false;
1138 boolean index2Exists = false;
1139 try (ResultSet result = metaData.getIndexInfo(null, null, tableName, false, false))
1140 {
1141 while (result.next())
1142 {
1143 String idxName = result.getString("INDEX_NAME");
1144 if (index1.equalsIgnoreCase(idxName))
1145 index1Exists = true;
1146 else if (index2.equalsIgnoreCase(idxName))
1147 index2Exists = true;
1148 }
1149 }
1150 if (!index1Exists)
1151 statement.executeUpdate(_sessionTableSchema.getCreateIndexOverExpiryStatementAsString(index1));
1152 if (!index2Exists)
1153 statement.executeUpdate(_sessionTableSchema.getCreateIndexOverSessionStatementAsString(index2));
1154
1155
1156 _insertSession = _sessionTableSchema.getInsertSessionStatementAsString();
1157 _deleteSession = _sessionTableSchema.getDeleteSessionStatementAsString();
1158 _updateSession = _sessionTableSchema.getUpdateSessionStatementAsString();
1159 _updateSessionNode = _sessionTableSchema.getUpdateSessionNodeStatementAsString();
1160 _updateSessionAccessTime = _sessionTableSchema.getUpdateSessionAccessTimeStatementAsString();
1161 _selectBoundedExpiredSessions = _sessionTableSchema.getBoundedExpiredSessionsStatementAsString();
1162 _selectExpiredSessions = _sessionTableSchema.getSelectExpiredSessionsStatementAsString();
1163 }
1164 }
1165
1166
1167
1168
1169
1170
1171
1172 private void insert (String id)
1173 throws SQLException
1174 {
1175 try (Connection connection = getConnection();
1176 PreparedStatement query = connection.prepareStatement(_queryId))
1177 {
1178 connection.setAutoCommit(true);
1179 query.setString(1, id);
1180 try (ResultSet result = query.executeQuery())
1181 {
1182
1183 if (!result.next())
1184 {
1185 try (PreparedStatement statement = connection.prepareStatement(_insertId))
1186 {
1187 statement.setString(1, id);
1188 statement.executeUpdate();
1189 }
1190 }
1191 }
1192 }
1193 }
1194
1195
1196
1197
1198
1199
1200
1201 private void delete (String id)
1202 throws SQLException
1203 {
1204 try (Connection connection = getConnection();
1205 PreparedStatement statement = connection.prepareStatement(_deleteId))
1206 {
1207 connection.setAutoCommit(true);
1208 statement.setString(1, id);
1209 statement.executeUpdate();
1210 }
1211 }
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221 private boolean exists (String id)
1222 throws SQLException
1223 {
1224 try (Connection connection = getConnection();
1225 PreparedStatement statement = connection.prepareStatement(_queryId))
1226 {
1227 connection.setAutoCommit(true);
1228 statement.setString(1, id);
1229 try (ResultSet result = statement.executeQuery())
1230 {
1231 return result.next();
1232 }
1233 }
1234 }
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247 private void scavenge ()
1248 {
1249 Connection connection = null;
1250 try
1251 {
1252 if (LOG.isDebugEnabled())
1253 LOG.debug(getWorkerName()+"- Scavenge sweep started at "+System.currentTimeMillis());
1254 if (_lastScavengeTime > 0)
1255 {
1256 connection = getConnection();
1257 connection.setAutoCommit(true);
1258 Set<String> expiredSessionIds = new HashSet<String>();
1259
1260
1261
1262 long lowerBound = (_lastScavengeTime - _scavengeIntervalMs);
1263 long upperBound = _lastScavengeTime;
1264 if (LOG.isDebugEnabled())
1265 LOG.debug (getWorkerName()+"- Pass 1: Searching for sessions expired between "+lowerBound + " and "+upperBound);
1266
1267 try (PreparedStatement statement = connection.prepareStatement(_selectBoundedExpiredSessions))
1268 {
1269 statement.setString(1, getWorkerName());
1270 statement.setLong(2, lowerBound);
1271 statement.setLong(3, upperBound);
1272 try (ResultSet result = statement.executeQuery())
1273 {
1274 while (result.next())
1275 {
1276 String sessionId = result.getString(_sessionTableSchema.getIdColumn());
1277 expiredSessionIds.add(sessionId);
1278 if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
1279 }
1280 }
1281 }
1282 scavengeSessions(expiredSessionIds, false);
1283
1284
1285
1286 try (PreparedStatement selectExpiredSessions = connection.prepareStatement(_selectExpiredSessions))
1287 {
1288 expiredSessionIds.clear();
1289 upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
1290 if (upperBound > 0)
1291 {
1292 if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 2: Searching for sessions expired before "+upperBound);
1293 selectExpiredSessions.setLong(1, upperBound);
1294 try (ResultSet result = selectExpiredSessions.executeQuery())
1295 {
1296 while (result.next())
1297 {
1298 String sessionId = result.getString(_sessionTableSchema.getIdColumn());
1299 String lastNode = result.getString(_sessionTableSchema.getLastNodeColumn());
1300 if ((getWorkerName() == null && lastNode == null) || (getWorkerName() != null && getWorkerName().equals(lastNode)))
1301 expiredSessionIds.add(sessionId);
1302 if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId+" last managed by "+getWorkerName());
1303 }
1304 }
1305 scavengeSessions(expiredSessionIds, false);
1306 }
1307
1308
1309
1310
1311
1312
1313 upperBound = _lastScavengeTime - (3 * _scavengeIntervalMs);
1314 expiredSessionIds.clear();
1315 if (upperBound > 0)
1316 {
1317 if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 3: searching for sessions expired before "+upperBound);
1318 selectExpiredSessions.setLong(1, upperBound);
1319 try (ResultSet result = selectExpiredSessions.executeQuery())
1320 {
1321 while (result.next())
1322 {
1323 String sessionId = result.getString(_sessionTableSchema.getIdColumn());
1324 expiredSessionIds.add(sessionId);
1325 if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
1326 }
1327 }
1328 scavengeSessions(expiredSessionIds, true);
1329 }
1330 }
1331 }
1332 }
1333 catch (Exception e)
1334 {
1335 if (isRunning())
1336 LOG.warn("Problem selecting expired sessions", e);
1337 else
1338 LOG.ignore(e);
1339 }
1340 finally
1341 {
1342 _lastScavengeTime=System.currentTimeMillis();
1343 if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Scavenge sweep ended at "+_lastScavengeTime);
1344 if (connection != null)
1345 {
1346 try
1347 {
1348 connection.close();
1349 }
1350 catch (SQLException e)
1351 {
1352 LOG.warn(e);
1353 }
1354 }
1355 }
1356 }
1357
1358
1359
1360
1361
1362 private void scavengeSessions (Set<String> expiredSessionIds, boolean forceDelete)
1363 {
1364 Set<String> remainingIds = new HashSet<String>(expiredSessionIds);
1365 Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
1366 for (int i=0; contexts!=null && i<contexts.length; i++)
1367 {
1368 SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
1369 if (sessionHandler != null)
1370 {
1371 SessionManager manager = sessionHandler.getSessionManager();
1372 if (manager != null && manager instanceof JDBCSessionManager)
1373 {
1374 Set<String> successfullyExpiredIds = ((JDBCSessionManager)manager).expire(expiredSessionIds);
1375 if (successfullyExpiredIds != null)
1376 remainingIds.removeAll(successfullyExpiredIds);
1377 }
1378 }
1379 }
1380
1381
1382 if (!remainingIds.isEmpty() && forceDelete)
1383 {
1384 LOG.info("Forcibly deleting unrecoverable expired sessions {}", remainingIds);
1385 try
1386 {
1387
1388 synchronized (_sessionIds)
1389 {
1390 _sessionIds.removeAll(remainingIds);
1391 }
1392
1393 cleanExpiredSessionIds(remainingIds);
1394 }
1395 catch (Exception e)
1396 {
1397 LOG.warn("Error removing expired session ids", e);
1398 }
1399 }
1400 }
1401
1402
1403
1404
1405 private void cleanExpiredSessionIds (Set<String> expiredIds)
1406 throws Exception
1407 {
1408 if (expiredIds == null || expiredIds.isEmpty())
1409 return;
1410
1411 String[] ids = expiredIds.toArray(new String[expiredIds.size()]);
1412 try (Connection con = getConnection())
1413 {
1414 con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
1415 con.setAutoCommit(false);
1416
1417 int start = 0;
1418 int end = 0;
1419 int blocksize = _deleteBlockSize;
1420 int block = 0;
1421
1422 try (Statement statement = con.createStatement())
1423 {
1424 while (end < ids.length)
1425 {
1426 start = block*blocksize;
1427 if ((ids.length - start) >= blocksize)
1428 end = start + blocksize;
1429 else
1430 end = ids.length;
1431
1432
1433 statement.executeUpdate(fillInClause("delete from "+_sessionIdTableSchema.getTableName()+" where "+_sessionIdTableSchema.getIdColumn()+" in ", ids, start, end));
1434
1435 statement.executeUpdate(fillInClause("delete from "+_sessionTableSchema.getTableName()+" where "+_sessionTableSchema.getIdColumn()+" in ", ids, start, end));
1436 block++;
1437 }
1438 }
1439 catch (Exception e)
1440 {
1441 con.rollback();
1442 throw e;
1443 }
1444 con.commit();
1445 }
1446 }
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456 private String fillInClause (String sql, String[] literals, int start, int end)
1457 throws Exception
1458 {
1459 StringBuffer buff = new StringBuffer();
1460 buff.append(sql);
1461 buff.append("(");
1462 for (int i=start; i<end; i++)
1463 {
1464 buff.append("'"+(literals[i])+"'");
1465 if (i+1<end)
1466 buff.append(",");
1467 }
1468 buff.append(")");
1469 return buff.toString();
1470 }
1471
1472
1473
1474 private void initializeDatabase ()
1475 throws Exception
1476 {
1477 if (_datasource != null)
1478 return;
1479
1480 if (_jndiName!=null)
1481 {
1482 InitialContext ic = new InitialContext();
1483 _datasource = (DataSource)ic.lookup(_jndiName);
1484 }
1485 else if ( _driver != null && _connectionUrl != null )
1486 {
1487 DriverManager.registerDriver(_driver);
1488 }
1489 else if (_driverClassName != null && _connectionUrl != null)
1490 {
1491 Class.forName(_driverClassName);
1492 }
1493 else
1494 throw new IllegalStateException("No database configured for sessions");
1495 }
1496
1497
1498 }