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 public void setBlobType (String name)
689 {
690 _dbAdaptor.setBlobType(name);
691 }
692
693 public DatabaseAdaptor getDbAdaptor()
694 {
695 return _dbAdaptor;
696 }
697
698 public void setDbAdaptor(DatabaseAdaptor dbAdaptor)
699 {
700 if (dbAdaptor == null)
701 throw new IllegalStateException ("DbAdaptor cannot be null");
702
703 _dbAdaptor = dbAdaptor;
704 }
705
706
707
708
709
710 public String getBlobType ()
711 {
712 return _dbAdaptor.getBlobType();
713 }
714
715
716
717
718
719 public String getLongType()
720 {
721 return _dbAdaptor.getLongType();
722 }
723
724
725
726
727
728 public void setLongType(String longType)
729 {
730 _dbAdaptor.setLongType(longType);
731 }
732
733 public SessionIdTableSchema getSessionIdTableSchema()
734 {
735 return _sessionIdTableSchema;
736 }
737
738 public void setSessionIdTableSchema(SessionIdTableSchema sessionIdTableSchema)
739 {
740 if (sessionIdTableSchema == null)
741 throw new IllegalArgumentException("Null SessionIdTableSchema");
742
743 _sessionIdTableSchema = sessionIdTableSchema;
744 }
745
746 public SessionTableSchema getSessionTableSchema()
747 {
748 return _sessionTableSchema;
749 }
750
751 public void setSessionTableSchema(SessionTableSchema sessionTableSchema)
752 {
753 _sessionTableSchema = sessionTableSchema;
754 }
755
756 public void setDeleteBlockSize (int bsize)
757 {
758 this._deleteBlockSize = bsize;
759 }
760
761 public int getDeleteBlockSize ()
762 {
763 return this._deleteBlockSize;
764 }
765
766 public void setScavengeInterval (long sec)
767 {
768 if (sec<=0)
769 sec=60;
770
771 long old_period=_scavengeIntervalMs;
772 long period=sec*1000L;
773
774 _scavengeIntervalMs=period;
775
776
777
778 long tenPercent = _scavengeIntervalMs/10;
779 if ((System.currentTimeMillis()%2) == 0)
780 _scavengeIntervalMs += tenPercent;
781
782 if (LOG.isDebugEnabled())
783 LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms");
784
785 synchronized (this)
786 {
787
788 if (_scheduler != null && (period!=old_period || _task==null))
789 {
790 if (_task!=null)
791 _task.cancel();
792 if (_scavenger == null)
793 _scavenger = new Scavenger();
794 _task = _scheduler.schedule(_scavenger,_scavengeIntervalMs,TimeUnit.MILLISECONDS);
795 }
796 }
797 }
798
799 public long getScavengeInterval ()
800 {
801 return _scavengeIntervalMs/1000;
802 }
803
804
805 @Override
806 public void addSession(HttpSession session)
807 {
808 if (session == null)
809 return;
810
811 synchronized (_sessionIds)
812 {
813 String id = ((JDBCSessionManager.Session)session).getClusterId();
814 try
815 {
816 insert(id);
817 _sessionIds.add(id);
818 }
819 catch (Exception e)
820 {
821 LOG.warn("Problem storing session id="+id, e);
822 }
823 }
824 }
825
826
827 public void addSession(String id)
828 {
829 if (id == null)
830 return;
831
832 synchronized (_sessionIds)
833 {
834 try
835 {
836 insert(id);
837 _sessionIds.add(id);
838 }
839 catch (Exception e)
840 {
841 LOG.warn("Problem storing session id="+id, e);
842 }
843 }
844 }
845
846
847
848 @Override
849 public void removeSession(HttpSession session)
850 {
851 if (session == null)
852 return;
853
854 removeSession(((JDBCSessionManager.Session)session).getClusterId());
855 }
856
857
858
859 public void removeSession (String id)
860 {
861
862 if (id == null)
863 return;
864
865 synchronized (_sessionIds)
866 {
867 if (LOG.isDebugEnabled())
868 LOG.debug("Removing sessionid="+id);
869 try
870 {
871 _sessionIds.remove(id);
872 delete(id);
873 }
874 catch (Exception e)
875 {
876 LOG.warn("Problem removing session id="+id, e);
877 }
878 }
879
880 }
881
882
883 @Override
884 public boolean idInUse(String id)
885 {
886 if (id == null)
887 return false;
888
889 String clusterId = getClusterId(id);
890 boolean inUse = false;
891 synchronized (_sessionIds)
892 {
893 inUse = _sessionIds.contains(clusterId);
894 }
895
896
897 if (inUse)
898 return true;
899
900
901 try
902 {
903 return exists(clusterId);
904 }
905 catch (Exception e)
906 {
907 LOG.warn("Problem checking inUse for id="+clusterId, e);
908 return false;
909 }
910 }
911
912
913
914
915
916
917 @Override
918 public void invalidateAll(String id)
919 {
920
921 removeSession(id);
922
923 synchronized (_sessionIds)
924 {
925
926
927 Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
928 for (int i=0; contexts!=null && i<contexts.length; i++)
929 {
930 SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
931 if (sessionHandler != null)
932 {
933 SessionManager manager = sessionHandler.getSessionManager();
934
935 if (manager != null && manager instanceof JDBCSessionManager)
936 {
937 ((JDBCSessionManager)manager).invalidateSession(id);
938 }
939 }
940 }
941 }
942 }
943
944
945 @Override
946 public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
947 {
948
949 String newClusterId = newSessionId(request.hashCode());
950
951 synchronized (_sessionIds)
952 {
953 removeSession(oldClusterId);
954 addSession(newClusterId);
955
956
957 Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
958 for (int i=0; contexts!=null && i<contexts.length; i++)
959 {
960 SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
961 if (sessionHandler != null)
962 {
963 SessionManager manager = sessionHandler.getSessionManager();
964
965 if (manager != null && manager instanceof JDBCSessionManager)
966 {
967 ((JDBCSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
968 }
969 }
970 }
971 }
972 }
973
974
975
976
977
978
979
980
981 @Override
982 public void doStart()
983 throws Exception
984 {
985 initializeDatabase();
986 prepareTables();
987 super.doStart();
988 if (LOG.isDebugEnabled())
989 LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec");
990
991
992 _scheduler =_server.getBean(Scheduler.class);
993 if (_scheduler == null)
994 {
995 _scheduler = new ScheduledExecutorScheduler();
996 _ownScheduler = true;
997 _scheduler.start();
998 }
999 else if (!_scheduler.isStarted())
1000 throw new IllegalStateException("Shared scheduler not started");
1001
1002 setScavengeInterval(getScavengeInterval());
1003 }
1004
1005
1006
1007
1008 @Override
1009 public void doStop ()
1010 throws Exception
1011 {
1012 synchronized(this)
1013 {
1014 if (_task!=null)
1015 _task.cancel();
1016 _task=null;
1017 if (_ownScheduler && _scheduler !=null)
1018 _scheduler.stop();
1019 _scheduler=null;
1020 }
1021 _sessionIds.clear();
1022 super.doStop();
1023 }
1024
1025
1026
1027
1028
1029
1030
1031 protected Connection getConnection ()
1032 throws SQLException
1033 {
1034 if (_datasource != null)
1035 return _datasource.getConnection();
1036 else
1037 return DriverManager.getConnection(_connectionUrl);
1038 }
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052 private void prepareTables()
1053 throws SQLException
1054 {
1055 if (_sessionIdTableSchema == null)
1056 throw new IllegalStateException ("No SessionIdTableSchema");
1057
1058 if (_sessionTableSchema == null)
1059 throw new IllegalStateException ("No SessionTableSchema");
1060
1061 try (Connection connection = getConnection();
1062 Statement statement = connection.createStatement())
1063 {
1064
1065 connection.setAutoCommit(true);
1066 DatabaseMetaData metaData = connection.getMetaData();
1067 _dbAdaptor.adaptTo(metaData);
1068 _sessionTableSchema.setDatabaseAdaptor(_dbAdaptor);
1069 _sessionIdTableSchema.setDatabaseAdaptor(_dbAdaptor);
1070
1071 _createSessionIdTable = _sessionIdTableSchema.getCreateStatementAsString();
1072 _insertId = _sessionIdTableSchema.getInsertStatementAsString();
1073 _deleteId = _sessionIdTableSchema.getDeleteStatementAsString();
1074 _queryId = _sessionIdTableSchema.getSelectStatementAsString();
1075
1076
1077 String tableName = _dbAdaptor.convertIdentifier(_sessionIdTableSchema.getTableName());
1078 try (ResultSet result = metaData.getTables(null, null, tableName, null))
1079 {
1080 if (!result.next())
1081 {
1082
1083 statement.executeUpdate(_createSessionIdTable);
1084 }
1085 }
1086
1087
1088 tableName = _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName());
1089 try (ResultSet result = metaData.getTables(null, null, tableName, null))
1090 {
1091 if (!result.next())
1092 {
1093
1094 _createSessionTable = _sessionTableSchema.getCreateStatementAsString();
1095 statement.executeUpdate(_createSessionTable);
1096 }
1097 else
1098 {
1099
1100 ResultSet colResult = null;
1101 try
1102 {
1103 colResult = metaData.getColumns(null, null,
1104 _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName()),
1105 _dbAdaptor.convertIdentifier(_sessionTableSchema.getMaxIntervalColumn()));
1106 }
1107 catch (SQLException s)
1108 {
1109 LOG.warn("Problem checking if "+_sessionTableSchema.getTableName()+
1110 " table contains "+_sessionTableSchema.getMaxIntervalColumn()+" column. Ensure table contains column definition: \""
1111 +_sessionTableSchema.getMaxIntervalColumn()+" long not null default -999\"");
1112 throw s;
1113 }
1114 try
1115 {
1116 if (!colResult.next())
1117 {
1118 try
1119 {
1120
1121 statement.executeUpdate(_sessionTableSchema.getAlterTableForMaxIntervalAsString());
1122 }
1123 catch (SQLException s)
1124 {
1125 LOG.warn("Problem adding "+_sessionTableSchema.getMaxIntervalColumn()+
1126 " column. Ensure table contains column definition: \""+_sessionTableSchema.getMaxIntervalColumn()+
1127 " long not null default -999\"");
1128 throw s;
1129 }
1130 }
1131 }
1132 finally
1133 {
1134 colResult.close();
1135 }
1136 }
1137 }
1138
1139 String index1 = "idx_"+_sessionTableSchema.getTableName()+"_expiry";
1140 String index2 = "idx_"+_sessionTableSchema.getTableName()+"_session";
1141
1142 boolean index1Exists = false;
1143 boolean index2Exists = false;
1144 try (ResultSet result = metaData.getIndexInfo(null, null, tableName, false, false))
1145 {
1146 while (result.next())
1147 {
1148 String idxName = result.getString("INDEX_NAME");
1149 if (index1.equalsIgnoreCase(idxName))
1150 index1Exists = true;
1151 else if (index2.equalsIgnoreCase(idxName))
1152 index2Exists = true;
1153 }
1154 }
1155 if (!index1Exists)
1156 statement.executeUpdate(_sessionTableSchema.getCreateIndexOverExpiryStatementAsString(index1));
1157 if (!index2Exists)
1158 statement.executeUpdate(_sessionTableSchema.getCreateIndexOverSessionStatementAsString(index2));
1159
1160
1161 _insertSession = _sessionTableSchema.getInsertSessionStatementAsString();
1162 _deleteSession = _sessionTableSchema.getDeleteSessionStatementAsString();
1163 _updateSession = _sessionTableSchema.getUpdateSessionStatementAsString();
1164 _updateSessionNode = _sessionTableSchema.getUpdateSessionNodeStatementAsString();
1165 _updateSessionAccessTime = _sessionTableSchema.getUpdateSessionAccessTimeStatementAsString();
1166 _selectBoundedExpiredSessions = _sessionTableSchema.getBoundedExpiredSessionsStatementAsString();
1167 _selectExpiredSessions = _sessionTableSchema.getSelectExpiredSessionsStatementAsString();
1168 }
1169 }
1170
1171
1172
1173
1174
1175
1176
1177 private void insert (String id)
1178 throws SQLException
1179 {
1180 try (Connection connection = getConnection();
1181 PreparedStatement query = connection.prepareStatement(_queryId))
1182 {
1183 connection.setAutoCommit(true);
1184 query.setString(1, id);
1185 try (ResultSet result = query.executeQuery())
1186 {
1187
1188 if (!result.next())
1189 {
1190 try (PreparedStatement statement = connection.prepareStatement(_insertId))
1191 {
1192 statement.setString(1, id);
1193 statement.executeUpdate();
1194 }
1195 }
1196 }
1197 }
1198 }
1199
1200
1201
1202
1203
1204
1205
1206 private void delete (String id)
1207 throws SQLException
1208 {
1209 try (Connection connection = getConnection();
1210 PreparedStatement statement = connection.prepareStatement(_deleteId))
1211 {
1212 connection.setAutoCommit(true);
1213 statement.setString(1, id);
1214 statement.executeUpdate();
1215 }
1216 }
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226 private boolean exists (String id)
1227 throws SQLException
1228 {
1229 try (Connection connection = getConnection();
1230 PreparedStatement statement = connection.prepareStatement(_queryId))
1231 {
1232 connection.setAutoCommit(true);
1233 statement.setString(1, id);
1234 try (ResultSet result = statement.executeQuery())
1235 {
1236 return result.next();
1237 }
1238 }
1239 }
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252 private void scavenge ()
1253 {
1254 Connection connection = null;
1255 try
1256 {
1257 if (LOG.isDebugEnabled())
1258 LOG.debug(getWorkerName()+"- Scavenge sweep started at "+System.currentTimeMillis());
1259 if (_lastScavengeTime > 0)
1260 {
1261 connection = getConnection();
1262 connection.setAutoCommit(true);
1263 Set<String> expiredSessionIds = new HashSet<String>();
1264
1265
1266
1267 long lowerBound = (_lastScavengeTime - _scavengeIntervalMs);
1268 long upperBound = _lastScavengeTime;
1269 if (LOG.isDebugEnabled())
1270 LOG.debug (getWorkerName()+"- Pass 1: Searching for sessions expired between "+lowerBound + " and "+upperBound);
1271
1272 try (PreparedStatement statement = connection.prepareStatement(_selectBoundedExpiredSessions))
1273 {
1274 statement.setString(1, getWorkerName());
1275 statement.setLong(2, lowerBound);
1276 statement.setLong(3, upperBound);
1277 try (ResultSet result = statement.executeQuery())
1278 {
1279 while (result.next())
1280 {
1281 String sessionId = result.getString(_sessionTableSchema.getIdColumn());
1282 expiredSessionIds.add(sessionId);
1283 if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
1284 }
1285 }
1286 }
1287 scavengeSessions(expiredSessionIds, false);
1288
1289
1290
1291 try (PreparedStatement selectExpiredSessions = connection.prepareStatement(_selectExpiredSessions))
1292 {
1293 expiredSessionIds.clear();
1294 upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
1295 if (upperBound > 0)
1296 {
1297 if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 2: Searching for sessions expired before "+upperBound);
1298 selectExpiredSessions.setLong(1, upperBound);
1299 try (ResultSet result = selectExpiredSessions.executeQuery())
1300 {
1301 while (result.next())
1302 {
1303 String sessionId = result.getString(_sessionTableSchema.getIdColumn());
1304 String lastNode = result.getString(_sessionTableSchema.getLastNodeColumn());
1305 if ((getWorkerName() == null && lastNode == null) || (getWorkerName() != null && getWorkerName().equals(lastNode)))
1306 expiredSessionIds.add(sessionId);
1307 if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId+" last managed by "+getWorkerName());
1308 }
1309 }
1310 scavengeSessions(expiredSessionIds, false);
1311 }
1312
1313
1314
1315
1316
1317
1318 upperBound = _lastScavengeTime - (3 * _scavengeIntervalMs);
1319 expiredSessionIds.clear();
1320 if (upperBound > 0)
1321 {
1322 if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 3: searching for sessions expired before "+upperBound);
1323 selectExpiredSessions.setLong(1, upperBound);
1324 try (ResultSet result = selectExpiredSessions.executeQuery())
1325 {
1326 while (result.next())
1327 {
1328 String sessionId = result.getString(_sessionTableSchema.getIdColumn());
1329 expiredSessionIds.add(sessionId);
1330 if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
1331 }
1332 }
1333 scavengeSessions(expiredSessionIds, true);
1334 }
1335 }
1336 }
1337 }
1338 catch (Exception e)
1339 {
1340 if (isRunning())
1341 LOG.warn("Problem selecting expired sessions", e);
1342 else
1343 LOG.ignore(e);
1344 }
1345 finally
1346 {
1347 _lastScavengeTime=System.currentTimeMillis();
1348 if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Scavenge sweep ended at "+_lastScavengeTime);
1349 if (connection != null)
1350 {
1351 try
1352 {
1353 connection.close();
1354 }
1355 catch (SQLException e)
1356 {
1357 LOG.warn(e);
1358 }
1359 }
1360 }
1361 }
1362
1363
1364
1365
1366
1367 private void scavengeSessions (Set<String> expiredSessionIds, boolean forceDelete)
1368 {
1369 Set<String> remainingIds = new HashSet<String>(expiredSessionIds);
1370 Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
1371 for (int i=0; contexts!=null && i<contexts.length; i++)
1372 {
1373 SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
1374 if (sessionHandler != null)
1375 {
1376 SessionManager manager = sessionHandler.getSessionManager();
1377 if (manager != null && manager instanceof JDBCSessionManager)
1378 {
1379 Set<String> successfullyExpiredIds = ((JDBCSessionManager)manager).expire(expiredSessionIds);
1380 if (successfullyExpiredIds != null)
1381 remainingIds.removeAll(successfullyExpiredIds);
1382 }
1383 }
1384 }
1385
1386
1387 if (!remainingIds.isEmpty() && forceDelete)
1388 {
1389 LOG.info("Forcibly deleting unrecoverable expired sessions {}", remainingIds);
1390 try
1391 {
1392
1393 synchronized (_sessionIds)
1394 {
1395 _sessionIds.removeAll(remainingIds);
1396 }
1397
1398 cleanExpiredSessionIds(remainingIds);
1399 }
1400 catch (Exception e)
1401 {
1402 LOG.warn("Error removing expired session ids", e);
1403 }
1404 }
1405 }
1406
1407
1408
1409
1410 private void cleanExpiredSessionIds (Set<String> expiredIds)
1411 throws Exception
1412 {
1413 if (expiredIds == null || expiredIds.isEmpty())
1414 return;
1415
1416 String[] ids = expiredIds.toArray(new String[expiredIds.size()]);
1417 try (Connection con = getConnection())
1418 {
1419 con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
1420 con.setAutoCommit(false);
1421
1422 int start = 0;
1423 int end = 0;
1424 int blocksize = _deleteBlockSize;
1425 int block = 0;
1426
1427 try (Statement statement = con.createStatement())
1428 {
1429 while (end < ids.length)
1430 {
1431 start = block*blocksize;
1432 if ((ids.length - start) >= blocksize)
1433 end = start + blocksize;
1434 else
1435 end = ids.length;
1436
1437
1438 statement.executeUpdate(fillInClause("delete from "+_sessionIdTableSchema.getTableName()+" where "+_sessionIdTableSchema.getIdColumn()+" in ", ids, start, end));
1439
1440 statement.executeUpdate(fillInClause("delete from "+_sessionTableSchema.getTableName()+" where "+_sessionTableSchema.getIdColumn()+" in ", ids, start, end));
1441 block++;
1442 }
1443 }
1444 catch (Exception e)
1445 {
1446 con.rollback();
1447 throw e;
1448 }
1449 con.commit();
1450 }
1451 }
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461 private String fillInClause (String sql, String[] literals, int start, int end)
1462 throws Exception
1463 {
1464 StringBuffer buff = new StringBuffer();
1465 buff.append(sql);
1466 buff.append("(");
1467 for (int i=start; i<end; i++)
1468 {
1469 buff.append("'"+(literals[i])+"'");
1470 if (i+1<end)
1471 buff.append(",");
1472 }
1473 buff.append(")");
1474 return buff.toString();
1475 }
1476
1477
1478
1479 private void initializeDatabase ()
1480 throws Exception
1481 {
1482 if (_datasource != null)
1483 return;
1484
1485 if (_jndiName!=null)
1486 {
1487 InitialContext ic = new InitialContext();
1488 _datasource = (DataSource)ic.lookup(_jndiName);
1489 }
1490 else if ( _driver != null && _connectionUrl != null )
1491 {
1492 DriverManager.registerDriver(_driver);
1493 }
1494 else if (_driverClassName != null && _connectionUrl != null)
1495 {
1496 Class.forName(_driverClassName);
1497 }
1498 else
1499 throw new IllegalStateException("No database configured for sessions");
1500 }
1501
1502
1503 }