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