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