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.ArrayList;
33 import java.util.Collection;
34 import java.util.HashSet;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.Random;
39 import java.util.Timer;
40 import java.util.TimerTask;
41
42 import javax.naming.InitialContext;
43 import javax.servlet.http.HttpServletRequest;
44 import javax.servlet.http.HttpSession;
45 import javax.sql.DataSource;
46
47 import org.eclipse.jetty.server.Handler;
48 import org.eclipse.jetty.server.Server;
49 import org.eclipse.jetty.server.SessionManager;
50 import org.eclipse.jetty.server.handler.ContextHandler;
51 import org.eclipse.jetty.util.log.Logger;
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
66 protected final HashSet<String> _sessionIds = new HashSet<String>();
67 protected Server _server;
68 protected Driver _driver;
69 protected String _driverClassName;
70 protected String _connectionUrl;
71 protected DataSource _datasource;
72 protected String _jndiName;
73 protected String _sessionIdTable = "JettySessionIds";
74 protected String _sessionTable = "JettySessions";
75 protected String _sessionTableRowId = "rowId";
76
77 protected Timer _timer;
78 protected TimerTask _task;
79 protected long _lastScavengeTime;
80 protected long _scavengeIntervalMs = 1000L * 60 * 10;
81 protected String _blobType;
82 protected String _longType;
83
84 protected String _createSessionIdTable;
85 protected String _createSessionTable;
86
87 protected String _selectBoundedExpiredSessions;
88 protected String _deleteOldExpiredSessions;
89
90 protected String _insertId;
91 protected String _deleteId;
92 protected String _queryId;
93
94 protected String _insertSession;
95 protected String _deleteSession;
96 protected String _updateSession;
97 protected String _updateSessionNode;
98 protected String _updateSessionAccessTime;
99
100 protected DatabaseAdaptor _dbAdaptor;
101
102 private String _selectExpiredSessions;
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117 public class DatabaseAdaptor
118 {
119 String _dbName;
120 boolean _isLower;
121 boolean _isUpper;
122
123
124
125 public DatabaseAdaptor (DatabaseMetaData dbMeta)
126 throws SQLException
127 {
128 _dbName = dbMeta.getDatabaseProductName().toLowerCase(Locale.ENGLISH);
129 LOG.debug ("Using database {}",_dbName);
130 _isLower = dbMeta.storesLowerCaseIdentifiers();
131 _isUpper = dbMeta.storesUpperCaseIdentifiers();
132 }
133
134
135
136
137
138
139
140
141 public String convertIdentifier (String identifier)
142 {
143 if (_isLower)
144 return identifier.toLowerCase(Locale.ENGLISH);
145 if (_isUpper)
146 return identifier.toUpperCase(Locale.ENGLISH);
147
148 return identifier;
149 }
150
151 public String getDBName ()
152 {
153 return _dbName;
154 }
155
156 public String getBlobType ()
157 {
158 if (_blobType != null)
159 return _blobType;
160
161 if (_dbName.startsWith("postgres"))
162 return "bytea";
163
164 return "blob";
165 }
166
167 public String getLongType ()
168 {
169 if (_longType != null)
170 return _longType;
171
172 if (_dbName.startsWith("oracle"))
173 return "number(20)";
174
175 return "bigint";
176 }
177
178 public InputStream getBlobInputStream (ResultSet result, String columnName)
179 throws SQLException
180 {
181 if (_dbName.startsWith("postgres"))
182 {
183 byte[] bytes = result.getBytes(columnName);
184 return new ByteArrayInputStream(bytes);
185 }
186
187 Blob blob = result.getBlob(columnName);
188 return blob.getBinaryStream();
189 }
190
191
192
193
194
195 public String getRowIdColumnName ()
196 {
197 if (_dbName != null && _dbName.startsWith("oracle"))
198 return "srowId";
199
200 return "rowId";
201 }
202
203
204 public boolean isEmptyStringNull ()
205 {
206 return (_dbName.startsWith("oracle"));
207 }
208
209 public PreparedStatement getLoadStatement (Connection connection, String rowId, String contextPath, String virtualHosts)
210 throws SQLException
211 {
212 if (contextPath == null || "".equals(contextPath))
213 {
214 if (isEmptyStringNull())
215 {
216 PreparedStatement statement = connection.prepareStatement("select * from "+_sessionTable+
217 " where sessionId = ? and contextPath is null and virtualHost = ?");
218 statement.setString(1, rowId);
219 statement.setString(2, virtualHosts);
220
221 return statement;
222 }
223 }
224
225
226
227 PreparedStatement statement = connection.prepareStatement("select * from "+_sessionTable+
228 " where sessionId = ? and contextPath = ? and virtualHost = ?");
229 statement.setString(1, rowId);
230 statement.setString(2, contextPath);
231 statement.setString(3, virtualHosts);
232
233 return statement;
234 }
235 }
236
237
238
239 public JDBCSessionIdManager(Server server)
240 {
241 super();
242 _server=server;
243 }
244
245 public JDBCSessionIdManager(Server server, Random random)
246 {
247 super(random);
248 _server=server;
249 }
250
251
252
253
254
255
256
257 public void setDriverInfo (String driverClassName, String connectionUrl)
258 {
259 _driverClassName=driverClassName;
260 _connectionUrl=connectionUrl;
261 }
262
263
264
265
266
267
268
269 public void setDriverInfo (Driver driverClass, String connectionUrl)
270 {
271 _driver=driverClass;
272 _connectionUrl=connectionUrl;
273 }
274
275
276 public void setDatasource (DataSource ds)
277 {
278 _datasource = ds;
279 }
280
281 public DataSource getDataSource ()
282 {
283 return _datasource;
284 }
285
286 public String getDriverClassName()
287 {
288 return _driverClassName;
289 }
290
291 public String getConnectionUrl ()
292 {
293 return _connectionUrl;
294 }
295
296 public void setDatasourceName (String jndi)
297 {
298 _jndiName=jndi;
299 }
300
301 public String getDatasourceName ()
302 {
303 return _jndiName;
304 }
305
306 public void setBlobType (String name)
307 {
308 _blobType = name;
309 }
310
311 public String getBlobType ()
312 {
313 return _blobType;
314 }
315
316
317
318 public String getLongType()
319 {
320 return _longType;
321 }
322
323 public void setLongType(String longType)
324 {
325 this._longType = longType;
326 }
327
328 public void setScavengeInterval (long sec)
329 {
330 if (sec<=0)
331 sec=60;
332
333 long old_period=_scavengeIntervalMs;
334 long period=sec*1000L;
335
336 _scavengeIntervalMs=period;
337
338
339
340 long tenPercent = _scavengeIntervalMs/10;
341 if ((System.currentTimeMillis()%2) == 0)
342 _scavengeIntervalMs += tenPercent;
343
344 if (LOG.isDebugEnabled())
345 LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms");
346 if (_timer!=null && (period!=old_period || _task==null))
347 {
348 synchronized (this)
349 {
350 if (_task!=null)
351 _task.cancel();
352 _task = new TimerTask()
353 {
354 @Override
355 public void run()
356 {
357 scavenge();
358 }
359 };
360 _timer.schedule(_task,_scavengeIntervalMs,_scavengeIntervalMs);
361 }
362 }
363 }
364
365 public long getScavengeInterval ()
366 {
367 return _scavengeIntervalMs/1000;
368 }
369
370
371 public void addSession(HttpSession session)
372 {
373 if (session == null)
374 return;
375
376 synchronized (_sessionIds)
377 {
378 String id = ((JDBCSessionManager.Session)session).getClusterId();
379 try
380 {
381 insert(id);
382 _sessionIds.add(id);
383 }
384 catch (Exception e)
385 {
386 LOG.warn("Problem storing session id="+id, e);
387 }
388 }
389 }
390
391 public void removeSession(HttpSession session)
392 {
393 if (session == null)
394 return;
395
396 removeSession(((JDBCSessionManager.Session)session).getClusterId());
397 }
398
399
400
401 public void removeSession (String id)
402 {
403
404 if (id == null)
405 return;
406
407 synchronized (_sessionIds)
408 {
409 if (LOG.isDebugEnabled())
410 LOG.debug("Removing session id="+id);
411 try
412 {
413 _sessionIds.remove(id);
414 delete(id);
415 }
416 catch (Exception e)
417 {
418 LOG.warn("Problem removing session id="+id, e);
419 }
420 }
421
422 }
423
424
425
426
427
428
429
430 public String getClusterId(String nodeId)
431 {
432 int dot=nodeId.lastIndexOf('.');
433 return (dot>0)?nodeId.substring(0,dot):nodeId;
434 }
435
436
437
438
439
440
441
442 public String getNodeId(String clusterId, HttpServletRequest request)
443 {
444 if (_workerName!=null)
445 return clusterId+'.'+_workerName;
446
447 return clusterId;
448 }
449
450
451 public boolean idInUse(String id)
452 {
453 if (id == null)
454 return false;
455
456 String clusterId = getClusterId(id);
457 boolean inUse = false;
458 synchronized (_sessionIds)
459 {
460 inUse = _sessionIds.contains(clusterId);
461 }
462
463
464 if (inUse)
465 return true;
466
467
468 try
469 {
470 return exists(clusterId);
471 }
472 catch (Exception e)
473 {
474 LOG.warn("Problem checking inUse for id="+clusterId, e);
475 return false;
476 }
477 }
478
479
480
481
482
483
484 public void invalidateAll(String id)
485 {
486
487 removeSession(id);
488
489 synchronized (_sessionIds)
490 {
491
492
493 Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
494 for (int i=0; contexts!=null && i<contexts.length; i++)
495 {
496 SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
497 if (sessionHandler != null)
498 {
499 SessionManager manager = sessionHandler.getSessionManager();
500
501 if (manager != null && manager instanceof JDBCSessionManager)
502 {
503 ((JDBCSessionManager)manager).invalidateSession(id);
504 }
505 }
506 }
507 }
508 }
509
510
511
512
513
514
515
516
517 @Override
518 public void doStart()
519 {
520 try
521 {
522 initializeDatabase();
523 prepareTables();
524 cleanExpiredSessions();
525 super.doStart();
526 if (LOG.isDebugEnabled())
527 LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec");
528 _timer=new Timer("JDBCSessionScavenger", true);
529 setScavengeInterval(getScavengeInterval());
530 }
531 catch (Exception e)
532 {
533 LOG.warn("Problem initialising JettySessionIds table", e);
534 }
535 }
536
537
538
539
540 @Override
541 public void doStop ()
542 throws Exception
543 {
544 synchronized(this)
545 {
546 if (_task!=null)
547 _task.cancel();
548 if (_timer!=null)
549 _timer.cancel();
550 _timer=null;
551 }
552 _sessionIds.clear();
553 super.doStop();
554 }
555
556
557
558
559
560
561
562 protected Connection getConnection ()
563 throws SQLException
564 {
565 if (_datasource != null)
566 return _datasource.getConnection();
567 else
568 return DriverManager.getConnection(_connectionUrl);
569 }
570
571
572
573
574
575
576
577
578
579 private void prepareTables()
580 throws SQLException
581 {
582 _createSessionIdTable = "create table "+_sessionIdTable+" (id varchar(120), primary key(id))";
583 _selectBoundedExpiredSessions = "select * from "+_sessionTable+" where expiryTime >= ? and expiryTime <= ?";
584 _selectExpiredSessions = "select * from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?";
585 _deleteOldExpiredSessions = "delete from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?";
586
587 _insertId = "insert into "+_sessionIdTable+" (id) values (?)";
588 _deleteId = "delete from "+_sessionIdTable+" where id = ?";
589 _queryId = "select * from "+_sessionIdTable+" where id = ?";
590
591 Connection connection = null;
592 try
593 {
594
595 connection = getConnection();
596 connection.setAutoCommit(true);
597 DatabaseMetaData metaData = connection.getMetaData();
598 _dbAdaptor = new DatabaseAdaptor(metaData);
599 _sessionTableRowId = _dbAdaptor.getRowIdColumnName();
600
601
602 String tableName = _dbAdaptor.convertIdentifier(_sessionIdTable);
603 ResultSet result = metaData.getTables(null, null, tableName, null);
604 if (!result.next())
605 {
606
607 connection.createStatement().executeUpdate(_createSessionIdTable);
608 }
609
610
611 tableName = _dbAdaptor.convertIdentifier(_sessionTable);
612 result = metaData.getTables(null, null, tableName, null);
613 if (!result.next())
614 {
615
616 String blobType = _dbAdaptor.getBlobType();
617 String longType = _dbAdaptor.getLongType();
618 _createSessionTable = "create table "+_sessionTable+" ("+_sessionTableRowId+" varchar(120), sessionId varchar(120), "+
619 " contextPath varchar(60), virtualHost varchar(60), lastNode varchar(60), accessTime "+longType+", "+
620 " lastAccessTime "+longType+", createTime "+longType+", cookieTime "+longType+", "+
621 " lastSavedTime "+longType+", expiryTime "+longType+", map "+blobType+", primary key("+_sessionTableRowId+"))";
622 connection.createStatement().executeUpdate(_createSessionTable);
623 }
624
625
626 String index1 = "idx_"+_sessionTable+"_expiry";
627 String index2 = "idx_"+_sessionTable+"_session";
628
629 result = metaData.getIndexInfo(null, null, tableName, false, false);
630 boolean index1Exists = false;
631 boolean index2Exists = false;
632 while (result.next())
633 {
634 String idxName = result.getString("INDEX_NAME");
635 if (index1.equalsIgnoreCase(idxName))
636 index1Exists = true;
637 else if (index2.equalsIgnoreCase(idxName))
638 index2Exists = true;
639 }
640 if (!(index1Exists && index2Exists))
641 {
642 Statement statement = connection.createStatement();
643 if (!index1Exists)
644 statement.executeUpdate("create index "+index1+" on "+_sessionTable+" (expiryTime)");
645 if (!index2Exists)
646 statement.executeUpdate("create index "+index2+" on "+_sessionTable+" (sessionId, contextPath)");
647 }
648
649
650 _insertSession = "insert into "+_sessionTable+
651 " ("+_sessionTableRowId+", sessionId, contextPath, virtualHost, lastNode, accessTime, lastAccessTime, createTime, cookieTime, lastSavedTime, expiryTime, map) "+
652 " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
653
654 _deleteSession = "delete from "+_sessionTable+
655 " where "+_sessionTableRowId+" = ?";
656
657 _updateSession = "update "+_sessionTable+
658 " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ?, map = ? where "+_sessionTableRowId+" = ?";
659
660 _updateSessionNode = "update "+_sessionTable+
661 " set lastNode = ? where "+_sessionTableRowId+" = ?";
662
663 _updateSessionAccessTime = "update "+_sessionTable+
664 " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ? where "+_sessionTableRowId+" = ?";
665
666
667 }
668 finally
669 {
670 if (connection != null)
671 connection.close();
672 }
673 }
674
675
676
677
678
679
680
681 private void insert (String id)
682 throws SQLException
683 {
684 Connection connection = null;
685 try
686 {
687 connection = getConnection();
688 connection.setAutoCommit(true);
689 PreparedStatement query = connection.prepareStatement(_queryId);
690 query.setString(1, id);
691 ResultSet result = query.executeQuery();
692
693 if (!result.next())
694 {
695 PreparedStatement statement = connection.prepareStatement(_insertId);
696 statement.setString(1, id);
697 statement.executeUpdate();
698 }
699 }
700 finally
701 {
702 if (connection != null)
703 connection.close();
704 }
705 }
706
707
708
709
710
711
712
713 private void delete (String id)
714 throws SQLException
715 {
716 Connection connection = null;
717 try
718 {
719 connection = getConnection();
720 connection.setAutoCommit(true);
721 PreparedStatement statement = connection.prepareStatement(_deleteId);
722 statement.setString(1, id);
723 statement.executeUpdate();
724 }
725 finally
726 {
727 if (connection != null)
728 connection.close();
729 }
730 }
731
732
733
734
735
736
737
738
739
740 private boolean exists (String id)
741 throws SQLException
742 {
743 Connection connection = null;
744 try
745 {
746 connection = getConnection();
747 connection.setAutoCommit(true);
748 PreparedStatement statement = connection.prepareStatement(_queryId);
749 statement.setString(1, id);
750 ResultSet result = statement.executeQuery();
751 return result.next();
752 }
753 finally
754 {
755 if (connection != null)
756 connection.close();
757 }
758 }
759
760
761
762
763
764
765
766
767
768
769
770
771 private void scavenge ()
772 {
773 Connection connection = null;
774 List<String> expiredSessionIds = new ArrayList<String>();
775 try
776 {
777 if (LOG.isDebugEnabled())
778 LOG.debug("Scavenge sweep started at "+System.currentTimeMillis());
779 if (_lastScavengeTime > 0)
780 {
781 connection = getConnection();
782 connection.setAutoCommit(true);
783
784 PreparedStatement statement = connection.prepareStatement(_selectBoundedExpiredSessions);
785 long lowerBound = (_lastScavengeTime - _scavengeIntervalMs);
786 long upperBound = _lastScavengeTime;
787 if (LOG.isDebugEnabled())
788 LOG.debug (" Searching for sessions expired between "+lowerBound + " and "+upperBound);
789
790 statement.setLong(1, lowerBound);
791 statement.setLong(2, upperBound);
792 ResultSet result = statement.executeQuery();
793 while (result.next())
794 {
795 String sessionId = result.getString("sessionId");
796 expiredSessionIds.add(sessionId);
797 if (LOG.isDebugEnabled()) LOG.debug (" Found expired sessionId="+sessionId);
798 }
799
800
801 Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
802 for (int i=0; contexts!=null && i<contexts.length; i++)
803 {
804
805 SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
806 if (sessionHandler != null)
807 {
808 SessionManager manager = sessionHandler.getSessionManager();
809 if (manager != null && manager instanceof JDBCSessionManager)
810 {
811 ((JDBCSessionManager)manager).expire(expiredSessionIds);
812 }
813 }
814 }
815
816
817 upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
818 if (upperBound > 0)
819 {
820 if (LOG.isDebugEnabled()) LOG.debug("Deleting old expired sessions expired before "+upperBound);
821 statement = connection.prepareStatement(_deleteOldExpiredSessions);
822 statement.setLong(1, upperBound);
823 int rows = statement.executeUpdate();
824 if (LOG.isDebugEnabled()) LOG.debug("Deleted "+rows+" rows");
825 }
826 }
827 }
828 catch (Exception e)
829 {
830 if (isRunning())
831 LOG.warn("Problem selecting expired sessions", e);
832 else
833 LOG.ignore(e);
834 }
835 finally
836 {
837 _lastScavengeTime=System.currentTimeMillis();
838 if (LOG.isDebugEnabled()) LOG.debug("Scavenge sweep ended at "+_lastScavengeTime);
839 if (connection != null)
840 {
841 try
842 {
843 connection.close();
844 }
845 catch (SQLException e)
846 {
847 LOG.warn(e);
848 }
849 }
850 }
851 }
852
853
854
855
856
857 private void cleanExpiredSessions ()
858 throws Exception
859 {
860 Connection connection = null;
861 List<String> expiredSessionIds = new ArrayList<String>();
862 try
863 {
864 connection = getConnection();
865 connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
866 connection.setAutoCommit(false);
867
868 PreparedStatement statement = connection.prepareStatement(_selectExpiredSessions);
869 long now = System.currentTimeMillis();
870 if (LOG.isDebugEnabled()) LOG.debug ("Searching for sessions expired before {}", now);
871
872 statement.setLong(1, now);
873 ResultSet result = statement.executeQuery();
874 while (result.next())
875 {
876 String sessionId = result.getString("sessionId");
877 expiredSessionIds.add(sessionId);
878 if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId={}", sessionId);
879 }
880
881 Statement sessionsTableStatement = null;
882 Statement sessionIdsTableStatement = null;
883
884 if (!expiredSessionIds.isEmpty())
885 {
886 sessionsTableStatement = connection.createStatement();
887 sessionsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionTable+" where sessionId in ", expiredSessionIds));
888 sessionIdsTableStatement = connection.createStatement();
889 sessionIdsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionIdTable+" where id in ", expiredSessionIds));
890 }
891 connection.commit();
892
893 synchronized (_sessionIds)
894 {
895 _sessionIds.removeAll(expiredSessionIds);
896 }
897 }
898 catch (Exception e)
899 {
900 if (connection != null)
901 connection.rollback();
902 throw e;
903 }
904 finally
905 {
906 try
907 {
908 if (connection != null)
909 connection.close();
910 }
911 catch (SQLException e)
912 {
913 LOG.warn(e);
914 }
915 }
916 }
917
918
919
920
921
922
923
924
925
926 private String createCleanExpiredSessionsSql (String sql,Collection<String> expiredSessionIds)
927 throws Exception
928 {
929 StringBuffer buff = new StringBuffer();
930 buff.append(sql);
931 buff.append("(");
932 Iterator<String> itor = expiredSessionIds.iterator();
933 while (itor.hasNext())
934 {
935 buff.append("'"+(itor.next())+"'");
936 if (itor.hasNext())
937 buff.append(",");
938 }
939 buff.append(")");
940
941 if (LOG.isDebugEnabled()) LOG.debug("Cleaning expired sessions with: {}", buff);
942 return buff.toString();
943 }
944
945 private void initializeDatabase ()
946 throws Exception
947 {
948 if (_datasource != null)
949 return;
950
951 if (_jndiName!=null)
952 {
953 InitialContext ic = new InitialContext();
954 _datasource = (DataSource)ic.lookup(_jndiName);
955 }
956 else if ( _driver != null && _connectionUrl != null )
957 {
958 DriverManager.registerDriver(_driver);
959 }
960 else if (_driverClassName != null && _connectionUrl != null)
961 {
962 Class.forName(_driverClassName);
963 }
964 else
965 throw new IllegalStateException("No database configured for sessions");
966 }
967
968
969 }