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