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 }