View Javadoc

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