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