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