View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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.HashSet;
33  import java.util.Locale;
34  import java.util.Random;
35  import java.util.Set;
36  import java.util.concurrent.TimeUnit;
37  
38  import javax.naming.InitialContext;
39  import javax.servlet.http.HttpServletRequest;
40  import javax.servlet.http.HttpSession;
41  import javax.sql.DataSource;
42  
43  import org.eclipse.jetty.server.Handler;
44  import org.eclipse.jetty.server.Server;
45  import org.eclipse.jetty.server.SessionManager;
46  import org.eclipse.jetty.server.handler.ContextHandler;
47  import org.eclipse.jetty.util.log.Logger;
48  import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
49  import org.eclipse.jetty.util.thread.Scheduler;
50  
51  
52  
53  /**
54   * JDBCSessionIdManager
55   *
56   * SessionIdManager implementation that uses a database to store in-use session ids,
57   * to support distributed sessions.
58   *
59   */
60  public class JDBCSessionIdManager extends AbstractSessionIdManager
61  {
62      final static Logger LOG = SessionHandler.LOG;
63      public final static int MAX_INTERVAL_NOT_SET = -999;
64  
65      protected final HashSet<String> _sessionIds = new HashSet<String>();
66      protected Server _server;
67      protected Driver _driver;
68      protected String _driverClassName;
69      protected String _connectionUrl;
70      protected DataSource _datasource;
71      protected String _jndiName;
72  
73      protected int _deleteBlockSize = 10; //number of ids to include in where 'in' clause
74  
75      protected Scheduler.Task _task; //scavenge task
76      protected Scheduler _scheduler;
77      protected Scavenger _scavenger;
78      protected boolean _ownScheduler;
79      protected long _lastScavengeTime;
80      protected long _scavengeIntervalMs = 1000L * 60 * 10; //10mins
81  
82  
83      protected String _createSessionIdTable;
84      protected String _createSessionTable;
85  
86      protected String _selectBoundedExpiredSessions;
87      private String _selectExpiredSessions;
88      
89      protected String _insertId;
90      protected String _deleteId;
91      protected String _queryId;
92  
93      protected  String _insertSession;
94      protected  String _deleteSession;
95      protected  String _updateSession;
96      protected  String _updateSessionNode;
97      protected  String _updateSessionAccessTime;
98  
99      protected DatabaseAdaptor _dbAdaptor = new DatabaseAdaptor();
100     protected SessionIdTableSchema _sessionIdTableSchema = new SessionIdTableSchema();
101     protected SessionTableSchema _sessionTableSchema = new SessionTableSchema();
102     
103   
104 
105  
106     /**
107      * SessionTableSchema
108      *
109      */
110     public static class SessionTableSchema
111     {        
112         protected DatabaseAdaptor _dbAdaptor;
113         protected String _tableName = "JettySessions";
114         protected String _rowIdColumn = "rowId";
115         protected String _idColumn = "sessionId";
116         protected String _contextPathColumn = "contextPath";
117         protected String _virtualHostColumn = "virtualHost"; 
118         protected String _lastNodeColumn = "lastNode";
119         protected String _accessTimeColumn = "accessTime"; 
120         protected String _lastAccessTimeColumn = "lastAccessTime";
121         protected String _createTimeColumn = "createTime";
122         protected String _cookieTimeColumn = "cookieTime";
123         protected String _lastSavedTimeColumn = "lastSavedTime";
124         protected String _expiryTimeColumn = "expiryTime";
125         protected String _maxIntervalColumn = "maxInterval";
126         protected String _mapColumn = "map";
127         
128         
129         protected void setDatabaseAdaptor(DatabaseAdaptor dbadaptor)
130         {
131             _dbAdaptor = dbadaptor;
132         }
133         
134         
135         public String getTableName()
136         {
137             return _tableName;
138         }
139         public void setTableName(String tableName)
140         {
141             checkNotNull(tableName);
142             _tableName = tableName;
143         }
144         public String getRowIdColumn()
145         {       
146             if ("rowId".equals(_rowIdColumn) && _dbAdaptor.isRowIdReserved())
147                 _rowIdColumn = "srowId";
148             return _rowIdColumn;
149         }
150         public void setRowIdColumn(String rowIdColumn)
151         {
152             checkNotNull(rowIdColumn);
153             if (_dbAdaptor == null)
154                 throw new IllegalStateException ("DbAdaptor is null");
155             
156             if (_dbAdaptor.isRowIdReserved() && "rowId".equals(rowIdColumn))
157                 throw new IllegalArgumentException("rowId is reserved word for Oracle");
158             
159             _rowIdColumn = rowIdColumn;
160         }
161         public String getIdColumn()
162         {
163             return _idColumn;
164         }
165         public void setIdColumn(String idColumn)
166         {
167             checkNotNull(idColumn);
168             _idColumn = idColumn;
169         }
170         public String getContextPathColumn()
171         {
172             return _contextPathColumn;
173         }
174         public void setContextPathColumn(String contextPathColumn)
175         {
176             checkNotNull(contextPathColumn);
177             _contextPathColumn = contextPathColumn;
178         }
179         public String getVirtualHostColumn()
180         {
181             return _virtualHostColumn;
182         }
183         public void setVirtualHostColumn(String virtualHostColumn)
184         {
185             checkNotNull(virtualHostColumn);
186             _virtualHostColumn = virtualHostColumn;
187         }
188         public String getLastNodeColumn()
189         {
190             return _lastNodeColumn;
191         }
192         public void setLastNodeColumn(String lastNodeColumn)
193         {
194             checkNotNull(lastNodeColumn);
195             _lastNodeColumn = lastNodeColumn;
196         }
197         public String getAccessTimeColumn()
198         {
199             return _accessTimeColumn;
200         }
201         public void setAccessTimeColumn(String accessTimeColumn)
202         {
203             checkNotNull(accessTimeColumn);
204             _accessTimeColumn = accessTimeColumn;
205         }
206         public String getLastAccessTimeColumn()
207         {
208             return _lastAccessTimeColumn;
209         }
210         public void setLastAccessTimeColumn(String lastAccessTimeColumn)
211         {
212             checkNotNull(lastAccessTimeColumn);
213             _lastAccessTimeColumn = lastAccessTimeColumn;
214         }
215         public String getCreateTimeColumn()
216         {
217             return _createTimeColumn;
218         }
219         public void setCreateTimeColumn(String createTimeColumn)
220         {
221             checkNotNull(createTimeColumn);
222             _createTimeColumn = createTimeColumn;
223         }
224         public String getCookieTimeColumn()
225         {
226             return _cookieTimeColumn;
227         }
228         public void setCookieTimeColumn(String cookieTimeColumn)
229         {
230             checkNotNull(cookieTimeColumn);
231             _cookieTimeColumn = cookieTimeColumn;
232         }
233         public String getLastSavedTimeColumn()
234         {
235             return _lastSavedTimeColumn;
236         }
237         public void setLastSavedTimeColumn(String lastSavedTimeColumn)
238         {
239             checkNotNull(lastSavedTimeColumn);
240             _lastSavedTimeColumn = lastSavedTimeColumn;
241         }
242         public String getExpiryTimeColumn()
243         {
244             return _expiryTimeColumn;
245         }
246         public void setExpiryTimeColumn(String expiryTimeColumn)
247         {
248             checkNotNull(expiryTimeColumn);
249             _expiryTimeColumn = expiryTimeColumn;
250         }
251         public String getMaxIntervalColumn()
252         {
253             return _maxIntervalColumn;
254         }
255         public void setMaxIntervalColumn(String maxIntervalColumn)
256         {
257             checkNotNull(maxIntervalColumn);
258             _maxIntervalColumn = maxIntervalColumn;
259         }
260         public String getMapColumn()
261         {
262             return _mapColumn;
263         }
264         public void setMapColumn(String mapColumn)
265         {
266             checkNotNull(mapColumn);
267             _mapColumn = mapColumn;
268         }
269         
270         public String getCreateStatementAsString ()
271         {
272             if (_dbAdaptor == null)
273                 throw new IllegalStateException ("No DBAdaptor");
274             
275             String blobType = _dbAdaptor.getBlobType();
276             String longType = _dbAdaptor.getLongType();
277             
278             return "create table "+_tableName+" ("+getRowIdColumn()+" varchar(120), "+_idColumn+" varchar(120), "+
279                     _contextPathColumn+" varchar(60), "+_virtualHostColumn+" varchar(60), "+_lastNodeColumn+" varchar(60), "+_accessTimeColumn+" "+longType+", "+
280                     _lastAccessTimeColumn+" "+longType+", "+_createTimeColumn+" "+longType+", "+_cookieTimeColumn+" "+longType+", "+
281                     _lastSavedTimeColumn+" "+longType+", "+_expiryTimeColumn+" "+longType+", "+_maxIntervalColumn+" "+longType+", "+
282                     _mapColumn+" "+blobType+", primary key("+getRowIdColumn()+"))";
283         }
284         
285         public String getCreateIndexOverExpiryStatementAsString (String indexName)
286         {
287             return "create index "+indexName+" on "+getTableName()+" ("+getExpiryTimeColumn()+")";
288         }
289         
290         public String getCreateIndexOverSessionStatementAsString (String indexName)
291         {
292             return "create index "+indexName+" on "+getTableName()+" ("+getIdColumn()+", "+getContextPathColumn()+")";
293         }
294         
295         public String getAlterTableForMaxIntervalAsString ()
296         {
297             if (_dbAdaptor == null)
298                 throw new IllegalStateException ("No DBAdaptor");
299             String longType = _dbAdaptor.getLongType();
300             String stem = "alter table "+getTableName()+" add "+getMaxIntervalColumn()+" "+longType;
301             if (_dbAdaptor.getDBName().contains("oracle"))
302                 return stem + " default "+ MAX_INTERVAL_NOT_SET + " not null";
303             else
304                 return stem +" not null default "+ MAX_INTERVAL_NOT_SET;
305         }
306         
307         private void checkNotNull(String s)
308         {
309             if (s == null)
310                 throw new IllegalArgumentException(s);
311         }
312         public String getInsertSessionStatementAsString()
313         {
314            return "insert into "+getTableName()+
315             " ("+getRowIdColumn()+", "+getIdColumn()+", "+getContextPathColumn()+", "+getVirtualHostColumn()+", "+getLastNodeColumn()+
316             ", "+getAccessTimeColumn()+", "+getLastAccessTimeColumn()+", "+getCreateTimeColumn()+", "+getCookieTimeColumn()+
317             ", "+getLastSavedTimeColumn()+", "+getExpiryTimeColumn()+", "+getMaxIntervalColumn()+", "+getMapColumn()+") "+
318             " values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
319         }
320         public String getDeleteSessionStatementAsString()
321         {
322             return "delete from "+getTableName()+
323             " where "+getRowIdColumn()+" = ?";
324         }
325         public String getUpdateSessionStatementAsString()
326         {
327             return "update "+getTableName()+
328                     " set "+getIdColumn()+" = ?, "+getLastNodeColumn()+" = ?, "+getAccessTimeColumn()+" = ?, "+
329                     getLastAccessTimeColumn()+" = ?, "+getLastSavedTimeColumn()+" = ?, "+getExpiryTimeColumn()+" = ?, "+
330                     getMaxIntervalColumn()+" = ?, "+getMapColumn()+" = ? where "+getRowIdColumn()+" = ?";
331         }
332         public String getUpdateSessionNodeStatementAsString()
333         {
334             return "update "+getTableName()+
335                     " set "+getLastNodeColumn()+" = ? where "+getRowIdColumn()+" = ?";
336         }
337         public String getUpdateSessionAccessTimeStatementAsString()
338         {
339            return "update "+getTableName()+
340             " set "+getLastNodeColumn()+" = ?, "+getAccessTimeColumn()+" = ?, "+getLastAccessTimeColumn()+" = ?, "+
341                    getLastSavedTimeColumn()+" = ?, "+getExpiryTimeColumn()+" = ?, "+getMaxIntervalColumn()+" = ? where "+getRowIdColumn()+" = ?";
342         }
343         
344         public String getBoundedExpiredSessionsStatementAsString()
345         {
346             return "select * from "+getTableName()+" where "+getLastNodeColumn()+" = ? and "+getExpiryTimeColumn()+" >= ? and "+getExpiryTimeColumn()+" <= ?";
347         }
348         
349         public String getSelectExpiredSessionsStatementAsString()
350         {
351             return "select * from "+getTableName()+" where "+getExpiryTimeColumn()+" >0 and "+getExpiryTimeColumn()+" <= ?";
352         }
353      
354         public PreparedStatement getLoadStatement (Connection connection, String rowId, String contextPath, String virtualHosts)
355         throws SQLException
356         { 
357             if (_dbAdaptor == null)
358                 throw new IllegalStateException("No DB adaptor");
359 
360 
361             if (contextPath == null || "".equals(contextPath))
362             {
363                 if (_dbAdaptor.isEmptyStringNull())
364                 {
365                     PreparedStatement statement = connection.prepareStatement("select * from "+getTableName()+
366                                                                               " where "+getIdColumn()+" = ? and "+
367                                                                               getContextPathColumn()+" is null and "+
368                                                                               getVirtualHostColumn()+" = ?");
369                     statement.setString(1, rowId);
370                     statement.setString(2, virtualHosts);
371 
372                     return statement;
373                 }
374             }
375 
376             PreparedStatement statement = connection.prepareStatement("select * from "+getTableName()+
377                                                                       " where "+getIdColumn()+" = ? and "+getContextPathColumn()+
378                                                                       " = ? and "+getVirtualHostColumn()+" = ?");
379             statement.setString(1, rowId);
380             statement.setString(2, contextPath);
381             statement.setString(3, virtualHosts);
382 
383             return statement;
384         }
385     }
386     
387     
388     
389     /**
390      * SessionIdTableSchema
391      *
392      */
393     public static class SessionIdTableSchema
394     {
395         protected DatabaseAdaptor _dbAdaptor;
396         protected String _tableName = "JettySessionIds";
397         protected String _idColumn = "id";
398 
399         public void setDatabaseAdaptor(DatabaseAdaptor dbAdaptor)
400         {
401             _dbAdaptor = dbAdaptor;
402         }
403         public String getIdColumn()
404         {
405             return _idColumn;
406         }
407 
408         public void setIdColumn(String idColumn)
409         {
410             checkNotNull(idColumn);
411             _idColumn = idColumn;
412         }
413 
414         public String getTableName()
415         {
416             return _tableName;
417         }
418 
419         public void setTableName(String tableName)
420         {
421             checkNotNull(tableName);
422             _tableName = tableName;
423         }
424 
425         public String getInsertStatementAsString ()
426         {
427             return "insert into "+_tableName+" ("+_idColumn+")  values (?)";
428         }
429 
430         public String getDeleteStatementAsString ()
431         {
432             return "delete from "+_tableName+" where "+_idColumn+" = ?";
433         }
434 
435         public String getSelectStatementAsString ()
436         {
437             return  "select * from "+_tableName+" where "+_idColumn+" = ?";
438         }
439         
440         public String getCreateStatementAsString ()
441         {
442             return "create table "+_tableName+" ("+_idColumn+" varchar(120), primary key("+_idColumn+"))";
443         }
444         
445         private void checkNotNull(String s)
446         {
447             if (s == null)
448                 throw new IllegalArgumentException(s);
449         }
450     }
451 
452 
453     /**
454      * DatabaseAdaptor
455      *
456      * Handles differences between databases.
457      *
458      * Postgres uses the getBytes and setBinaryStream methods to access
459      * a "bytea" datatype, which can be up to 1Gb of binary data. MySQL
460      * is happy to use the "blob" type and getBlob() methods instead.
461      *
462      * TODO if the differences become more major it would be worthwhile
463      * refactoring this class.
464      */
465     public static class DatabaseAdaptor
466     {
467         String _dbName;
468         boolean _isLower;
469         boolean _isUpper;
470         
471         protected String _blobType; //if not set, is deduced from the type of the database at runtime
472         protected String _longType; //if not set, is deduced from the type of the database at runtime
473 
474 
475         public DatabaseAdaptor ()
476         {           
477         }
478         
479         
480         public void adaptTo(DatabaseMetaData dbMeta)  
481         throws SQLException
482         {
483             _dbName = dbMeta.getDatabaseProductName().toLowerCase(Locale.ENGLISH);
484             if (LOG.isDebugEnabled())
485                 LOG.debug ("Using database {}",_dbName);
486             _isLower = dbMeta.storesLowerCaseIdentifiers();
487             _isUpper = dbMeta.storesUpperCaseIdentifiers(); 
488         }
489         
490        
491         public void setBlobType(String blobType)
492         {
493             _blobType = blobType;
494         }
495         
496         public String getBlobType ()
497         {
498             if (_blobType != null)
499                 return _blobType;
500 
501             if (_dbName.startsWith("postgres"))
502                 return "bytea";
503 
504             return "blob";
505         }
506         
507 
508         public void setLongType(String longType)
509         {
510             _longType = longType;
511         }
512         
513 
514         public String getLongType ()
515         {
516             if (_longType != null)
517                 return _longType;
518 
519             if (_dbName == null)
520                 throw new IllegalStateException ("DbAdaptor missing metadata");
521             
522             if (_dbName.startsWith("oracle"))
523                 return "number(20)";
524 
525             return "bigint";
526         }
527         
528 
529         /**
530          * Convert a camel case identifier into either upper or lower
531          * depending on the way the db stores identifiers.
532          *
533          * @param identifier the raw identifier
534          * @return the converted identifier
535          */
536         public String convertIdentifier (String identifier)
537         {
538             if (_dbName == null)
539                 throw new IllegalStateException ("DbAdaptor missing metadata");
540             
541             if (_isLower)
542                 return identifier.toLowerCase(Locale.ENGLISH);
543             if (_isUpper)
544                 return identifier.toUpperCase(Locale.ENGLISH);
545 
546             return identifier;
547         }
548 
549         public String getDBName ()
550         {
551             return _dbName;
552         }
553 
554 
555         public InputStream getBlobInputStream (ResultSet result, String columnName)
556         throws SQLException
557         {
558             if (_dbName == null)
559                 throw new IllegalStateException ("DbAdaptor missing metadata");
560             
561             if (_dbName.startsWith("postgres"))
562             {
563                 byte[] bytes = result.getBytes(columnName);
564                 return new ByteArrayInputStream(bytes);
565             }
566 
567             Blob blob = result.getBlob(columnName);
568             return blob.getBinaryStream();
569         }
570 
571 
572         public boolean isEmptyStringNull ()
573         {
574             if (_dbName == null)
575                 throw new IllegalStateException ("DbAdaptor missing metadata");
576             
577             return (_dbName.startsWith("oracle"));
578         }
579         
580         /**
581          * rowId is a reserved word for Oracle, so change the name of this column
582          * @return true if db in use is oracle
583          */
584         public boolean isRowIdReserved ()
585         {
586             if (_dbName == null)
587                 throw new IllegalStateException ("DbAdaptor missing metadata");
588             
589             return (_dbName != null && _dbName.startsWith("oracle"));
590         }
591     }
592 
593     
594     /**
595      * Scavenger
596      *
597      */
598     protected class Scavenger implements Runnable
599     {
600 
601         @Override
602         public void run()
603         {
604            try
605            {
606                scavenge();
607            }
608            finally
609            {
610                if (_scheduler != null && _scheduler.isRunning())
611                    _task = _scheduler.schedule(this, _scavengeIntervalMs, TimeUnit.MILLISECONDS);
612            }
613         }
614     }
615 
616 
617     public JDBCSessionIdManager(Server server)
618     {
619         super();
620         _server=server;
621     }
622 
623     public JDBCSessionIdManager(Server server, Random random)
624     {
625        super(random);
626        _server=server;
627     }
628 
629     /**
630      * Configure jdbc connection information via a jdbc Driver
631      *
632      * @param driverClassName the driver classname
633      * @param connectionUrl the driver connection url
634      */
635     public void setDriverInfo (String driverClassName, String connectionUrl)
636     {
637         _driverClassName=driverClassName;
638         _connectionUrl=connectionUrl;
639     }
640 
641     /**
642      * Configure jdbc connection information via a jdbc Driver
643      *
644      * @param driverClass the driver class
645      * @param connectionUrl the driver connection url
646      */
647     public void setDriverInfo (Driver driverClass, String connectionUrl)
648     {
649         _driver=driverClass;
650         _connectionUrl=connectionUrl;
651     }
652 
653 
654     public void setDatasource (DataSource ds)
655     {
656         _datasource = ds;
657     }
658 
659     public DataSource getDataSource ()
660     {
661         return _datasource;
662     }
663 
664     public String getDriverClassName()
665     {
666         return _driverClassName;
667     }
668 
669     public String getConnectionUrl ()
670     {
671         return _connectionUrl;
672     }
673 
674     public void setDatasourceName (String jndi)
675     {
676         _jndiName=jndi;
677     }
678 
679     public String getDatasourceName ()
680     {
681         return _jndiName;
682     }
683 
684     /**
685      * @param name the name of the blob
686      * @deprecated see DbAdaptor.setBlobType
687      */
688     @Deprecated
689     public void setBlobType (String name)
690     {
691         _dbAdaptor.setBlobType(name);
692     }
693 
694     public DatabaseAdaptor getDbAdaptor()
695     {
696         return _dbAdaptor;
697     }
698 
699     public void setDbAdaptor(DatabaseAdaptor dbAdaptor)
700     {
701         if (dbAdaptor == null)
702             throw new IllegalStateException ("DbAdaptor cannot be null");
703         
704         _dbAdaptor = dbAdaptor;
705     }
706 
707     /**
708      * @return the blob type
709      * @deprecated see DbAdaptor.getBlobType
710      */
711     @Deprecated
712     public String getBlobType ()
713     {
714         return _dbAdaptor.getBlobType();
715     }
716 
717     /**
718      * @return the long type
719      * @deprecated see DbAdaptor.getLogType
720      */
721     @Deprecated
722     public String getLongType()
723     {
724         return _dbAdaptor.getLongType();
725     }
726 
727     /**
728      * @param longType the long type
729      * @deprecated see DbAdaptor.setLongType
730      */
731     @Deprecated
732     public void setLongType(String longType)
733     {
734        _dbAdaptor.setLongType(longType);
735     }
736     
737     public SessionIdTableSchema getSessionIdTableSchema()
738     {
739         return _sessionIdTableSchema;
740     }
741 
742     public void setSessionIdTableSchema(SessionIdTableSchema sessionIdTableSchema)
743     {
744         if (sessionIdTableSchema == null)
745             throw new IllegalArgumentException("Null SessionIdTableSchema");
746         
747         _sessionIdTableSchema = sessionIdTableSchema;
748     }
749 
750     public SessionTableSchema getSessionTableSchema()
751     {
752         return _sessionTableSchema;
753     }
754 
755     public void setSessionTableSchema(SessionTableSchema sessionTableSchema)
756     {
757         _sessionTableSchema = sessionTableSchema;
758     }
759 
760     public void setDeleteBlockSize (int bsize)
761     {
762         this._deleteBlockSize = bsize;
763     }
764 
765     public int getDeleteBlockSize ()
766     {
767         return this._deleteBlockSize;
768     }
769     
770     public void setScavengeInterval (long sec)
771     {
772         if (sec<=0)
773             sec=60;
774 
775         long old_period=_scavengeIntervalMs;
776         long period=sec*1000L;
777 
778         _scavengeIntervalMs=period;
779 
780         //add a bit of variability into the scavenge time so that not all
781         //nodes with the same scavenge time sync up
782         long tenPercent = _scavengeIntervalMs/10;
783         if ((System.currentTimeMillis()%2) == 0)
784             _scavengeIntervalMs += tenPercent;
785 
786         if (LOG.isDebugEnabled())
787             LOG.debug("Scavenging every "+_scavengeIntervalMs+" ms");
788         
789         synchronized (this)
790         {
791             //if (_timer!=null && (period!=old_period || _task==null))
792             if (_scheduler != null && (period!=old_period || _task==null))
793             {
794                 if (_task!=null)
795                     _task.cancel();
796                 if (_scavenger == null)
797                     _scavenger = new Scavenger();
798                 _task = _scheduler.schedule(_scavenger,_scavengeIntervalMs,TimeUnit.MILLISECONDS);
799             }
800         }
801     }
802 
803     public long getScavengeInterval ()
804     {
805         return _scavengeIntervalMs/1000;
806     }
807 
808 
809     @Override
810     public void addSession(HttpSession session)
811     {
812         if (session == null)
813             return;
814 
815         synchronized (_sessionIds)
816         {
817             String id = ((JDBCSessionManager.Session)session).getClusterId();
818             try
819             {
820                 insert(id);
821                 _sessionIds.add(id);
822             }
823             catch (Exception e)
824             {
825                 LOG.warn("Problem storing session id="+id, e);
826             }
827         }
828     }
829     
830   
831     public void addSession(String id)
832     {
833         if (id == null)
834             return;
835 
836         synchronized (_sessionIds)
837         {           
838             try
839             {
840                 insert(id);
841                 _sessionIds.add(id);
842             }
843             catch (Exception e)
844             {
845                 LOG.warn("Problem storing session id="+id, e);
846             }
847         }
848     }
849 
850 
851 
852     @Override
853     public void removeSession(HttpSession session)
854     {
855         if (session == null)
856             return;
857 
858         removeSession(((JDBCSessionManager.Session)session).getClusterId());
859     }
860 
861 
862 
863     public void removeSession (String id)
864     {
865 
866         if (id == null)
867             return;
868 
869         synchronized (_sessionIds)
870         {
871             if (LOG.isDebugEnabled())
872                 LOG.debug("Removing sessionid="+id);
873             try
874             {
875                 _sessionIds.remove(id);
876                 delete(id);
877             }
878             catch (Exception e)
879             {
880                 LOG.warn("Problem removing session id="+id, e);
881             }
882         }
883 
884     }
885 
886 
887     @Override
888     public boolean idInUse(String id)
889     {
890         if (id == null)
891             return false;
892 
893         String clusterId = getClusterId(id);
894         boolean inUse = false;
895         synchronized (_sessionIds)
896         {
897             inUse = _sessionIds.contains(clusterId);
898         }
899 
900         
901         if (inUse)
902             return true; //optimisation - if this session is one we've been managing, we can check locally
903 
904         //otherwise, we need to go to the database to check
905         try
906         {
907             return exists(clusterId);
908         }
909         catch (Exception e)
910         {
911             LOG.warn("Problem checking inUse for id="+clusterId, e);
912             return false;
913         }
914     }
915 
916     /**
917      * Invalidate the session matching the id on all contexts.
918      *
919      * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String)
920      */
921     @Override
922     public void invalidateAll(String id)
923     {
924         //take the id out of the list of known sessionids for this node
925         removeSession(id);
926 
927         synchronized (_sessionIds)
928         {
929             //tell all contexts that may have a session object with this id to
930             //get rid of them
931             Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
932             for (int i=0; contexts!=null && i<contexts.length; i++)
933             {
934                 SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
935                 if (sessionHandler != null)
936                 {
937                     SessionManager manager = sessionHandler.getSessionManager();
938 
939                     if (manager != null && manager instanceof JDBCSessionManager)
940                     {
941                         ((JDBCSessionManager)manager).invalidateSession(id);
942                     }
943                 }
944             }
945         }
946     }
947 
948 
949     @Override
950     public void renewSessionId (String oldClusterId, String oldNodeId, HttpServletRequest request)
951     {
952         //generate a new id
953         String newClusterId = newSessionId(request.hashCode());
954 
955         synchronized (_sessionIds)
956         {
957             removeSession(oldClusterId);//remove the old one from the list (and database)
958             addSession(newClusterId); //add in the new session id to the list (and database)
959 
960             //tell all contexts to update the id 
961             Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
962             for (int i=0; contexts!=null && i<contexts.length; i++)
963             {
964                 SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
965                 if (sessionHandler != null) 
966                 {
967                     SessionManager manager = sessionHandler.getSessionManager();
968 
969                     if (manager != null && manager instanceof JDBCSessionManager)
970                     {
971                         ((JDBCSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
972                     }
973                 }
974             }
975         }
976     }
977 
978 
979     /**
980      * Start up the id manager.
981      *
982      * Makes necessary database tables and starts a Session
983      * scavenger thread.
984      */
985     @Override
986     public void doStart()
987     throws Exception
988     {           
989         initializeDatabase();
990         prepareTables();   
991         super.doStart();
992         if (LOG.isDebugEnabled()) 
993             LOG.debug("Scavenging interval = "+getScavengeInterval()+" sec");
994         
995          //try and use a common scheduler, fallback to own
996          _scheduler =_server.getBean(Scheduler.class);
997          if (_scheduler == null)
998          {
999              _scheduler = new ScheduledExecutorScheduler();
1000              _ownScheduler = true;
1001              _scheduler.start();
1002          }
1003          else if (!_scheduler.isStarted())
1004              throw new IllegalStateException("Shared scheduler not started");
1005   
1006         setScavengeInterval(getScavengeInterval());
1007     }
1008 
1009     /**
1010      * Stop the scavenger.
1011      */
1012     @Override
1013     public void doStop ()
1014     throws Exception
1015     {
1016         synchronized(this)
1017         {
1018             if (_task!=null)
1019                 _task.cancel();
1020             _task=null;
1021             if (_ownScheduler && _scheduler !=null)
1022                 _scheduler.stop();
1023             _scheduler=null;
1024         }
1025         _sessionIds.clear();
1026         super.doStop();
1027     }
1028 
1029     /**
1030      * Get a connection from the driver or datasource.
1031      *
1032      * @return the connection for the datasource
1033      * @throws SQLException if unable to get the connection
1034      */
1035     protected Connection getConnection ()
1036     throws SQLException
1037     {
1038         if (_datasource != null)
1039             return _datasource.getConnection();
1040         else
1041             return DriverManager.getConnection(_connectionUrl);
1042     }
1043 
1044     /**
1045      * Set up the tables in the database
1046      * @throws SQLException
1047      */
1048     /**
1049      * @throws SQLException
1050      */
1051     private void prepareTables()
1052     throws SQLException
1053     {
1054         if (_sessionIdTableSchema == null)
1055             throw new IllegalStateException ("No SessionIdTableSchema");
1056         
1057         if (_sessionTableSchema == null)
1058             throw new IllegalStateException ("No SessionTableSchema");
1059         
1060         try (Connection connection = getConnection();
1061              Statement statement = connection.createStatement())
1062         {
1063             //make the id table
1064             connection.setAutoCommit(true);
1065             DatabaseMetaData metaData = connection.getMetaData();
1066             _dbAdaptor.adaptTo(metaData);
1067             _sessionTableSchema.setDatabaseAdaptor(_dbAdaptor);
1068             _sessionIdTableSchema.setDatabaseAdaptor(_dbAdaptor);
1069             
1070             _createSessionIdTable = _sessionIdTableSchema.getCreateStatementAsString();
1071             _insertId = _sessionIdTableSchema.getInsertStatementAsString();
1072             _deleteId =  _sessionIdTableSchema.getDeleteStatementAsString();
1073             _queryId = _sessionIdTableSchema.getSelectStatementAsString();
1074             
1075             //checking for table existence is case-sensitive, but table creation is not
1076             String tableName = _dbAdaptor.convertIdentifier(_sessionIdTableSchema.getTableName());
1077             try (ResultSet result = metaData.getTables(null, null, tableName, null))
1078             {
1079                 if (!result.next())
1080                 {
1081                     //table does not exist, so create it
1082                     statement.executeUpdate(_createSessionIdTable);
1083                 }
1084             }         
1085             
1086             //make the session table if necessary
1087             tableName = _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName());
1088             try (ResultSet result = metaData.getTables(null, null, tableName, null))
1089             {
1090                 if (!result.next())
1091                 {
1092                     //table does not exist, so create it
1093                     _createSessionTable = _sessionTableSchema.getCreateStatementAsString();
1094                     statement.executeUpdate(_createSessionTable);
1095                 }
1096                 else
1097                 {
1098                     //session table exists, check it has maxinterval column
1099                     ResultSet colResult = null;
1100                     try
1101                     {
1102                         colResult = metaData.getColumns(null, null,
1103                                                         _dbAdaptor.convertIdentifier(_sessionTableSchema.getTableName()), 
1104                                                         _dbAdaptor.convertIdentifier(_sessionTableSchema.getMaxIntervalColumn()));
1105                     }
1106                     catch (SQLException s)
1107                     {
1108                         LOG.warn("Problem checking if "+_sessionTableSchema.getTableName()+
1109                                  " table contains "+_sessionTableSchema.getMaxIntervalColumn()+" column. Ensure table contains column definition: \""
1110                                 +_sessionTableSchema.getMaxIntervalColumn()+" long not null default -999\"");
1111                         throw s;
1112                     }
1113                     try
1114                     {
1115                         if (!colResult.next())
1116                         {
1117                             try
1118                             {
1119                                 //add the maxinterval column
1120                                 statement.executeUpdate(_sessionTableSchema.getAlterTableForMaxIntervalAsString());
1121                             }
1122                             catch (SQLException s)
1123                             {
1124                                 LOG.warn("Problem adding "+_sessionTableSchema.getMaxIntervalColumn()+
1125                                          " column. Ensure table contains column definition: \""+_sessionTableSchema.getMaxIntervalColumn()+
1126                                          " long not null default -999\"");
1127                                 throw s;
1128                             }
1129                         }
1130                     }
1131                     finally
1132                     {
1133                         colResult.close();
1134                     }
1135                 }
1136             }
1137             //make some indexes on the JettySessions table
1138             String index1 = "idx_"+_sessionTableSchema.getTableName()+"_expiry";
1139             String index2 = "idx_"+_sessionTableSchema.getTableName()+"_session";
1140 
1141             boolean index1Exists = false;
1142             boolean index2Exists = false;
1143             try (ResultSet result = metaData.getIndexInfo(null, null, tableName, false, false))
1144             {
1145                 while (result.next())
1146                 {
1147                     String idxName = result.getString("INDEX_NAME");
1148                     if (index1.equalsIgnoreCase(idxName))
1149                         index1Exists = true;
1150                     else if (index2.equalsIgnoreCase(idxName))
1151                         index2Exists = true;
1152                 }
1153             }
1154             if (!index1Exists)
1155                 statement.executeUpdate(_sessionTableSchema.getCreateIndexOverExpiryStatementAsString(index1));
1156             if (!index2Exists)
1157                 statement.executeUpdate(_sessionTableSchema.getCreateIndexOverSessionStatementAsString(index2));
1158 
1159             //set up some strings representing the statements for session manipulation
1160             _insertSession = _sessionTableSchema.getInsertSessionStatementAsString();
1161             _deleteSession = _sessionTableSchema.getDeleteSessionStatementAsString();
1162             _updateSession = _sessionTableSchema.getUpdateSessionStatementAsString();
1163             _updateSessionNode = _sessionTableSchema.getUpdateSessionNodeStatementAsString();
1164             _updateSessionAccessTime = _sessionTableSchema.getUpdateSessionAccessTimeStatementAsString();
1165             _selectBoundedExpiredSessions = _sessionTableSchema.getBoundedExpiredSessionsStatementAsString();
1166             _selectExpiredSessions = _sessionTableSchema.getSelectExpiredSessionsStatementAsString();
1167         }
1168     }
1169 
1170     /**
1171      * Insert a new used session id into the table.
1172      *
1173      * @param id
1174      * @throws SQLException
1175      */
1176     private void insert (String id)
1177     throws SQLException
1178     {
1179         try (Connection connection = getConnection();
1180                 PreparedStatement query = connection.prepareStatement(_queryId))
1181         {
1182             connection.setAutoCommit(true);
1183             query.setString(1, id);
1184             try (ResultSet result = query.executeQuery())
1185             {
1186                 //only insert the id if it isn't in the db already
1187                 if (!result.next())
1188                 {
1189                     try (PreparedStatement statement = connection.prepareStatement(_insertId))
1190                     {
1191                         statement.setString(1, id);
1192                         statement.executeUpdate();
1193                     }
1194                 }
1195             }
1196         }
1197     }
1198 
1199     /**
1200      * Remove a session id from the table.
1201      *
1202      * @param id
1203      * @throws SQLException
1204      */
1205     private void delete (String id)
1206     throws SQLException
1207     {
1208         try (Connection connection = getConnection();
1209                 PreparedStatement statement = connection.prepareStatement(_deleteId))
1210         {
1211             connection.setAutoCommit(true);
1212             statement.setString(1, id);
1213             statement.executeUpdate();
1214         }
1215     }
1216 
1217 
1218     /**
1219      * Check if a session id exists.
1220      *
1221      * @param id
1222      * @return
1223      * @throws SQLException
1224      */
1225     private boolean exists (String id)
1226     throws SQLException
1227     {
1228         try (Connection connection = getConnection();
1229                 PreparedStatement statement = connection.prepareStatement(_queryId))
1230         {
1231             connection.setAutoCommit(true);
1232             statement.setString(1, id);
1233             try (ResultSet result = statement.executeQuery())
1234             {
1235                 return result.next();
1236             }
1237         }
1238     }
1239 
1240     /**
1241      * Look for sessions in the database that have expired.
1242      *
1243      * We do this in the SessionIdManager and not the SessionManager so
1244      * that we only have 1 scavenger, otherwise if there are n SessionManagers
1245      * there would be n scavengers, all contending for the database.
1246      *
1247      * We look first for sessions that expired in the previous interval, then
1248      * for sessions that expired previously - these are old sessions that no
1249      * node is managing any more and have become stuck in the database.
1250      */
1251     private void scavenge ()
1252     {
1253         Set<String> candidateIds = getAllCandidateExpiredSessionIds();
1254         
1255         Connection connection = null;
1256         try
1257         {
1258             if (LOG.isDebugEnabled())
1259                 LOG.debug(getWorkerName()+"- Scavenge sweep started at "+System.currentTimeMillis());
1260             if (_lastScavengeTime > 0)
1261             {
1262                 connection = getConnection();
1263                 connection.setAutoCommit(true);
1264                 Set<String> expiredSessionIds = new HashSet<String>();
1265                 
1266                 
1267                 //Pass 1: find sessions for which we were last managing node that have just expired since last pass
1268                 long lowerBound = (_lastScavengeTime - _scavengeIntervalMs);
1269                 long upperBound = _lastScavengeTime;
1270                 if (LOG.isDebugEnabled())
1271                     LOG.debug (getWorkerName()+"- Pass 1: Searching for sessions expired between "+lowerBound + " and "+upperBound);
1272 
1273                 try (PreparedStatement statement = connection.prepareStatement(_selectBoundedExpiredSessions))
1274                 {
1275                     statement.setString(1, getWorkerName());
1276                     statement.setLong(2, lowerBound);
1277                     statement.setLong(3, upperBound);
1278                     try (ResultSet result = statement.executeQuery())
1279                     {
1280                         while (result.next())
1281                         {
1282                             String sessionId = result.getString(_sessionTableSchema.getIdColumn());
1283                             expiredSessionIds.add(sessionId);
1284                             if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
1285                         }
1286                     }
1287                 }
1288                 scavengeSessions(candidateIds, expiredSessionIds, false);
1289 
1290 
1291                 //Pass 2: find sessions that have expired a while ago for which this node was their last manager
1292                 try (PreparedStatement selectExpiredSessions = connection.prepareStatement(_selectExpiredSessions))
1293                 {
1294                     expiredSessionIds.clear();
1295                     upperBound = _lastScavengeTime - (2 * _scavengeIntervalMs);
1296                     if (upperBound > 0)
1297                     {
1298                         if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 2: Searching for sessions expired before "+upperBound);
1299                         selectExpiredSessions.setLong(1, upperBound);
1300                         try (ResultSet result = selectExpiredSessions.executeQuery())
1301                         {
1302                             while (result.next())
1303                             {
1304                                 String sessionId = result.getString(_sessionTableSchema.getIdColumn());
1305                                 String lastNode = result.getString(_sessionTableSchema.getLastNodeColumn());
1306                                 if ((getWorkerName() == null && lastNode == null) || (getWorkerName() != null && getWorkerName().equals(lastNode)))
1307                                     expiredSessionIds.add(sessionId);
1308                                 if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId+" last managed by "+getWorkerName());
1309                             }
1310                         }
1311                         scavengeSessions(candidateIds, expiredSessionIds, false);
1312                     }
1313 
1314 
1315                     //Pass 3:
1316                     //find all sessions that have expired at least a couple of scanIntervals ago
1317                     //if we did not succeed in loading them (eg their related context no longer exists, can't be loaded etc) then
1318                     //they are simply deleted
1319                     upperBound = _lastScavengeTime - (3 * _scavengeIntervalMs);
1320                     expiredSessionIds.clear();
1321                     if (upperBound > 0)
1322                     {
1323                         if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Pass 3: searching for sessions expired before "+upperBound);
1324                         selectExpiredSessions.setLong(1, upperBound);
1325                         try (ResultSet result = selectExpiredSessions.executeQuery())
1326                         {
1327                             while (result.next())
1328                             {
1329                                 String sessionId = result.getString(_sessionTableSchema.getIdColumn());
1330                                 expiredSessionIds.add(sessionId);
1331                                 if (LOG.isDebugEnabled()) LOG.debug ("Found expired sessionId="+sessionId);
1332                             }
1333                         }
1334                         scavengeSessions(candidateIds, expiredSessionIds, true);
1335                     }
1336                 }
1337                 
1338                 //Tell session managers to check remaining sessions in memory that may have expired 
1339                 //but are no longer in the database
1340                 scavengeSessions(candidateIds);
1341             }
1342         }
1343         catch (Exception e)
1344         {
1345             if (isRunning())
1346                 LOG.warn("Problem selecting expired sessions", e);
1347             else
1348                 LOG.ignore(e);
1349         }
1350         finally
1351         {
1352             _lastScavengeTime=System.currentTimeMillis();
1353             if (LOG.isDebugEnabled()) LOG.debug(getWorkerName()+"- Scavenge sweep ended at "+_lastScavengeTime);
1354             if (connection != null)
1355             {
1356                 try
1357                 {
1358                     connection.close();
1359                 }
1360                 catch (SQLException e)
1361                 {
1362                     LOG.warn(e);
1363                 }
1364             }
1365         }
1366     }
1367     
1368     
1369     /**
1370      * @param expiredSessionIds
1371      */
1372     private void scavengeSessions (Set<String> candidateIds, Set<String> expiredSessionIds, boolean forceDelete)
1373     {       
1374         Set<String> remainingIds = new HashSet<String>(expiredSessionIds);
1375         Set<SessionManager> managers = getAllSessionManagers();
1376         for (SessionManager m:managers)
1377         {
1378             Set<String> successfullyExpiredIds = ((JDBCSessionManager)m).expire(expiredSessionIds);
1379             if (successfullyExpiredIds != null)
1380             {
1381                 remainingIds.removeAll(successfullyExpiredIds);
1382                 candidateIds.removeAll(successfullyExpiredIds);
1383             }
1384         }
1385     
1386 
1387         //Any remaining ids are of those sessions that no context removed
1388         if (!remainingIds.isEmpty() && forceDelete)
1389         {
1390             LOG.info("Forcibly deleting unrecoverable expired sessions {}", remainingIds);
1391             try
1392             {
1393                 //ensure they aren't in the local list of in-use session ids
1394                 synchronized (_sessionIds)
1395                 {
1396                     _sessionIds.removeAll(remainingIds);
1397                 }
1398                 
1399                 cleanExpiredSessionIds(remainingIds);
1400             }
1401             catch (Exception e)
1402             {
1403                 LOG.warn("Error removing expired session ids", e);
1404             }
1405         }
1406     }
1407     
1408     /**
1409      * These are the session ids that the session managers thought had 
1410      * expired, but were not expired in the database. This could be
1411      * because the session is live on another node, or that the
1412      * session no longer exists in the database because some other
1413      * node removed it.
1414      * @param candidateIds
1415      */
1416     private void scavengeSessions (Set<String> candidateIds)
1417     {
1418         if (candidateIds.isEmpty())
1419             return;
1420         
1421         
1422         Set<SessionManager> managers = getAllSessionManagers();
1423         
1424         for (SessionManager m:managers)
1425         {
1426             //tell the session managers to check the sessions that have expired in memory
1427             //if they are no longer in the database, they should be removed
1428             ((JDBCSessionManager)m).expireCandidates(candidateIds);
1429         }
1430     }
1431     
1432     private Set<String>  getAllCandidateExpiredSessionIds()
1433     {
1434         HashSet<String> candidateIds = new HashSet<>();
1435         
1436         Set<SessionManager> managers = getAllSessionManagers();
1437         
1438         for (SessionManager m:managers)
1439         {
1440             candidateIds.addAll(((JDBCSessionManager)m).getCandidateExpiredIds());
1441         }
1442         
1443         return candidateIds;
1444     }
1445     
1446     
1447     private Set<SessionManager> getAllSessionManagers()
1448     {
1449         HashSet<SessionManager> managers = new HashSet<>();
1450     
1451         Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
1452         for (int i=0; contexts!=null && i<contexts.length; i++)
1453         {
1454             SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
1455             if (sessionHandler != null)
1456             {
1457                 SessionManager manager = sessionHandler.getSessionManager();
1458                 if (manager != null && manager instanceof JDBCSessionManager)
1459                     managers.add(manager);
1460             }
1461         }
1462         return managers;
1463     }
1464 
1465 
1466    
1467     
1468     private void cleanExpiredSessionIds (Set<String> expiredIds)
1469     throws Exception
1470     {
1471         if (expiredIds == null || expiredIds.isEmpty())
1472             return;
1473         
1474         String[] ids = expiredIds.toArray(new String[expiredIds.size()]);
1475         try (Connection con = getConnection())
1476         {
1477             con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
1478             con.setAutoCommit(false);
1479 
1480             int start = 0;
1481             int end = 0;
1482             int blocksize = _deleteBlockSize;
1483             int block = 0;
1484        
1485             try (Statement statement = con.createStatement())
1486             {
1487                 while (end < ids.length)
1488                 {
1489                     start = block*blocksize;
1490                     if ((ids.length -  start)  >= blocksize)
1491                         end = start + blocksize;
1492                     else
1493                         end = ids.length;
1494 
1495                     //take them out of the sessionIds table
1496                     statement.executeUpdate(fillInClause("delete from "+_sessionIdTableSchema.getTableName()+" where "+_sessionIdTableSchema.getIdColumn()+" in ", ids, start, end));
1497                     //take them out of the sessions table
1498                     statement.executeUpdate(fillInClause("delete from "+_sessionTableSchema.getTableName()+" where "+_sessionTableSchema.getIdColumn()+" in ", ids, start, end));
1499                     block++;
1500                 }
1501             }
1502             catch (Exception e)
1503             {
1504                 con.rollback();
1505                 throw e;
1506             }
1507             con.commit();
1508         }
1509     }
1510 
1511     
1512     
1513     /**
1514      * 
1515      * @param sql
1516      * @param atoms
1517      * @throws Exception
1518      */
1519     private String fillInClause (String sql, String[] literals, int start, int end)
1520     throws Exception
1521     {
1522         StringBuffer buff = new StringBuffer();
1523         buff.append(sql);
1524         buff.append("(");
1525         for (int i=start; i<end; i++)
1526         {
1527             buff.append("'"+(literals[i])+"'");
1528             if (i+1<end)
1529                 buff.append(",");
1530         }
1531         buff.append(")");
1532         return buff.toString();
1533     }
1534     
1535     
1536     
1537     private void initializeDatabase ()
1538     throws Exception
1539     {
1540         if (_datasource != null)
1541             return; //already set up
1542         
1543         if (_jndiName!=null)
1544         {
1545             InitialContext ic = new InitialContext();
1546             _datasource = (DataSource)ic.lookup(_jndiName);
1547         }
1548         else if ( _driver != null && _connectionUrl != null )
1549         {
1550             DriverManager.registerDriver(_driver);
1551         }
1552         else if (_driverClassName != null && _connectionUrl != null)
1553         {
1554             Class.forName(_driverClassName);
1555         }
1556         else
1557             throw new IllegalStateException("No database configured for sessions");
1558     }
1559     
1560    
1561 }