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