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