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 throws Exception
520 {
521 initializeDatabase();
522 prepareTables();
523 cleanExpiredSessions();
524 super.doStart();
525 if (LOG.isDebugEnabled())
526 LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec");
527 _timer=new Timer("JDBCSessionScavenger", true);
528 setScavengeInterval(getScavengeInterval());
529 }
530
531
532
533
534 @Override
535 public void doStop ()
536 throws Exception
537 {
538 synchronized(this)
539 {
540 if (_task!=null)
541 _task.cancel();
542 if (_timer!=null)
543 _timer.cancel();
544 _timer=null;
545 }
546 _sessionIds.clear();
547 super.doStop();
548 }
549
550
551
552
553
554
555
556 protected Connection getConnection ()
557 throws SQLException
558 {
559 if (_datasource != null)
560 return _datasource.getConnection();
561 else
562 return DriverManager.getConnection(_connectionUrl);
563 }
564
565
566
567
568
569
570
571
572
573 private void prepareTables()
574 throws SQLException
575 {
576 _createSessionIdTable = "create table "+_sessionIdTable+" (id varchar(120), primary key(id))";
577 _selectBoundedExpiredSessions = "select * from "+_sessionTable+" where expiryTime >= ? and expiryTime <= ?";
578 _selectExpiredSessions = "select * from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?";
579 _deleteOldExpiredSessions = "delete from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?";
580
581 _insertId = "insert into "+_sessionIdTable+" (id) values (?)";
582 _deleteId = "delete from "+_sessionIdTable+" where id = ?";
583 _queryId = "select * from "+_sessionIdTable+" where id = ?";
584
585 Connection connection = null;
586 try
587 {
588
589 connection = getConnection();
590 connection.setAutoCommit(true);
591 DatabaseMetaData metaData = connection.getMetaData();
592 _dbAdaptor = new DatabaseAdaptor(metaData);
593 _sessionTableRowId = _dbAdaptor.getRowIdColumnName();
594
595
596 String tableName = _dbAdaptor.convertIdentifier(_sessionIdTable);
597 ResultSet result = metaData.getTables(null, null, tableName, null);
598 if (!result.next())
599 {
600
601 connection.createStatement().executeUpdate(_createSessionIdTable);
602 }
603
604
605 tableName = _dbAdaptor.convertIdentifier(_sessionTable);
606 result = metaData.getTables(null, null, tableName, null);
607 if (!result.next())
608 {
609
610 String blobType = _dbAdaptor.getBlobType();
611 String longType = _dbAdaptor.getLongType();
612 _createSessionTable = "create table "+_sessionTable+" ("+_sessionTableRowId+" varchar(120), sessionId varchar(120), "+
613 " contextPath varchar(60), virtualHost varchar(60), lastNode varchar(60), accessTime "+longType+", "+
614 " lastAccessTime "+longType+", createTime "+longType+", cookieTime "+longType+", "+
615 " lastSavedTime "+longType+", expiryTime "+longType+", map "+blobType+", primary key("+_sessionTableRowId+"))";
616 connection.createStatement().executeUpdate(_createSessionTable);
617 }
618
619
620 String index1 = "idx_"+_sessionTable+"_expiry";
621 String index2 = "idx_"+_sessionTable+"_session";
622
623 result = metaData.getIndexInfo(null, null, tableName, false, false);
624 boolean index1Exists = false;
625 boolean index2Exists = false;
626 while (result.next())
627 {
628 String idxName = result.getString("INDEX_NAME");
629 if (index1.equalsIgnoreCase(idxName))
630 index1Exists = true;
631 else if (index2.equalsIgnoreCase(idxName))
632 index2Exists = true;
633 }
634 if (!(index1Exists && index2Exists))
635 {
636 Statement statement = connection.createStatement();
637 if (!index1Exists)
638 statement.executeUpdate("create index "+index1+" on "+_sessionTable+" (expiryTime)");
639 if (!index2Exists)
640 statement.executeUpdate("create index "+index2+" on "+_sessionTable+" (sessionId, contextPath)");
641 }
642
643
644 _insertSession = "insert into "+_sessionTable+
645 " ("+_sessionTableRowId+", sessionId, contextPath, virtualHost, lastNode, accessTime, lastAccessTime, createTime, cookieTime, lastSavedTime, expiryTime, map) "+
646 " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
647
648 _deleteSession = "delete from "+_sessionTable+
649 " where "+_sessionTableRowId+" = ?";
650
651 _updateSession = "update "+_sessionTable+
652 " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ?, map = ? where "+_sessionTableRowId+" = ?";
653
654 _updateSessionNode = "update "+_sessionTable+
655 " set lastNode = ? where "+_sessionTableRowId+" = ?";
656
657 _updateSessionAccessTime = "update "+_sessionTable+
658 " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ? where "+_sessionTableRowId+" = ?";
659
660
661 }
662 finally
663 {
664 if (connection != null)
665 connection.close();
666 }
667 }
668
669
670
671
672
673
674
675 private void insert (String id)
676 throws SQLException
677 {
678 Connection connection = null;
679 try
680 {
681 connection = getConnection();
682 connection.setAutoCommit(true);
683 PreparedStatement query = connection.prepareStatement(_queryId);
684 query.setString(1, id);
685 ResultSet result = query.executeQuery();
686
687 if (!result.next())
688 {
689 PreparedStatement statement = connection.prepareStatement(_insertId);
690 statement.setString(1, id);
691 statement.executeUpdate();
692 }
693 }
694 finally
695 {
696 if (connection != null)
697 connection.close();
698 }
699 }
700
701
702
703
704
705
706
707 private void delete (String id)
708 throws SQLException
709 {
710 Connection connection = null;
711 try
712 {
713 connection = getConnection();
714 connection.setAutoCommit(true);
715 PreparedStatement statement = connection.prepareStatement(_deleteId);
716 statement.setString(1, id);
717 statement.executeUpdate();
718 }
719 finally
720 {
721 if (connection != null)
722 connection.close();
723 }
724 }
725
726
727
728
729
730
731
732
733
734 private boolean exists (String id)
735 throws SQLException
736 {
737 Connection connection = null;
738 try
739 {
740 connection = getConnection();
741 connection.setAutoCommit(true);
742 PreparedStatement statement = connection.prepareStatement(_queryId);
743 statement.setString(1, id);
744 ResultSet result = statement.executeQuery();
745 return result.next();
746 }
747 finally
748 {
749 if (connection != null)
750 connection.close();
751 }
752 }
753
754
755
756
757
758
759
760
761
762
763
764
765 private void scavenge ()
766 {
767 Connection connection = null;
768 List<String> expiredSessionIds = new ArrayList<String>();
769 try
770 {
771 if (LOG.isDebugEnabled())
772 LOG.debug("Scavenge sweep started at "+System.currentTimeMillis());
773 if (_lastScavengeTime > 0)
774 {
775 connection = getConnection();
776 connection.setAutoCommit(true);
777
778 PreparedStatement statement = connection.prepareStatement(_selectBoundedExpiredSessions);
779 long lowerBound = (_lastScavengeTime - _scavengeIntervalMs);
780 long upperBound = _lastScavengeTime;
781 if (LOG.isDebugEnabled())
782 LOG.debug (" Searching for sessions expired between "+lowerBound + " and "+upperBound);
783
784 statement.setLong(1, lowerBound);
785 statement.setLong(2, upperBound);
786 ResultSet result = statement.executeQuery();
787 while (result.next())
788 {
789 String sessionId = result.getString("sessionId");
790 expiredSessionIds.add(sessionId);
791 if (LOG.isDebugEnabled()) LOG.debug (" Found expired sessionId="+sessionId);
792 }
793
794
795 Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
796 for (int i=0; contexts!=null && i<contexts.length; i++)
797 {
798
799 SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
800 if (sessionHandler != null)
801 {
802 SessionManager manager = sessionHandler.getSessionManager();
803 if (manager != null && manager instanceof JDBCSessionManager)
804 {
805 ((JDBCSessionManager)manager).expire(expiredSessionIds);
806 }
807 }
808 }
809
810
811 upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
812 if (upperBound > 0)
813 {
814 if (LOG.isDebugEnabled()) LOG.debug("Deleting old expired sessions expired before "+upperBound);
815 statement = connection.prepareStatement(_deleteOldExpiredSessions);
816 statement.setLong(1, upperBound);
817 int rows = statement.executeUpdate();
818 if (LOG.isDebugEnabled()) LOG.debug("Deleted "+rows+" rows of old sessions expired before "+upperBound);
819 }
820 }
821 }
822 catch (Exception e)
823 {
824 if (isRunning())
825 LOG.warn("Problem selecting expired sessions", e);
826 else
827 LOG.ignore(e);
828 }
829 finally
830 {
831 _lastScavengeTime=System.currentTimeMillis();
832 if (LOG.isDebugEnabled()) LOG.debug("Scavenge sweep ended at "+_lastScavengeTime);
833 if (connection != null)
834 {
835 try
836 {
837 connection.close();
838 }
839 catch (SQLException e)
840 {
841 LOG.warn(e);
842 }
843 }
844 }
845 }
846
847
848
849
850
851 private void cleanExpiredSessions ()
852 throws Exception
853 {
854 Connection connection = null;
855 List<String> expiredSessionIds = new ArrayList<String>();
856 try
857 {
858 connection = getConnection();
859 connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
860 connection.setAutoCommit(false);
861
862 PreparedStatement statement = connection.prepareStatement(_selectExpiredSessions);
863 long now = System.currentTimeMillis();
864 if (LOG.isDebugEnabled()) LOG.debug ("Searching for sessions expired before {}", now);
865
866 statement.setLong(1, now);
867 ResultSet result = statement.executeQuery();
868 while (result.next())
869 {
870 String sessionId = result.getString("sessionId");
871 expiredSessionIds.add(sessionId);
872 if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId={}", sessionId);
873 }
874
875 Statement sessionsTableStatement = null;
876 Statement sessionIdsTableStatement = null;
877
878 if (!expiredSessionIds.isEmpty())
879 {
880 sessionsTableStatement = connection.createStatement();
881 sessionsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionTable+" where sessionId in ", expiredSessionIds));
882 sessionIdsTableStatement = connection.createStatement();
883 sessionIdsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionIdTable+" where id in ", expiredSessionIds));
884 }
885 connection.commit();
886
887 synchronized (_sessionIds)
888 {
889 _sessionIds.removeAll(expiredSessionIds);
890 }
891 }
892 catch (Exception e)
893 {
894 if (connection != null)
895 connection.rollback();
896 throw e;
897 }
898 finally
899 {
900 try
901 {
902 if (connection != null)
903 connection.close();
904 }
905 catch (SQLException e)
906 {
907 LOG.warn(e);
908 }
909 }
910 }
911
912
913
914
915
916
917
918
919
920 private String createCleanExpiredSessionsSql (String sql,Collection<String> expiredSessionIds)
921 throws Exception
922 {
923 StringBuffer buff = new StringBuffer();
924 buff.append(sql);
925 buff.append("(");
926 Iterator<String> itor = expiredSessionIds.iterator();
927 while (itor.hasNext())
928 {
929 buff.append("'"+(itor.next())+"'");
930 if (itor.hasNext())
931 buff.append(",");
932 }
933 buff.append(")");
934
935 if (LOG.isDebugEnabled()) LOG.debug("Cleaning expired sessions with: {}", buff);
936 return buff.toString();
937 }
938
939 private void initializeDatabase ()
940 throws Exception
941 {
942 if (_datasource != null)
943 return;
944
945 if (_jndiName!=null)
946 {
947 InitialContext ic = new InitialContext();
948 _datasource = (DataSource)ic.lookup(_jndiName);
949 }
950 else if ( _driver != null && _connectionUrl != null )
951 {
952 DriverManager.registerDriver(_driver);
953 }
954 else if (_driverClassName != null && _connectionUrl != null)
955 {
956 Class.forName(_driverClassName);
957 }
958 else
959 throw new IllegalStateException("No database configured for sessions");
960 }
961
962
963 }