Mercurial Hosting > luan
comparison src/org/eclipse/jetty/server/session/JDBCSessionIdManager.java @ 802:3428c60d7cfc
replace jetty jars with source
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Wed, 07 Sep 2016 21:15:48 -0600 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
801:6a21393191c1 | 802:3428c60d7cfc |
---|---|
1 // | |
2 // ======================================================================== | |
3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. | |
4 // ------------------------------------------------------------------------ | |
5 // All rights reserved. This program and the accompanying materials | |
6 // are made available under the terms of the Eclipse Public License v1.0 | |
7 // and Apache License v2.0 which accompanies this distribution. | |
8 // | |
9 // The Eclipse Public License is available at | |
10 // http://www.eclipse.org/legal/epl-v10.html | |
11 // | |
12 // The Apache License v2.0 is available at | |
13 // http://www.opensource.org/licenses/apache2.0.php | |
14 // | |
15 // You may elect to redistribute this code under either of these licenses. | |
16 // ======================================================================== | |
17 // | |
18 | |
19 package org.eclipse.jetty.server.session; | |
20 | |
21 import java.io.ByteArrayInputStream; | |
22 import java.io.InputStream; | |
23 import java.sql.Blob; | |
24 import java.sql.Connection; | |
25 import java.sql.DatabaseMetaData; | |
26 import java.sql.Driver; | |
27 import java.sql.DriverManager; | |
28 import java.sql.PreparedStatement; | |
29 import java.sql.ResultSet; | |
30 import java.sql.SQLException; | |
31 import java.sql.Statement; | |
32 import java.util.ArrayList; | |
33 import java.util.Collection; | |
34 import java.util.HashSet; | |
35 import java.util.Iterator; | |
36 import java.util.List; | |
37 import java.util.Locale; | |
38 import java.util.Random; | |
39 import java.util.Timer; | |
40 import java.util.TimerTask; | |
41 | |
42 import javax.naming.InitialContext; | |
43 import javax.servlet.http.HttpServletRequest; | |
44 import javax.servlet.http.HttpSession; | |
45 import javax.sql.DataSource; | |
46 | |
47 import org.eclipse.jetty.server.Handler; | |
48 import org.eclipse.jetty.server.Server; | |
49 import org.eclipse.jetty.server.SessionManager; | |
50 import org.eclipse.jetty.server.handler.ContextHandler; | |
51 import org.eclipse.jetty.util.log.Logger; | |
52 | |
53 | |
54 | |
55 /** | |
56 * JDBCSessionIdManager | |
57 * | |
58 * SessionIdManager implementation that uses a database to store in-use session ids, | |
59 * to support distributed sessions. | |
60 * | |
61 */ | |
62 public class JDBCSessionIdManager extends AbstractSessionIdManager | |
63 { | |
64 final static Logger LOG = SessionHandler.LOG; | |
65 | |
66 protected final HashSet<String> _sessionIds = new HashSet<String>(); | |
67 protected Server _server; | |
68 protected Driver _driver; | |
69 protected String _driverClassName; | |
70 protected String _connectionUrl; | |
71 protected DataSource _datasource; | |
72 protected String _jndiName; | |
73 protected String _sessionIdTable = "JettySessionIds"; | |
74 protected String _sessionTable = "JettySessions"; | |
75 protected String _sessionTableRowId = "rowId"; | |
76 | |
77 protected Timer _timer; //scavenge timer | |
78 protected TimerTask _task; //scavenge task | |
79 protected long _lastScavengeTime; | |
80 protected long _scavengeIntervalMs = 1000L * 60 * 10; //10mins | |
81 protected String _blobType; //if not set, is deduced from the type of the database at runtime | |
82 protected String _longType; //if not set, is deduced from the type of the database at runtime | |
83 | |
84 protected String _createSessionIdTable; | |
85 protected String _createSessionTable; | |
86 | |
87 protected String _selectBoundedExpiredSessions; | |
88 protected String _deleteOldExpiredSessions; | |
89 | |
90 protected String _insertId; | |
91 protected String _deleteId; | |
92 protected String _queryId; | |
93 | |
94 protected String _insertSession; | |
95 protected String _deleteSession; | |
96 protected String _updateSession; | |
97 protected String _updateSessionNode; | |
98 protected String _updateSessionAccessTime; | |
99 | |
100 protected DatabaseAdaptor _dbAdaptor; | |
101 | |
102 private String _selectExpiredSessions; | |
103 | |
104 | |
105 /** | |
106 * DatabaseAdaptor | |
107 * | |
108 * Handles differences between databases. | |
109 * | |
110 * Postgres uses the getBytes and setBinaryStream methods to access | |
111 * a "bytea" datatype, which can be up to 1Gb of binary data. MySQL | |
112 * is happy to use the "blob" type and getBlob() methods instead. | |
113 * | |
114 * TODO if the differences become more major it would be worthwhile | |
115 * refactoring this class. | |
116 */ | |
117 public class DatabaseAdaptor | |
118 { | |
119 String _dbName; | |
120 boolean _isLower; | |
121 boolean _isUpper; | |
122 | |
123 | |
124 | |
125 public DatabaseAdaptor (DatabaseMetaData dbMeta) | |
126 throws SQLException | |
127 { | |
128 _dbName = dbMeta.getDatabaseProductName().toLowerCase(Locale.ENGLISH); | |
129 LOG.debug ("Using database {}",_dbName); | |
130 _isLower = dbMeta.storesLowerCaseIdentifiers(); | |
131 _isUpper = dbMeta.storesUpperCaseIdentifiers(); | |
132 } | |
133 | |
134 /** | |
135 * Convert a camel case identifier into either upper or lower | |
136 * depending on the way the db stores identifiers. | |
137 * | |
138 * @param identifier | |
139 * @return the converted identifier | |
140 */ | |
141 public String convertIdentifier (String identifier) | |
142 { | |
143 if (_isLower) | |
144 return identifier.toLowerCase(Locale.ENGLISH); | |
145 if (_isUpper) | |
146 return identifier.toUpperCase(Locale.ENGLISH); | |
147 | |
148 return identifier; | |
149 } | |
150 | |
151 public String getDBName () | |
152 { | |
153 return _dbName; | |
154 } | |
155 | |
156 public String getBlobType () | |
157 { | |
158 if (_blobType != null) | |
159 return _blobType; | |
160 | |
161 if (_dbName.startsWith("postgres")) | |
162 return "bytea"; | |
163 | |
164 return "blob"; | |
165 } | |
166 | |
167 public String getLongType () | |
168 { | |
169 if (_longType != null) | |
170 return _longType; | |
171 | |
172 if (_dbName.startsWith("oracle")) | |
173 return "number(20)"; | |
174 | |
175 return "bigint"; | |
176 } | |
177 | |
178 public InputStream getBlobInputStream (ResultSet result, String columnName) | |
179 throws SQLException | |
180 { | |
181 if (_dbName.startsWith("postgres")) | |
182 { | |
183 byte[] bytes = result.getBytes(columnName); | |
184 return new ByteArrayInputStream(bytes); | |
185 } | |
186 | |
187 Blob blob = result.getBlob(columnName); | |
188 return blob.getBinaryStream(); | |
189 } | |
190 | |
191 /** | |
192 * rowId is a reserved word for Oracle, so change the name of this column | |
193 * @return | |
194 */ | |
195 public String getRowIdColumnName () | |
196 { | |
197 if (_dbName != null && _dbName.startsWith("oracle")) | |
198 return "srowId"; | |
199 | |
200 return "rowId"; | |
201 } | |
202 | |
203 | |
204 public boolean isEmptyStringNull () | |
205 { | |
206 return (_dbName.startsWith("oracle")); | |
207 } | |
208 | |
209 public PreparedStatement getLoadStatement (Connection connection, String rowId, String contextPath, String virtualHosts) | |
210 throws SQLException | |
211 { | |
212 if (contextPath == null || "".equals(contextPath)) | |
213 { | |
214 if (isEmptyStringNull()) | |
215 { | |
216 PreparedStatement statement = connection.prepareStatement("select * from "+_sessionTable+ | |
217 " where sessionId = ? and contextPath is null and virtualHost = ?"); | |
218 statement.setString(1, rowId); | |
219 statement.setString(2, virtualHosts); | |
220 | |
221 return statement; | |
222 } | |
223 } | |
224 | |
225 | |
226 | |
227 PreparedStatement statement = connection.prepareStatement("select * from "+_sessionTable+ | |
228 " where sessionId = ? and contextPath = ? and virtualHost = ?"); | |
229 statement.setString(1, rowId); | |
230 statement.setString(2, contextPath); | |
231 statement.setString(3, virtualHosts); | |
232 | |
233 return statement; | |
234 } | |
235 } | |
236 | |
237 | |
238 | |
239 public JDBCSessionIdManager(Server server) | |
240 { | |
241 super(); | |
242 _server=server; | |
243 } | |
244 | |
245 public JDBCSessionIdManager(Server server, Random random) | |
246 { | |
247 super(random); | |
248 _server=server; | |
249 } | |
250 | |
251 /** | |
252 * Configure jdbc connection information via a jdbc Driver | |
253 * | |
254 * @param driverClassName | |
255 * @param connectionUrl | |
256 */ | |
257 public void setDriverInfo (String driverClassName, String connectionUrl) | |
258 { | |
259 _driverClassName=driverClassName; | |
260 _connectionUrl=connectionUrl; | |
261 } | |
262 | |
263 /** | |
264 * Configure jdbc connection information via a jdbc Driver | |
265 * | |
266 * @param driverClass | |
267 * @param connectionUrl | |
268 */ | |
269 public void setDriverInfo (Driver driverClass, String connectionUrl) | |
270 { | |
271 _driver=driverClass; | |
272 _connectionUrl=connectionUrl; | |
273 } | |
274 | |
275 | |
276 public void setDatasource (DataSource ds) | |
277 { | |
278 _datasource = ds; | |
279 } | |
280 | |
281 public DataSource getDataSource () | |
282 { | |
283 return _datasource; | |
284 } | |
285 | |
286 public String getDriverClassName() | |
287 { | |
288 return _driverClassName; | |
289 } | |
290 | |
291 public String getConnectionUrl () | |
292 { | |
293 return _connectionUrl; | |
294 } | |
295 | |
296 public void setDatasourceName (String jndi) | |
297 { | |
298 _jndiName=jndi; | |
299 } | |
300 | |
301 public String getDatasourceName () | |
302 { | |
303 return _jndiName; | |
304 } | |
305 | |
306 public void setBlobType (String name) | |
307 { | |
308 _blobType = name; | |
309 } | |
310 | |
311 public String getBlobType () | |
312 { | |
313 return _blobType; | |
314 } | |
315 | |
316 | |
317 | |
318 public String getLongType() | |
319 { | |
320 return _longType; | |
321 } | |
322 | |
323 public void setLongType(String longType) | |
324 { | |
325 this._longType = longType; | |
326 } | |
327 | |
328 public void setScavengeInterval (long sec) | |
329 { | |
330 if (sec<=0) | |
331 sec=60; | |
332 | |
333 long old_period=_scavengeIntervalMs; | |
334 long period=sec*1000L; | |
335 | |
336 _scavengeIntervalMs=period; | |
337 | |
338 //add a bit of variability into the scavenge time so that not all | |
339 //nodes with the same scavenge time sync up | |
340 long tenPercent = _scavengeIntervalMs/10; | |
341 if ((System.currentTimeMillis()%2) == 0) | |
342 _scavengeIntervalMs += tenPercent; | |
343 | |
344 if (LOG.isDebugEnabled()) | |
345 LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms"); | |
346 if (_timer!=null && (period!=old_period || _task==null)) | |
347 { | |
348 synchronized (this) | |
349 { | |
350 if (_task!=null) | |
351 _task.cancel(); | |
352 _task = new TimerTask() | |
353 { | |
354 @Override | |
355 public void run() | |
356 { | |
357 scavenge(); | |
358 } | |
359 }; | |
360 _timer.schedule(_task,_scavengeIntervalMs,_scavengeIntervalMs); | |
361 } | |
362 } | |
363 } | |
364 | |
365 public long getScavengeInterval () | |
366 { | |
367 return _scavengeIntervalMs/1000; | |
368 } | |
369 | |
370 | |
371 public void addSession(HttpSession session) | |
372 { | |
373 if (session == null) | |
374 return; | |
375 | |
376 synchronized (_sessionIds) | |
377 { | |
378 String id = ((JDBCSessionManager.Session)session).getClusterId(); | |
379 try | |
380 { | |
381 insert(id); | |
382 _sessionIds.add(id); | |
383 } | |
384 catch (Exception e) | |
385 { | |
386 LOG.warn("Problem storing session id="+id, e); | |
387 } | |
388 } | |
389 } | |
390 | |
391 public void removeSession(HttpSession session) | |
392 { | |
393 if (session == null) | |
394 return; | |
395 | |
396 removeSession(((JDBCSessionManager.Session)session).getClusterId()); | |
397 } | |
398 | |
399 | |
400 | |
401 public void removeSession (String id) | |
402 { | |
403 | |
404 if (id == null) | |
405 return; | |
406 | |
407 synchronized (_sessionIds) | |
408 { | |
409 if (LOG.isDebugEnabled()) | |
410 LOG.debug("Removing session id="+id); | |
411 try | |
412 { | |
413 _sessionIds.remove(id); | |
414 delete(id); | |
415 } | |
416 catch (Exception e) | |
417 { | |
418 LOG.warn("Problem removing session id="+id, e); | |
419 } | |
420 } | |
421 | |
422 } | |
423 | |
424 | |
425 /** | |
426 * Get the session id without any node identifier suffix. | |
427 * | |
428 * @see org.eclipse.jetty.server.SessionIdManager#getClusterId(java.lang.String) | |
429 */ | |
430 public String getClusterId(String nodeId) | |
431 { | |
432 int dot=nodeId.lastIndexOf('.'); | |
433 return (dot>0)?nodeId.substring(0,dot):nodeId; | |
434 } | |
435 | |
436 | |
437 /** | |
438 * Get the session id, including this node's id as a suffix. | |
439 * | |
440 * @see org.eclipse.jetty.server.SessionIdManager#getNodeId(java.lang.String, javax.servlet.http.HttpServletRequest) | |
441 */ | |
442 public String getNodeId(String clusterId, HttpServletRequest request) | |
443 { | |
444 if (_workerName!=null) | |
445 return clusterId+'.'+_workerName; | |
446 | |
447 return clusterId; | |
448 } | |
449 | |
450 | |
451 public boolean idInUse(String id) | |
452 { | |
453 if (id == null) | |
454 return false; | |
455 | |
456 String clusterId = getClusterId(id); | |
457 boolean inUse = false; | |
458 synchronized (_sessionIds) | |
459 { | |
460 inUse = _sessionIds.contains(clusterId); | |
461 } | |
462 | |
463 | |
464 if (inUse) | |
465 return true; //optimisation - if this session is one we've been managing, we can check locally | |
466 | |
467 //otherwise, we need to go to the database to check | |
468 try | |
469 { | |
470 return exists(clusterId); | |
471 } | |
472 catch (Exception e) | |
473 { | |
474 LOG.warn("Problem checking inUse for id="+clusterId, e); | |
475 return false; | |
476 } | |
477 } | |
478 | |
479 /** | |
480 * Invalidate the session matching the id on all contexts. | |
481 * | |
482 * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String) | |
483 */ | |
484 public void invalidateAll(String id) | |
485 { | |
486 //take the id out of the list of known sessionids for this node | |
487 removeSession(id); | |
488 | |
489 synchronized (_sessionIds) | |
490 { | |
491 //tell all contexts that may have a session object with this id to | |
492 //get rid of them | |
493 Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class); | |
494 for (int i=0; contexts!=null && i<contexts.length; i++) | |
495 { | |
496 SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class); | |
497 if (sessionHandler != null) | |
498 { | |
499 SessionManager manager = sessionHandler.getSessionManager(); | |
500 | |
501 if (manager != null && manager instanceof JDBCSessionManager) | |
502 { | |
503 ((JDBCSessionManager)manager).invalidateSession(id); | |
504 } | |
505 } | |
506 } | |
507 } | |
508 } | |
509 | |
510 | |
511 /** | |
512 * Start up the id manager. | |
513 * | |
514 * Makes necessary database tables and starts a Session | |
515 * scavenger thread. | |
516 */ | |
517 @Override | |
518 public void doStart() | |
519 throws Exception | |
520 { | |
521 initializeDatabase(); | |
522 prepareTables(); | |
523 cleanExpiredSessions(); | |
524 super.doStart(); | |
525 if (LOG.isDebugEnabled()) | |
526 LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec"); | |
527 _timer=new Timer("JDBCSessionScavenger", true); | |
528 setScavengeInterval(getScavengeInterval()); | |
529 } | |
530 | |
531 /** | |
532 * Stop the scavenger. | |
533 */ | |
534 @Override | |
535 public void doStop () | |
536 throws Exception | |
537 { | |
538 synchronized(this) | |
539 { | |
540 if (_task!=null) | |
541 _task.cancel(); | |
542 if (_timer!=null) | |
543 _timer.cancel(); | |
544 _timer=null; | |
545 } | |
546 _sessionIds.clear(); | |
547 super.doStop(); | |
548 } | |
549 | |
550 /** | |
551 * Get a connection from the driver or datasource. | |
552 * | |
553 * @return the connection for the datasource | |
554 * @throws SQLException | |
555 */ | |
556 protected Connection getConnection () | |
557 throws SQLException | |
558 { | |
559 if (_datasource != null) | |
560 return _datasource.getConnection(); | |
561 else | |
562 return DriverManager.getConnection(_connectionUrl); | |
563 } | |
564 | |
565 | |
566 | |
567 | |
568 | |
569 /** | |
570 * Set up the tables in the database | |
571 * @throws SQLException | |
572 */ | |
573 private void prepareTables() | |
574 throws SQLException | |
575 { | |
576 _createSessionIdTable = "create table "+_sessionIdTable+" (id varchar(120), primary key(id))"; | |
577 _selectBoundedExpiredSessions = "select * from "+_sessionTable+" where expiryTime >= ? and expiryTime <= ?"; | |
578 _selectExpiredSessions = "select * from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?"; | |
579 _deleteOldExpiredSessions = "delete from "+_sessionTable+" where expiryTime >0 and expiryTime <= ?"; | |
580 | |
581 _insertId = "insert into "+_sessionIdTable+" (id) values (?)"; | |
582 _deleteId = "delete from "+_sessionIdTable+" where id = ?"; | |
583 _queryId = "select * from "+_sessionIdTable+" where id = ?"; | |
584 | |
585 Connection connection = null; | |
586 try | |
587 { | |
588 //make the id table | |
589 connection = getConnection(); | |
590 connection.setAutoCommit(true); | |
591 DatabaseMetaData metaData = connection.getMetaData(); | |
592 _dbAdaptor = new DatabaseAdaptor(metaData); | |
593 _sessionTableRowId = _dbAdaptor.getRowIdColumnName(); | |
594 | |
595 //checking for table existence is case-sensitive, but table creation is not | |
596 String tableName = _dbAdaptor.convertIdentifier(_sessionIdTable); | |
597 ResultSet result = metaData.getTables(null, null, tableName, null); | |
598 if (!result.next()) | |
599 { | |
600 //table does not exist, so create it | |
601 connection.createStatement().executeUpdate(_createSessionIdTable); | |
602 } | |
603 | |
604 //make the session table if necessary | |
605 tableName = _dbAdaptor.convertIdentifier(_sessionTable); | |
606 result = metaData.getTables(null, null, tableName, null); | |
607 if (!result.next()) | |
608 { | |
609 //table does not exist, so create it | |
610 String blobType = _dbAdaptor.getBlobType(); | |
611 String longType = _dbAdaptor.getLongType(); | |
612 _createSessionTable = "create table "+_sessionTable+" ("+_sessionTableRowId+" varchar(120), sessionId varchar(120), "+ | |
613 " contextPath varchar(60), virtualHost varchar(60), lastNode varchar(60), accessTime "+longType+", "+ | |
614 " lastAccessTime "+longType+", createTime "+longType+", cookieTime "+longType+", "+ | |
615 " lastSavedTime "+longType+", expiryTime "+longType+", map "+blobType+", primary key("+_sessionTableRowId+"))"; | |
616 connection.createStatement().executeUpdate(_createSessionTable); | |
617 } | |
618 | |
619 //make some indexes on the JettySessions table | |
620 String index1 = "idx_"+_sessionTable+"_expiry"; | |
621 String index2 = "idx_"+_sessionTable+"_session"; | |
622 | |
623 result = metaData.getIndexInfo(null, null, tableName, false, false); | |
624 boolean index1Exists = false; | |
625 boolean index2Exists = false; | |
626 while (result.next()) | |
627 { | |
628 String idxName = result.getString("INDEX_NAME"); | |
629 if (index1.equalsIgnoreCase(idxName)) | |
630 index1Exists = true; | |
631 else if (index2.equalsIgnoreCase(idxName)) | |
632 index2Exists = true; | |
633 } | |
634 if (!(index1Exists && index2Exists)) | |
635 { | |
636 Statement statement = connection.createStatement(); | |
637 try | |
638 { | |
639 if (!index1Exists) | |
640 statement.executeUpdate("create index "+index1+" on "+_sessionTable+" (expiryTime)"); | |
641 if (!index2Exists) | |
642 statement.executeUpdate("create index "+index2+" on "+_sessionTable+" (sessionId, contextPath)"); | |
643 } | |
644 finally | |
645 { | |
646 if (statement!=null) | |
647 { | |
648 try { statement.close(); } | |
649 catch(Exception e) { LOG.warn(e); } | |
650 } | |
651 } | |
652 } | |
653 | |
654 //set up some strings representing the statements for session manipulation | |
655 _insertSession = "insert into "+_sessionTable+ | |
656 " ("+_sessionTableRowId+", sessionId, contextPath, virtualHost, lastNode, accessTime, lastAccessTime, createTime, cookieTime, lastSavedTime, expiryTime, map) "+ | |
657 " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; | |
658 | |
659 _deleteSession = "delete from "+_sessionTable+ | |
660 " where "+_sessionTableRowId+" = ?"; | |
661 | |
662 _updateSession = "update "+_sessionTable+ | |
663 " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ?, map = ? where "+_sessionTableRowId+" = ?"; | |
664 | |
665 _updateSessionNode = "update "+_sessionTable+ | |
666 " set lastNode = ? where "+_sessionTableRowId+" = ?"; | |
667 | |
668 _updateSessionAccessTime = "update "+_sessionTable+ | |
669 " set lastNode = ?, accessTime = ?, lastAccessTime = ?, lastSavedTime = ?, expiryTime = ? where "+_sessionTableRowId+" = ?"; | |
670 | |
671 | |
672 } | |
673 finally | |
674 { | |
675 if (connection != null) | |
676 connection.close(); | |
677 } | |
678 } | |
679 | |
680 /** | |
681 * Insert a new used session id into the table. | |
682 * | |
683 * @param id | |
684 * @throws SQLException | |
685 */ | |
686 private void insert (String id) | |
687 throws SQLException | |
688 { | |
689 Connection connection = null; | |
690 PreparedStatement statement = null; | |
691 PreparedStatement query = null; | |
692 try | |
693 { | |
694 connection = getConnection(); | |
695 connection.setAutoCommit(true); | |
696 query = connection.prepareStatement(_queryId); | |
697 query.setString(1, id); | |
698 ResultSet result = query.executeQuery(); | |
699 //only insert the id if it isn't in the db already | |
700 if (!result.next()) | |
701 { | |
702 statement = connection.prepareStatement(_insertId); | |
703 statement.setString(1, id); | |
704 statement.executeUpdate(); | |
705 } | |
706 } | |
707 finally | |
708 { | |
709 if (query!=null) | |
710 { | |
711 try { query.close(); } | |
712 catch(Exception e) { LOG.warn(e); } | |
713 } | |
714 | |
715 if (statement!=null) | |
716 { | |
717 try { statement.close(); } | |
718 catch(Exception e) { LOG.warn(e); } | |
719 } | |
720 | |
721 if (connection != null) | |
722 connection.close(); | |
723 } | |
724 } | |
725 | |
726 /** | |
727 * Remove a session id from the table. | |
728 * | |
729 * @param id | |
730 * @throws SQLException | |
731 */ | |
732 private void delete (String id) | |
733 throws SQLException | |
734 { | |
735 Connection connection = null; | |
736 PreparedStatement statement = null; | |
737 try | |
738 { | |
739 connection = getConnection(); | |
740 connection.setAutoCommit(true); | |
741 statement = connection.prepareStatement(_deleteId); | |
742 statement.setString(1, id); | |
743 statement.executeUpdate(); | |
744 } | |
745 finally | |
746 { | |
747 if (statement!=null) | |
748 { | |
749 try { statement.close(); } | |
750 catch(Exception e) { LOG.warn(e); } | |
751 } | |
752 | |
753 if (connection != null) | |
754 connection.close(); | |
755 } | |
756 } | |
757 | |
758 | |
759 /** | |
760 * Check if a session id exists. | |
761 * | |
762 * @param id | |
763 * @return | |
764 * @throws SQLException | |
765 */ | |
766 private boolean exists (String id) | |
767 throws SQLException | |
768 { | |
769 Connection connection = null; | |
770 PreparedStatement statement = null; | |
771 try | |
772 { | |
773 connection = getConnection(); | |
774 connection.setAutoCommit(true); | |
775 statement = connection.prepareStatement(_queryId); | |
776 statement.setString(1, id); | |
777 ResultSet result = statement.executeQuery(); | |
778 return result.next(); | |
779 } | |
780 finally | |
781 { | |
782 if (statement!=null) | |
783 { | |
784 try { statement.close(); } | |
785 catch(Exception e) { LOG.warn(e); } | |
786 } | |
787 | |
788 if (connection != null) | |
789 connection.close(); | |
790 } | |
791 } | |
792 | |
793 /** | |
794 * Look for sessions in the database that have expired. | |
795 * | |
796 * We do this in the SessionIdManager and not the SessionManager so | |
797 * that we only have 1 scavenger, otherwise if there are n SessionManagers | |
798 * there would be n scavengers, all contending for the database. | |
799 * | |
800 * We look first for sessions that expired in the previous interval, then | |
801 * for sessions that expired previously - these are old sessions that no | |
802 * node is managing any more and have become stuck in the database. | |
803 */ | |
804 private void scavenge () | |
805 { | |
806 Connection connection = null; | |
807 PreparedStatement statement = null; | |
808 List<String> expiredSessionIds = new ArrayList<String>(); | |
809 try | |
810 { | |
811 if (LOG.isDebugEnabled()) | |
812 LOG.debug("Scavenge sweep started at "+System.currentTimeMillis()); | |
813 if (_lastScavengeTime > 0) | |
814 { | |
815 connection = getConnection(); | |
816 connection.setAutoCommit(true); | |
817 //"select sessionId from JettySessions where expiryTime > (lastScavengeTime - scanInterval) and expiryTime < lastScavengeTime"; | |
818 statement = connection.prepareStatement(_selectBoundedExpiredSessions); | |
819 long lowerBound = (_lastScavengeTime - _scavengeIntervalMs); | |
820 long upperBound = _lastScavengeTime; | |
821 if (LOG.isDebugEnabled()) | |
822 LOG.debug (" Searching for sessions expired between "+lowerBound + " and "+upperBound); | |
823 | |
824 statement.setLong(1, lowerBound); | |
825 statement.setLong(2, upperBound); | |
826 ResultSet result = statement.executeQuery(); | |
827 while (result.next()) | |
828 { | |
829 String sessionId = result.getString("sessionId"); | |
830 expiredSessionIds.add(sessionId); | |
831 if (LOG.isDebugEnabled()) LOG.debug (" Found expired sessionId="+sessionId); | |
832 } | |
833 | |
834 //tell the SessionManagers to expire any sessions with a matching sessionId in memory | |
835 Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class); | |
836 for (int i=0; contexts!=null && i<contexts.length; i++) | |
837 { | |
838 | |
839 SessionHandler sessionHandler = (SessionHandler)((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class); | |
840 if (sessionHandler != null) | |
841 { | |
842 SessionManager manager = sessionHandler.getSessionManager(); | |
843 if (manager != null && manager instanceof JDBCSessionManager) | |
844 { | |
845 ((JDBCSessionManager)manager).expire(expiredSessionIds); | |
846 } | |
847 } | |
848 } | |
849 | |
850 //find all sessions that have expired at least a couple of scanIntervals ago and just delete them | |
851 upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs); | |
852 if (upperBound > 0) | |
853 { | |
854 if (LOG.isDebugEnabled()) LOG.debug("Deleting old expired sessions expired before "+upperBound); | |
855 try | |
856 { | |
857 statement = connection.prepareStatement(_deleteOldExpiredSessions); | |
858 statement.setLong(1, upperBound); | |
859 int rows = statement.executeUpdate(); | |
860 if (LOG.isDebugEnabled()) LOG.debug("Deleted "+rows+" rows of old sessions expired before "+upperBound); | |
861 } | |
862 finally | |
863 { | |
864 if (statement!=null) | |
865 { | |
866 try { statement.close(); } | |
867 catch(Exception e) { LOG.warn(e); } | |
868 } | |
869 } | |
870 } | |
871 } | |
872 } | |
873 catch (Exception e) | |
874 { | |
875 if (isRunning()) | |
876 LOG.warn("Problem selecting expired sessions", e); | |
877 else | |
878 LOG.ignore(e); | |
879 } | |
880 finally | |
881 { | |
882 _lastScavengeTime=System.currentTimeMillis(); | |
883 if (LOG.isDebugEnabled()) LOG.debug("Scavenge sweep ended at "+_lastScavengeTime); | |
884 if (connection != null) | |
885 { | |
886 try | |
887 { | |
888 connection.close(); | |
889 } | |
890 catch (SQLException e) | |
891 { | |
892 LOG.warn(e); | |
893 } | |
894 } | |
895 } | |
896 } | |
897 | |
898 /** | |
899 * Get rid of sessions and sessionids from sessions that have already expired | |
900 * @throws Exception | |
901 */ | |
902 private void cleanExpiredSessions () | |
903 { | |
904 Connection connection = null; | |
905 PreparedStatement statement = null; | |
906 Statement sessionsTableStatement = null; | |
907 Statement sessionIdsTableStatement = null; | |
908 List<String> expiredSessionIds = new ArrayList<String>(); | |
909 try | |
910 { | |
911 connection = getConnection(); | |
912 connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); | |
913 connection.setAutoCommit(false); | |
914 | |
915 statement = connection.prepareStatement(_selectExpiredSessions); | |
916 long now = System.currentTimeMillis(); | |
917 if (LOG.isDebugEnabled()) LOG.debug ("Searching for sessions expired before {}", now); | |
918 | |
919 statement.setLong(1, now); | |
920 ResultSet result = statement.executeQuery(); | |
921 while (result.next()) | |
922 { | |
923 String sessionId = result.getString("sessionId"); | |
924 expiredSessionIds.add(sessionId); | |
925 if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId={}", sessionId); | |
926 } | |
927 | |
928 sessionsTableStatement = null; | |
929 sessionIdsTableStatement = null; | |
930 | |
931 if (!expiredSessionIds.isEmpty()) | |
932 { | |
933 sessionsTableStatement = connection.createStatement(); | |
934 sessionsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionTable+" where sessionId in ", expiredSessionIds)); | |
935 sessionIdsTableStatement = connection.createStatement(); | |
936 sessionIdsTableStatement.executeUpdate(createCleanExpiredSessionsSql("delete from "+_sessionIdTable+" where id in ", expiredSessionIds)); | |
937 } | |
938 connection.commit(); | |
939 | |
940 synchronized (_sessionIds) | |
941 { | |
942 _sessionIds.removeAll(expiredSessionIds); //in case they were in our local cache of session ids | |
943 } | |
944 } | |
945 catch (Exception e) | |
946 { | |
947 if (connection != null) | |
948 { | |
949 try | |
950 { | |
951 LOG.warn("Rolling back clean of expired sessions", e); | |
952 connection.rollback(); | |
953 } | |
954 catch (Exception x) { LOG.warn("Rollback of expired sessions failed", x);} | |
955 } | |
956 } | |
957 finally | |
958 { | |
959 if (sessionIdsTableStatement!=null) | |
960 { | |
961 try { sessionIdsTableStatement.close(); } | |
962 catch(Exception e) { LOG.warn(e); } | |
963 } | |
964 | |
965 if (sessionsTableStatement!=null) | |
966 { | |
967 try { sessionsTableStatement.close(); } | |
968 catch(Exception e) { LOG.warn(e); } | |
969 } | |
970 | |
971 if (statement!=null) | |
972 { | |
973 try { statement.close(); } | |
974 catch(Exception e) { LOG.warn(e); } | |
975 } | |
976 | |
977 try | |
978 { | |
979 if (connection != null) | |
980 connection.close(); | |
981 } | |
982 catch (SQLException e) | |
983 { | |
984 LOG.warn(e); | |
985 } | |
986 } | |
987 } | |
988 | |
989 | |
990 /** | |
991 * | |
992 * @param sql | |
993 * @param connection | |
994 * @param expiredSessionIds | |
995 * @throws Exception | |
996 */ | |
997 private String createCleanExpiredSessionsSql (String sql,Collection<String> expiredSessionIds) | |
998 throws Exception | |
999 { | |
1000 StringBuffer buff = new StringBuffer(); | |
1001 buff.append(sql); | |
1002 buff.append("("); | |
1003 Iterator<String> itor = expiredSessionIds.iterator(); | |
1004 while (itor.hasNext()) | |
1005 { | |
1006 buff.append("'"+(itor.next())+"'"); | |
1007 if (itor.hasNext()) | |
1008 buff.append(","); | |
1009 } | |
1010 buff.append(")"); | |
1011 | |
1012 if (LOG.isDebugEnabled()) LOG.debug("Cleaning expired sessions with: {}", buff); | |
1013 return buff.toString(); | |
1014 } | |
1015 | |
1016 private void initializeDatabase () | |
1017 throws Exception | |
1018 { | |
1019 if (_datasource != null) | |
1020 return; //already set up | |
1021 | |
1022 if (_jndiName!=null) | |
1023 { | |
1024 InitialContext ic = new InitialContext(); | |
1025 _datasource = (DataSource)ic.lookup(_jndiName); | |
1026 } | |
1027 else if ( _driver != null && _connectionUrl != null ) | |
1028 { | |
1029 DriverManager.registerDriver(_driver); | |
1030 } | |
1031 else if (_driverClassName != null && _connectionUrl != null) | |
1032 { | |
1033 Class.forName(_driverClassName); | |
1034 } | |
1035 else | |
1036 throw new IllegalStateException("No database configured for sessions"); | |
1037 } | |
1038 | |
1039 | |
1040 } |