Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/lutris/appserver/server/session/StandardSessionManager.java


1   /*
2    * Enhydra Java Application Server Project
3    * 
4    * The contents of this file are subject to the Enhydra Public License
5    * Version 1.1 (the "License"); you may not use this file except in
6    * compliance with the License. You may obtain a copy of the License on
7    * the Enhydra web site ( http://www.enhydra.org/ ).
8    * 
9    * Software distributed under the License is distributed on an "AS IS"
10   * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 
11   * the License for the specific terms governing rights and limitations
12   * under the License.
13   * 
14   * The Initial Developer of the Enhydra Application Server is Lutris
15   * Technologies, Inc. The Enhydra Application Server and portions created
16   * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
17   * All Rights Reserved.
18   * 
19   * Contributor(s):
20   * 
21   * $Id: StandardSessionManager.java,v 1.42.2.4.4.1 2000/10/19 17:59:06 jasona Exp $
22   */
23  
24  package com.lutris.appserver.server.session;
25  
26  import java.security.*;
27  import java.io.*;
28  import java.util.*;
29  import java.lang.reflect.Constructor;
30  import com.lutris.util.*;
31  import com.lutris.logging.*;
32  import com.lutris.appserver.server.*;
33  import com.lutris.appserver.server.user.User;
34  import com.lutris.appserver.server.session.persistent.*;
35  
36  /**
37   * This session manager maintains the mapping between session keys
38   * and sessions.  It generates secure session keys that are safe to use
39   * as cookie values in web applications.  It also manages the life
40   * of a session.  If a session is not used (touched) for a preconfigured
41   * amount of time, then it is expired and removed from the session
42   * manager.  The following parameters can be used to configure
43   * the session manager.  They should be grouped together in a section,
44   * normally <code>SessionManager</code>, which is specified to
45   * the constructor.<p>
46   * <ul>
47   * <li><code>SessionLifetimeMax: {int}</code><p>
48   *
49   *     The maximum number of minutes a session is valid.  If this
50   *     value us zero, then there is no lifetime limit.
51   *     This parameter must be set in the configuration file.<p>
52   *
53   * <li><code>SessionIdleTimeMax: {int}</code><p>
54   *
55   *     Maximum number of minutes a session may be idle before
56   *     the session is expired (deleted).  If this value
57   *     is less than or equal to zero, then the session may
58   *     be idle indefinitly.<p>
59   *
60   * <li><code>SessionNoUserIdleTimeMax: {int}</code><p>
61   *
62   *     Maximum number of minutes a session that does not have a
63   *     <CODE>User</CODE> object assocaited with it may be idle before
64   *     the session is expired (deleted).  If this value
65   *     is less than or equal to zero, then the session may
66   *     be idle indefinitly.  Default is the same as <code>MaxIdleTime</code><p>
67   *
68   * <li><code>IdleScanInterval: {int}</code><p>
69   *
70   *     How often, in seconds, the session manager tests if
71   *     any sessions should be expired.
72   *     This parameter must be greater than zero.<p>
73   *
74   * <li><code>RandomizerIntervals: {int [,int]...}</code><p>
75   *
76   *     The set of varying intervals to use for adding user-generated entropy
77   *     to the random number generator.  It is a good idea to use several
78   *     different prime numbers.  These intervals are in seconds.<p>
79   *
80   * <li><code>SessionHome.Mode: {BASIC | PAGE_TO_DISK | PAGE_TO_DB | CUSTOM}</code><p>
81   *
82   *     Indicates the mode (StandardSessionHome interface) that the standard
83   *     session manager will use.
84   *     In 'BASIC' mode the standard session manager uses the <code>BasicSessionHome</code>
85   *     to manage sessions.  In 'PAGE_TO_DISK' the standard session manager uses
86   *     the <code>DiskPagedSessionHome</code> to manage sessions.  In 'PAGE_TO_DB'
87   *     the standard session manager used the <code>PersistentSessionHome</code>
88   *     to manage sessions.  In 'CUSTOM' the application specifies the classes
89   *     that should be loaded and provide the <code>StandardSessionHome</code>
90   *     implementation.  If set to 'CUSTOM' then the settings
91   *     <code>SessionHome.Class</code> and <code>SessionUserTable.Class</code>
92   *     should be set.
93   *
94   * <li><code>SessionHome.Class: {String}</code><p>
95   *
96   *     The class that implements the StandardSessionHome interface.  This
97   *     class is loaded by StandardSessionManager and used to managed
98   *     the collection of sessions.  If this option is not specified then
99   *     BasicSessionHome is used.  The SessionHome that is
100  *     loaded by the session manager is expected to contain a consturctor
101  *     that takes the following arguments:
102  *     <code>SessionHome(StandardSessionManager mgr,
103  *                       com.lutris.util.Config config,
104  *                       ClassLoader loader)</code><p>
105  *
106  * <li><code>SessionUserTable.Class: {String}</code><p>
107  *
108  *     The class that implements the StandardSessiondUserTable interface.  This
109  *     class is loaded by StandardSessionManager and used to managed
110  *     the session to user relationships.  If this option is not specified then
111  *     BasicSessionUserTable is used.  The SessionUserTable that is
112  *     loaded by the session manager is expected to contain a consturctor
113  *     that takes the following arguments:
114  *     <code>SessionUserTable(com.lutris.util.Config config)</code><p>
115  *
116  * </ul>
117  *
118  * @version  $Revision: 1.42.2.4.4.1 $
119  * @author  John Marco
120  * @author  Shawn McMurdo
121  * @author      Kyle Clark
122  * @author      Mark Diekhans
123  * @see         com.lutris.util.Config
124  * @see         StandardSessionHome
125  * @see         StandardSessionUserTable
126  * @see         BasicSessionHome
127  * @see         DiskPagedSessionHome
128  * @see         com.lutris.appserver.server.session.persistent.PersistentSessionHome
129  */
130 public class StandardSessionManager implements SessionManager,
131                                                StandardSessionIdleHandler {
132 
133     /**
134      * Represents the current session management mode.
135      */
136     protected int mode;
137 
138     /**
139      * Indicates that the session manager is using
140      * the basic session home interface to manage sessions.
141      * @see #getMode
142      * @see com.lutris.appserver.server.session.BasicSessionHome
143      */
144     public static final int MODE_BASIC = 1;
145 
146     /**
147      * Indicates that the session manager is using
148      * the page to disk home interface to manage sessions.
149      * @see #getMode
150      * @see com.lutris.appserver.server.session.DiskPagedSessionHome
151      */
152     public static final int MODE_PAGE_TO_DISK = 2;
153 
154     /**
155      * Indicates that the session manager is using
156      * the page to database home interface to manage sessions.
157      * @see #getMode
158      * @see com.lutris.appserver.server.session.persistent.PersistentSessionHome
159      */
160     public static final int MODE_PAGE_TO_DB = 3;
161 
162     /**
163      * Indicates that the session manager is using
164      * the custom home interface to manage sessions.
165      * @see #getMode
166      */
167     public static final int MODE_CUSTOM = 4;
168 
169     /**
170      * Indicates that url encoding of session ids is never preformed.
171      * @see #getEncodeUrlState
172      **/
173     public static final String ENCODE_URL_NEVER = "Never";
174 
175     /**
176      * Indicates that url encoding of session ids is always preformed.
177      * @see #getEncodeUrlState
178      **/
179     public static final String ENCODE_URL_ALWAYS = "Always";
180 
181     /**
182      * Indicates that url encoding of session ids is preformed only when
183      * cookies are disabled on the client browser.  This is automatically
184      * detected.
185      * @see #getEncodeUrlState
186      **/
187     public static final String ENCODE_URL_AUTO = "Auto";
188 
189     /**
190      * Default maximum session life time, in seconds.  Zero if there is no
191      * limit.  A derived <CODE>SessionManager</CODE> may override this value
192      * to use the default expiration logic or override
193      * <CODE>isSessionExpired</CODE> to define custom expiration logic.
194      *
195      * @see lutris.session.StandardSessionManager#getMaxSessionLifeTime
196      */
197     protected static long defaultMaxSessionLifeTime = 0;
198 
199     /**
200      * Default maximum session idle time, in seconds.  A derived
201      * <CODE>SessionManager</CODE> may override this value to use the default
202      * expiration logic or override <CODE>isSessionExpired</CODE>
203      * to define custom expiration logic.
204      * A value less-than or equal to zero disables idle checking.
205      * Default value is 30 minutes.
206      *
207      * @see #getMaxSessionIdleTime
208      */
209     protected static long defaultMaxSessionIdleTime = 30*60;
210 
211     /**
212      * Indicates url encoding status.  Assumes that
213      * comms.response.writeHTML(HTMLDocument doc) is used to commit the
214      * response to the client.  The default is to Auto.<br>
215      * <code>Never</code> indicates urls are never encoded with session keys.
216      * This indicates that session cookies have to be used or no session state
217      * can be maintained.<br>
218      * <code>Always</code> indicates that urls are always encoded with session
219      * keys.  Session cookies are never used.<br>
220      * <code>Auto</code> indicates that session cookies will be if available.
221      * If not, urls will automatically be encoded.<br>
222      * @see #getEncodeUrlState
223      */
224     protected static String defaultEncodeUrlState = "Auto";
225 
226     /**
227      * Default interval, in seconds, to scan for sessions to expire.
228      * A derived <CODE>SessionManager</CODE> may override this value.
229      * Default value is 30 seconds.
230      */
231     protected static long defaultIdleScanInterval = 30;
232 
233     /**
234      * Default list of randomize key generator time intervals, in seconds.
235      * A list of prime numbers is recommended.
236      * A derived <CODE>SessionManager</CODE> may override this value.
237      */
238     protected static long[] defaultRandomizerIntervals = {
239         301, 1001, 5003
240     };
241 
242     /**
243      * The name of the config variable for the max session lifetime.
244      */
245     public static final String CFG_LIFE = "SessionLifetimeMax";
246 
247     /**
248      * The name of the config variable for the max session idle time.
249      */
250     public static final String CFG_IDLE = "SessionIdleTimeMax";
251 
252     /**
253      * The name of the config variable for the url encoding state
254      */
255     public static final String CFG_ENCODE_URL_STATE = "SessionEncodeUrlState";
256 
257     /**
258      * The name of the config variable for the max idle time for sessions
259      * with no user.
260      */
261     public static final String CFG_NOUSER_IDLE = "SessionNoUserIdleTimeMax";
262 
263     /**
264      * The name of the config variable for the interval between scans for
265      * idle sessions.
266      */
267     public static final String CFG_SCAN = "IdleScanInterval";
268 
269     /**
270      * The name of the config variable for the interval between introduction
271      * of randomness to the session key generator.
272      */
273     public static final String CFG_RANDOM = "RandomizerIntervals";
274 
275     /**
276      * The name of the config variable for the session home settings.
277      */
278     public static final String CFG_SESSION_HOME = "SessionHome";
279 
280     /**
281      * The name of the config variable for the session home type.
282      */
283     public static final String CFG_SESSION_HOME_TYPE = "SessionHome.Mode";
284 
285     /**
286      * Indicates that a sesson is still active.
287      *
288      * @see #sessionDeleted
289      */
290     public static final int SESSION_ACTIVE = 0;
291 
292     /**
293      * Indicates that a sesson was deleted due to max time
294      * being exceeded.
295      *
296      * @see #sessionDeleted
297      */
298     public static final int SESSION_MAX_TIME = 1;
299 
300     /**
301      * Indicates that a sesson was deleted due to idle time
302      * being exceeded.
303      *
304      * @see #sessionDeleted
305      */
306     public static final int SESSION_IDLE_EXPIRE = 2;
307 
308     /**
309      * Indicates that a sesson was explictly deleted.
310      *
311      * @see #sessionDeleted
312      */
313     public static final int SESSION_EXPLICT_DELETE = 3;
314 
315     /**
316      * Table of currently active user sessions is managed
317      * by the session home.  Different implementations of
318      * StandardSessionHome can be loaded by this manager.  A specific
319      * implementation can manage failover persistence, paging, etc.
320      */
321     private StandardSessionHome sessionHome;
322 
323     /**
324      * Table of users objects.  This table tracks the number of simultaneous
325      * sessions a user currently has active. Table entries are of type
326      * <CODE>Vector</CODE>, constaining Session objects, keyed by User
327      * object reference.
328      */
329     private StandardSessionUserTable sessionUserTable;
330 
331     // The maximum number of sessions (highwater mark).
332     private int maxSessions;
333     private Date maxSessionsDate;
334 
335     /**
336      * Object that generates random session keys.  Manages a thread to
337      * for increase randomization.
338      *
339      * @see StandardSessionKeyGen
340      */
341     private StandardSessionKeyGen keyGenerator;
342 
343     /**
344      * Maximum session life time, in seconds.  Zero if there is no
345      * limit.
346      *
347      * @see lutris.session.StandardSessionManager#getMaxSessionLifeTime
348      */
349     private long maxSessionLifeTime;
350 
351     /**
352      * Maximum session idle time, in seconds.
353      * A value less-than or equal to zero disables idle checking.
354      *
355      * @see #getMaxSessionIdleTime
356      */
357     protected long maxSessionIdleTime;
358 
359     /**
360      * The url encoding state.  Either <code>Never</code>
361      * <code>Always</code> <code>Auto</code>.
362      */
363     protected String encodeUrlState;
364 
365     /**
366      * Background thread that keeps track of session idle time.
367      * Sessions that have been idle for more than a configurable time
368      * limit are terminated by this thread.
369      *
370      * @see StandardSessionIdleTimer
371      */
372     private StandardSessionIdleTimer idleTimer;
373 
374     /**
375      * Maximum session idle time, in seconds for sessions
376      * that don't have a <CODE>User</CODE> object associated with them.
377      * A value less-than or equal to zero disables idle checking.
378      *
379      * @see #getMaxNoUserSessionIdleTime
380      */
381     protected long maxNoUserSessionIdleTime;
382 
383     /**
384      * The time in seconds between scans for idle sessions to expire.
385      */
386     protected long scanInterval;
387 
388     /**
389      * Log channel for debugging messages.
390      */
391     LogChannel logChannel = null;
392 
393     /**
394      * The application using this session manager.
395      */
396     Application app = null;
397 
398     /**
399      * The class loader used by this app/context.
400      */
401 
402      ClassLoader classLoader;
403 
404     /**
405      * The supported session home types.
406      */
407     private static final String BASIC = "BASIC";
408     private static final String PAGE_TO_DISK = "PAGE_TO_DISK";
409     private static final String PAGE_TO_DB= "PAGE_TO_DB";
410     private static final String CUSTOM = "CUSTOM";
411 
412     /**
413      * The session home interface type being used.
414      */
415     String sessionHomeType = BASIC;
416 
417     /**
418      * Log configuration information.
419      */
420     private void logConfig() {
421         if (logChannel != null) {
422             logChannel.write(Logger.DEBUG,
423         "SessionManager." + CFG_LIFE + " = "
424         + maxSessionLifeTime + " sec");
425             logChannel.write(Logger.DEBUG,
426         "SessionManager." + CFG_IDLE + " = "
427         + maxSessionIdleTime + " sec");
428             logChannel.write(Logger.DEBUG,
429         "SessionManager." + CFG_NOUSER_IDLE + " = "
430         + maxNoUserSessionIdleTime + " sec");
431             logChannel.write(Logger.DEBUG,
432         "SessionManager." + CFG_SCAN + " = "
433         + scanInterval + " sec");
434             logChannel.write(Logger.DEBUG,
435         "SessionManager." + CFG_ENCODE_URL_STATE + " = "
436         + encodeUrlState);
437         }
438     }
439 
440     /**
441      * Creates a new <code>SessionManager</code> object.
442      * This constructor will first looks for the session manager
443      * configuration parameters that have the specified configuration
444      * prefix prepended to the standard session manager configuration
445      * option.<p>
446      *
447      * @param app the application associate with this session
448      *   manager.
449      * @param config Object parsed from configuration file.  This should be
450      *   for the section constaining the session manager configuration.
451      * @param sessionMgrLogChannel If not <CODE>null</CODE>, channel to
452      *   log debugging information to.
453      * @exception ConfigException signifies a problem in the
454      *   configuration file.
455      * @exception SessionException
456      *   if all classes (Home and UserTable) couldn't be loaded
457      *   by the session manager.
458      */
459     public StandardSessionManager(Application application,
460                                   Config config,
461                                   LogChannel sessionMgrLogChannel)
462             throws ConfigException, SessionException {
463 
464 
465        this(application.getClass().getClassLoader(),config,sessionMgrLogChannel);
466        app = application;
467     }
468 
469     public StandardSessionManager(ClassLoader classLoader,
470                                   Config config,
471                                   LogChannel sessionMgrLogChannel)
472             throws ConfigException, SessionException {
473 
474         this.classLoader = classLoader;
475         logChannel = sessionMgrLogChannel;
476 
477         // High-water mark
478         maxSessions = 0;
479         maxSessionsDate = new Date();
480 
481         /*
482          * Config file specifies minutes, but the values are kept internally
483          * as seconds.
484          */
485 
486         // Session lifetime
487         if (config.containsKey(CFG_LIFE)) {
488             maxSessionLifeTime = config.getLong(CFG_LIFE) * 60;
489         } else if (config.containsKey("Lifetime")) {
490       // backwards compatability
491             maxSessionLifeTime = config.getLong("Lifetime") * 60;
492         } else {
493             maxSessionLifeTime = defaultMaxSessionLifeTime;
494         }
495 
496         // Idle time.
497         if (config.containsKey(CFG_IDLE)) {
498             maxSessionIdleTime = config.getLong(CFG_IDLE) * 60;
499         } else if (config.containsKey("MaxIdleTime")) {
500       // backwards compatability
501             maxSessionIdleTime = config.getLong("MaxIdleTime") * 60;
502         } else {
503             maxSessionIdleTime = defaultMaxSessionIdleTime;
504         }
505 
506         // Url encoding state
507         if (config.containsKey(CFG_ENCODE_URL_STATE)) {
508             encodeUrlState = config.getString(CFG_ENCODE_URL_STATE);
509         } else if (config.containsKey("EncodeUrlState")) {
510       // backwards compatability
511             encodeUrlState = config.getString("EncodeUrlState");
512         } else {
513             encodeUrlState = defaultEncodeUrlState;
514         }
515   if (!encodeUrlState.equalsIgnoreCase(ENCODE_URL_NEVER) &&
516       !encodeUrlState.equalsIgnoreCase(ENCODE_URL_ALWAYS) &&
517       !encodeUrlState.equalsIgnoreCase(ENCODE_URL_AUTO)) {
518       throw new ConfigException("EncodeUrlState must be one of the following: " + ENCODE_URL_NEVER + ", " + ENCODE_URL_ALWAYS + ", or " + ENCODE_URL_AUTO + ".");
519   }
520 
521         if (config.containsKey(CFG_NOUSER_IDLE)) {
522             maxNoUserSessionIdleTime = config.getLong(CFG_NOUSER_IDLE) * 60;
523         } else if (config.containsKey("MaxNoUserIdleTime")) {
524       // backwards compatability
525             maxNoUserSessionIdleTime = config.getLong("MaxNoUserIdleTime") * 60;
526         } else {
527             maxNoUserSessionIdleTime = maxSessionIdleTime;
528         }
529 
530         scanInterval = defaultIdleScanInterval;
531         if (config.containsKey(CFG_SCAN)){
532             scanInterval = config.getLong(CFG_SCAN);
533         } else if (config.containsKey("IdleScanInterval")){
534       // backwards compatability
535             scanInterval = config.getLong("IdleScanInterval");
536         }
537         if (scanInterval <= 0) {
538            throw new ConfigException("IdleScanInterval must be greater than zero.");
539         }
540   idleTimer = new StandardSessionIdleTimer(this, app, scanInterval);
541 
542         if (config.containsKey(CFG_SESSION_HOME_TYPE)) {
543             sessionHomeType = config.getString(CFG_SESSION_HOME_TYPE);
544         }
545         if (!sessionHomeType.equalsIgnoreCase(BASIC)
546             && !sessionHomeType.equalsIgnoreCase(PAGE_TO_DISK)
547             && !sessionHomeType.equalsIgnoreCase(PAGE_TO_DB)
548             && !sessionHomeType.equalsIgnoreCase(CUSTOM)) {
549             throw new ConfigException("Invalid " + CFG_SESSION_HOME_TYPE
550                                       + ": '"  + sessionHomeType + "'");
551         }
552 
553         // Session Home
554         sessionHome = loadSessionHome(config);
555 
556         // Session User Table
557         sessionUserTable = loadSessionUserTable(config);
558 
559         // Random key generator.
560         long[] intervals = config.getLongs(CFG_RANDOM,
561                                            defaultRandomizerIntervals);
562         if (intervals.length == 0) {
563            throw new ConfigException (CFG_RANDOM + " must contain some values.");
564         }
565         for (int i = 0; i < intervals.length; i++) {
566             if (intervals[i] <= 0) {
567                 throw new ConfigException (CFG_RANDOM + " must contain positive integers.");
568             }
569         }
570   keyGenerator = new StandardSessionKeyGen(intervals);
571 
572         // Start threads.
573   keyGenerator.start();
574   idleTimer.start();
575 
576         logConfig();
577     }
578 
579     /**
580      * Loads the StandardSessionHome used by this session manager.
581      *
582      * @param config
583      *   This session manager's config section.
584      * @exception
585      *   ConfigException if the configuration is invalid.
586      * @exception
587      *   SessionException if the StandardSessionHome could not be loaded.
588      */
589     protected StandardSessionHome loadSessionHome(Config config)
590         throws ConfigException, SessionException {
591         try {
592             StandardSessionHome sessionHome = null;
593             Config homeConfig = null;
594             if (config.containsKey(CFG_SESSION_HOME)) {
595                 homeConfig = (Config)config.getSection(CFG_SESSION_HOME);
596             } else {
597                 homeConfig = new Config();
598             }
599             if (sessionHomeType.equalsIgnoreCase(CUSTOM)) {
600                 if (homeConfig.containsKey("Class")) {
601                     String homeClassName = homeConfig.getString("Class");
602                     try {
603                         Class [] paramTypes = new Class[3];
604                         Object [] args = new Object[3];
605                         paramTypes[0] = Class.forName("com.lutris.appserver.server.session.StandardSessionManager");
606                         paramTypes[1] = Class.forName("com.lutris.util.Config");
607                         paramTypes[2] = Class.forName("java.lang.ClassLoader");
608                         args[0] = this;
609                         args[1] = homeConfig;
610                         args[2] = classLoader;
611                         Constructor c = Class.forName(homeClassName).getConstructor(paramTypes);
612                         sessionHome = (StandardSessionHome)c.newInstance(args);
613                         if (logChannel != null) {
614                             logChannel.write(Logger.DEBUG, "SessionMgr: "
615                                              + "StandardSessionHome: " + homeClassName);
616                         }
617                     } catch (Exception e) {
618                         throw new SessionException("Unable to load " + homeClassName, e);
619                     }
620                 }
621                 mode = MODE_CUSTOM;
622             } else if (sessionHomeType.equalsIgnoreCase(PAGE_TO_DISK)) {
623                 sessionHome = new DiskPagedSessionHome(this, homeConfig,classLoader);
624                 mode = MODE_PAGE_TO_DISK;
625             } else if (sessionHomeType.equalsIgnoreCase(PAGE_TO_DB)) {
626                 sessionHome = new PersistentSessionHome(this, homeConfig,
627                                                         classLoader);
628                 mode = MODE_PAGE_TO_DB;
629             } else {
630                 sessionHome = new BasicSessionHome(this, homeConfig);
631                 mode = MODE_BASIC;
632             }
633             if (logChannel != null) {
634                 logChannel.write(Logger.DEBUG, "SessionMgr: "
635                                  + "StandardSessionHome: " + sessionHomeType + "\n");
636             }
637             return sessionHome;
638         } catch (KeywordValueException e) {
639             e.printStackTrace();
640             throw new ConfigException("SessionMgr: unable to load StandardSessionHome: "
641                                       + e);
642         }
643     }
644 
645     /**
646      * Loads the StandardSessiondUserTable used by this session manager.
647      *
648      * @param config
649      *   This session manager's config section.
650      * @exception
651      *   ConfigException if the StandardSessionHome could not be loaded.
652      */
653     protected StandardSessionUserTable loadSessionUserTable(Config config)
654         throws ConfigException, SessionException {
655         try {
656             StandardSessionUserTable sessionUserTable = null;
657             Config userTableConfig = null;
658             if (config.containsKey("SessionUserTable")) {
659                 userTableConfig = (Config)config.getSection("SessionUserTable");
660             } else {
661                 userTableConfig = new Config();
662             }
663             if (sessionHomeType.equalsIgnoreCase(CUSTOM)) {
664                 String tableClassName = userTableConfig.getString("Class");
665                 try {
666                     Class [] paramTypes = new Class[1];
667                     Object [] args = new Object[1];
668                     paramTypes[0] = Class.forName("com.lutris.util.Config");
669                     args[0] = userTableConfig;
670                     Constructor c = Class.forName(tableClassName).getConstructor(paramTypes);
671                     sessionUserTable = (StandardSessionUserTable)c.newInstance(args);
672                     if (logChannel != null) {
673                         logChannel.write(Logger.DEBUG, "SessionMgr: "
674                                          + "StandardSessionUserTable: " + tableClassName);
675                     }
676                 } catch (NoSuchMethodException e) {
677                     throw new SessionException("Unable to load " + tableClassName + ": "
678                                                + "Constructor not found.", e);
679                 } catch (Exception e) {
680                     throw new SessionException("Unable to create instance of " + tableClassName, e);
681                 }
682             } else if (sessionHomeType.equalsIgnoreCase(PAGE_TO_DISK)) {
683                 sessionUserTable = new PagedSessionUserTable(userTableConfig);
684             } else if (sessionHomeType.equalsIgnoreCase(PAGE_TO_DB)) {
685                 sessionUserTable = new PersistentSessionUserTable(userTableConfig);
686             } else {
687                 sessionUserTable = new BasicSessionUserTable(userTableConfig);
688             }
689             return sessionUserTable;
690         } catch (KeywordValueException e) {
691             throw new ConfigException("SessionMgr: unable to load StandardSessionUserTable: "
692                                       + e);
693         }
694     }
695 
696     /**
697      * Shutdown this session manager, stopping threads that are
698      * associated with it.
699      */
700     public synchronized void shutdown() {
701         if (keyGenerator != null) {
702             keyGenerator.shutdown();
703             keyGenerator = null;
704         }
705         if (idleTimer != null) {
706             idleTimer.shutdown();
707             idleTimer = null;
708         }
709         if (sessionHome != null) {
710             sessionHome.shutdown();
711             sessionHome = null;
712         }
713         if (sessionUserTable != null) {
714             sessionUserTable.shutdown();
715             sessionUserTable = null;
716         }
717     }
718 
719     /**
720      * Allocate a new <CODE>StandardSession</CODE> object.  This maybe
721      * overridden by derived session managers to allocated a
722      * <CODE>Session</CODE> object derived from <CODE>StandardSession</CODE>.
723      * This is called by <CODE>createSession</CODE>.
724      *
725      * @param   sessionKey The session key to associate with the
726      *          session.
727      * @return  session The new <code>StandardSession</code> object.
728      * @see  StandardSession
729      * @deprecated The instance of StandardSessionHome should be replaced
730      *   instead of extending this method.
731      */
732     protected StandardSession newSession(String sessionKey)
733         throws CreateSessionException, SessionException {
734         return (StandardSession) sessionHome.createSession(sessionKey);
735     }
736 
737     /**
738      * Create a new <CODE>Session</CODE> object and associate a unique random
739      * key.  No <CODE>User</CODE> is initially associated with
740      * (<CODE>Session.getUser()</CODE> returns <CODE>null</CODE>/
741      *
742      * @return  session The new <code>Session</code> object.
743      * @exception SessionException
744      *   if the session cannot be created.
745      * @see  Session
746      */
747     public Session createSession() throws SessionException {
748   return createSession(null);
749     }
750 
751     /**
752      * Create a new <CODE>Session</CODE> object and associate a unique random
753      * key.  No <CODE>User</CODE> is initially associated with
754      * (<CODE>Session.getUser()</CODE> returns <CODE>null</CODE>/
755      *
756      * @param ipPortToken
757      *        The base64 encoded IP and Port number to include in session key
758      * @return  session The new <code>Session</code> object.
759      * @exception SessionException
760      *   if the session cannot be created.
761      * @see  Session
762      */
763     public Session createSession(String ipPortToken)
764   throws SessionException {
765         StandardSession session = null;
766   String sessionKey = null;
767 
768         /*
769          * Create a new key that does not already exist in
770          * the session table.  The odds of this loop making more than
771          * one pass are astronomically small, but not zero.
772          */
773         do {
774             try {
775                 sessionKey = keyGenerator.newSessionKey();
776     if (ipPortToken != null) {
777         sessionKey += ("(" + ipPortToken + ")");
778     }
779                 session = sessionHome.createSession(sessionKey);
780             } catch (DuplicateKeyException e) {
781                 // TODO - log message?
782                 session = null;
783             }
784         } while (session == null);
785 
786         int currentSize = sessionHome.size();
787         if (currentSize > maxSessions) {
788             maxSessions = currentSize;
789             maxSessionsDate = new Date();
790         }
791         keyGenerator.incrementRandomCounter();
792 
793         if (logChannel != null) {
794             logChannel.write(Logger.DEBUG, "SessionMgr: createSession: " + sessionKey);
795         }
796 
797         return session;
798     }
799 
800     /**
801      * Log a user being registered or unregistered.
802      */
803     private void logRegisterUser(Session session, String which) {
804         if (logChannel != null) {
805             String userName = null;
806             User user = session.getUser();
807             if (user != null) {
808                 userName = user.getName();
809             }
810             logChannel.write(Logger.DEBUG, "SessionMgr: "
811                              + which + ": " + session.getSessionKey() +
812                              " user = \"" + userName + "\"");
813         }
814     }
815 
816     /**
817      * Adds a session's user to the session-to-user table.
818      * This is called by the <CODE>Session</CODE> object to associate
819      * a session with a user.
820      *
821      * @param  session The now logged-in <code>Session</code> instance to
822      *       store in the session table.
823      * @see  Session
824      * @see  User
825      * @see  #unregisterUser
826      * @exception SessionException
827      *   if an error occurs.
828      */
829     protected synchronized void registerUser(Session session)
830         throws SessionException {
831         sessionUserTable.add(session.getSessionKey(), session.getUser());
832         logRegisterUser(session, "registerUser");
833     }
834 
835     /**
836      * Removes a session's user from the session-to-user table.
837      * This is called by the <CODE>Session</CODE> object to disassoicate
838      * a user from a session.
839      *
840      * @param  session The now <code>Session</code> instance
841      *       the user is being logged out from.
842      * @see  Session
843      * @exception SessionException
844      *   if an error occurs.
845      */
846     protected synchronized void unregisterUser(Session session)
847         throws SessionException {
848         logRegisterUser(session, "unregisterUser");
849         sessionUserTable.remove(session.getSessionKey(), session.getUser());
850     }
851 
852     /**
853      * Determine if a session should be expired based on the idle
854      * time.  This method is designed to be overriden by derived classes that
855      * want to use different idle time checks.
856      *
857      * @param session Session to check.
858      * @return A reason code for the experation.
859      *  One of <CODE>SESSION_MAX_TIME</CODE>,
860      *  <CODE>SESSION_IDLE_EXPIRE</CODE>, or
861      *  <CODE>SESSION_ACTIVE</CODE>, to indicate that the session
862      *  is not expired.
863      *
864      * @see #sessionDeleted
865      * @see #maxSessionIdleTime
866      * @see #maxNoUserSessionIdleTime
867      */
868     protected int isSessionExpired(StandardSession session) {
869         long now = System.currentTimeMillis();
870         /*
871          * Has the session reached it's maximum age, regardless of user
872          * interaction? This is an optional feature.
873          */
874         long maxAge = session.getTimeExpires();
875         if ((maxAge > 0) && (now > maxAge)) {
876             return SESSION_MAX_TIME;
877         }
878 
879         /*
880          * Has the session been idle too long? There are two thresholds,
881          * for whether or not a user is logged in.
882          * Both are optional features.
883          */
884         long idle = now - session.getTimeLastUsed();
885         long maxIdle;
886         if (session.getUser() != null) {
887             maxIdle = session.getMaxIdleTime();
888         } else {
889             maxIdle = session.getMaxNoUserIdleTime();
890         }
891         if ((maxIdle > 0) && (idle > maxIdle)) {
892             return SESSION_IDLE_EXPIRE;
893         }
894 
895         /*
896          * The session is ok.
897          */
898         return SESSION_ACTIVE;
899     }
900 
901     /**
902      * Method called when a session is deleted.
903      * This method is designed to be overriden by derived classes that
904      * want need to take specific action, such as saving the sessions
905      * state.
906      * The default implementation of this method does nothing.
907      *
908      * @param session Session that is being delete.
909      * @param reason The reason the session was being deleted.
910      *  One of <CODE>SESSION_MAX_TIME</CODE>,
911      *  <CODE>SESSION_IDLE_EXPIRE</CODE>,
912      *  <CODE>SESSION_EXPLICT_DELETE</CODE>.
913      * @see #isSessionExpired
914      */
915     protected void sessionDeleted(Session session, int reason) {
916         // Default implementation does nothing.
917     }
918 
919     /**
920      * Does the actual work of deleting an entry from the session table.
921      *
922      * @param session The session to delete.
923      * @param reason The reason the session was being deleted.
924      * @exception SessionException
925      *   If the session cannot be deleted.
926      */
927     private void doDeleteSession(StandardSession session, int reason)
928         throws SessionException {
929         keyGenerator.incrementRandomCounter();
930 
931         if (logChannel != null) {
932             String userName = null;
933             User user = session.getUser();
934             if (user != null) {
935                 userName = user.getName();
936             }
937             String reasonMsg;
938             switch (reason) {
939             case SESSION_ACTIVE:
940                 reasonMsg = "SESSION_ACTIVE";
941                 break;
942             case SESSION_MAX_TIME:
943                 reasonMsg = "SESSION_MAX_TIME";
944                 break;
945             case SESSION_IDLE_EXPIRE:
946                 reasonMsg = "SESSION_IDLE_EXPIRE";
947                 break;
948             case SESSION_EXPLICT_DELETE:
949                 reasonMsg = "SESSION_EXPLICT_DELETE";
950                 break;
951             default:
952                 reasonMsg = "bad reason code " + reason;
953                 break;
954             }
955 
956             logChannel.write(Logger.DEBUG, "SessionMgr deleteSession: "
957                              + session.getSessionKey() + " user = \"" + userName
958                              + "\" reason = " + reasonMsg);
959         }
960         if (session.getUser() != null) {
961             sessionUserTable.remove(session.getSessionKey(), session.getUser());
962         }
963   sessionHome.removeSession(session.getSessionKey());
964     }
965 
966     /**
967      * Removes a session from the Session Table.  This method is used
968      * to log a user off the application. Note that
969      * <CODE>sessionDeleted()</CODE> is called.
970      *
971      * @param session Session object to remove from table removed.
972      * @exception SessionException
973      *   If the session cannot be deleted.
974      */
975     public synchronized void deleteSession(Session session) throws SessionException {
976         sessionDeleted((StandardSession)session, SESSION_EXPLICT_DELETE);
977   doDeleteSession((StandardSession)session, SESSION_EXPLICT_DELETE);
978     }
979 
980     /**
981      * Removes a session from the Session Table.  This method is used
982      * to log a user off the application. Note that
983      * <CODE>sessionDeleted()</CODE> is called.
984      *
985      * @param sessionKey A key corresponding to a user session to be
986      *       removed.
987      * @exception SessionException
988      *   If the session cannot be deleted.
989      */
990     public synchronized void deleteSession(String sessionKey) throws SessionException {
991   Session session = (Session)sessionHome.getSession(sessionKey);
992   if (session != null) {
993             deleteSession(session);
994         }
995     }
996 
997     /**
998      * Puts a session into the 'passive' state.  A 'passive'
999      * session may be made persistent.
1000     *
1001     * @param thread the thread currently associated with the session.
1002     * @param sessionKey the session key for the session
1003     *   that will be made persistent.
1004     * @exception SessionException
1005     *   If the session cannot be put into the passive state.
1006     */
1007    public void passivateSession(Thread thread, String sessionKey) throws SessionException {
1008        sessionHome.passivateSession(thread, sessionKey);
1009    }
1010
1011
1012    /**
1013     * Scans session table to determine if any sessions need to be
1014     * expired.
1015     *
1016     * @exception SessionException
1017     *   if an error occurs.
1018     */
1019    public void cleanUpIdleSessions() throws SessionException {
1020  Enumeration e = sessionHome.keys();
1021  StandardSession session;
1022        String sessionKey;
1023        debug(3, "checking for idle sessions... ");
1024  while (e.hasMoreElements()) {
1025            sessionKey = (String)e.nextElement();
1026      session = (StandardSession)sessionHome.getSession(Thread.currentThread(), sessionKey);
1027            if (session != null) {
1028                int stat = isSessionExpired(session);
1029                if (stat != SESSION_ACTIVE) {
1030                    debug(3, "cleaning up idle session: " + sessionKey);
1031                    sessionDeleted(session, stat);
1032                    doDeleteSession(session, stat);
1033                } else {
1034                    sessionHome.passivateSession(Thread.currentThread(), sessionKey);
1035                }
1036            }
1037  }
1038    }
1039
1040    /**
1041     * Returns whether the <code>Session</code> object associated with
1042     * the specified session key exists.
1043     *
1044     * @param  sessionKey The String used to reference a
1045     *    <code>Session</code> object.
1046     * @return  If the key is associated with an active session, then
1047     *     return true, otherwise return false.
1048     * @exception SessionException
1049     *   If existence of the session cannot be tested.
1050     */
1051    public synchronized boolean sessionExists(String sessionKey)
1052        throws SessionException {
1053  if (sessionKey == null) {
1054      return false;
1055        }
1056  return sessionHome.containsKey(sessionKey);
1057    }
1058
1059    /**
1060     * Lookup the <code>Session</code> object associated with the
1061     * specified session key. <P>
1062     *
1063     * Each time a session is returned via this method, the session's
1064     * last used (referenced) time is updated to the current time.
1065     *
1066     * @param  sessionKey The String used to reference a
1067     *    <code>Session</code> object.
1068     * @return  If the key is associated with an active session, then
1069     *     the corresponding <code>Session</code> object is
1070     *     returned.  Otherwise <code>null</code> is returned.
1071     * @see  Session
1072     * @see  StandardSession#touch
1073     * @exception SessionException
1074     *   If the session cannot be retrieved.
1075     */
1076    public synchronized Session getSession(String sessionKey) throws SessionException {
1077        keyGenerator.incrementRandomCounter();
1078  StandardSession session = (StandardSession)sessionHome.getSession(sessionKey);
1079        if (session != null) {
1080            session.touch();
1081        } else {
1082            sessionUserTable.remove(sessionKey);
1083        }
1084  return session;
1085    }
1086
1087    /**
1088     * Lookup the <code>Session</code> object associated with the
1089     * specified session key. <P>
1090     *
1091     * Each time a session is returned via this method, the session's
1092     * last used (referenced) time is updated to the current time.
1093     *
1094     * @param   thread the thread to associate with the session.
1095     * @param  sessionKey The String used to reference a
1096     *    <code>Session</code> object.
1097     * @return  If the key is associated with an active session, then
1098     *     the corresponding <code>Session</code> object is
1099     *     returned.  Otherwise <code>null</code> is returned.
1100     * @see  Session
1101     * @see  StandardSession#touch
1102     * @exception SessionException
1103     *   If the session cannot be retrieved.
1104     */
1105    public synchronized Session getSession(Thread thread, String sessionKey)
1106        throws SessionException {
1107        keyGenerator.incrementRandomCounter();
1108  StandardSession session =
1109            (StandardSession)sessionHome.getSession(thread, sessionKey);
1110        if (session != null) {
1111            session.touch();
1112        } else {
1113            sessionUserTable.remove(sessionKey);
1114        }
1115  return session;
1116    }
1117
1118    /**
1119     * Get all of the active sessions.
1120     *
1121     * @return An enumeration of the active sessions.
1122     * @exception SessionException
1123     *   If the sessions cannot be retrieved.
1124     */
1125    public synchronized Enumeration getSessionKeys() throws SessionException {
1126        return sessionHome.keys();
1127    }
1128
1129    /**
1130     * Lookup the keys for the active sessions for a user.
1131     * A given user may have multiple sessions associated
1132     * with it (using FTP as an example, the "anonymous" account).
1133     *
1134     * @param user The user to search for.
1135     * @return An enumeration containing the session keys of the
1136     *   active sessions for the user.
1137     * @exception SessionException
1138     *   If the sessions cannot be retrieved.
1139     */
1140    public synchronized Enumeration getSessionKeys(User user) throws SessionException {
1141        return sessionUserTable.getSessionKeys(user);
1142    }
1143
1144    /**
1145     * Gets the number of currently active sessions.
1146     *
1147     * @return  The number of currently active sessions.
1148     * @exception SessionException
1149     *   If the session cannot be calculated.
1150     */
1151    public int activeSessionCount() throws SessionException {
1152  return sessionHome.size();
1153    }
1154
1155    /**
1156     * Gets the number of session that are paged to persistent store.
1157     *
1158     * @return  The number of currently paged sessions.
1159     * @exception SessionException
1160     *   If the session cannot be calculated.
1161     */
1162    public int pagedSessionCount() throws SessionException {
1163  return sessionHome.pagedSize();
1164    }
1165
1166    /**
1167     * Returns the current mode for the session manager.
1168     *
1169     * @return  the mode.
1170     * @exception SessionException
1171     *   If the mode cannot be determined.
1172     * @see #MODE_BASIC
1173     * @see #MODE_PAGE_TO_DISK
1174     * @see #MODE_PAGE_TO_DB
1175     * @see #MODE_CUSTOM
1176     */
1177    public int getMode() throws SessionException {
1178  return mode;
1179    }
1180
1181   /**
1182     * Gets the maximum number of concurent sessions that existed
1183     * at any time since this object was created, or
1184     * <CODE>resetMaxSessionCount()</CODE> was called.
1185     * This is a historical highwater mark.
1186     *
1187     * @return The highwater mark for number of sessions, or -1.
1188     */
1189    public int maxSessionCount() {
1190        return maxSessions;
1191    }
1192
1193    /**
1194     * Gets the time when the maximum refered to by
1195     * <CODE>maxSessionCount()</CODE> occured.
1196     *
1197     * @return The Date of when the maximum number of sessions occured.
1198     */
1199    public Date maxSessionCountDate() {
1200        return maxSessionsDate;
1201    }
1202
1203    /**
1204     * Reset the maximum session count. See <CODE>maxSessionCount()</CODE>.
1205     * The highwater mark should be reset to the current number of sessions.
1206     *
1207     * @exception SessionException
1208     *   if the max session count cannot be reset.
1209     */
1210    public void resetMaxSessionCount() throws SessionException {
1211        maxSessions = sessionHome.size();
1212        maxSessionsDate = new Date();
1213    }
1214
1215    /**
1216     * Returns the maximum session life time, in seconds.
1217     */
1218    public long getMaxSessionLifeTime() {
1219        return maxSessionLifeTime;
1220    }
1221
1222    /**
1223     * Sets the maximum session idle time, in seconds.
1224     */
1225    public void setMaxSessionIdleTime(long maxSessionIdleTime) {
1226        this.maxSessionIdleTime = maxSessionIdleTime;
1227    }
1228
1229    /**
1230     * Returns the maximum session idle time, in seconds.
1231     */
1232    public long getMaxSessionIdleTime() {
1233        return maxSessionIdleTime;
1234    }
1235
1236    /**
1237     * Returns the url encoding state.  Either <code>Never</code>
1238     * <code>Always</code> <code>Auto</code>
1239     */
1240    public String getEncodeUrlState() {
1241        return encodeUrlState;
1242    }
1243
1244    /**
1245     * Returns the maximum no-user session idle time, in seconds.
1246     */
1247    public long getMaxNoUserSessionIdleTime() {
1248        return maxNoUserSessionIdleTime;
1249    }
1250
1251    /**
1252     * Prints debug information under Logger.DEBUG.
1253     *
1254     * @param msg the message to print.
1255     */
1256    private void debug(String msg) {
1257        debug(0, msg);
1258    }
1259
1260    /**
1261     * Prints debug information under Logger.DEBUG.
1262     *
1263     * @param level the debug level.
1264     * @param msg the message to print.
1265     */
1266    private void debug(int level, String msg) {
1267        int dbg = Logger.DEBUG;
1268        switch (level) {
1269        case 1:
1270            dbg = Logger.DEBUG1;
1271            break;
1272        case 2:
1273            dbg = Logger.DEBUG2;
1274            break;
1275        case 3:
1276            dbg = Logger.DEBUG3;
1277            break;
1278        case 4:
1279            dbg = Logger.DEBUG4;
1280            break;
1281        case 5:
1282            dbg = Logger.DEBUG5;
1283            break;
1284        case 6:
1285            dbg = Logger.DEBUG6;
1286            break;
1287        case 7:
1288            dbg = Logger.DEBUG7;
1289            break;
1290        case 8:
1291            dbg = Logger.DEBUG8;
1292            break;
1293        case 9:
1294            dbg = Logger.DEBUG9;
1295            break;
1296        default:
1297            dbg = Logger.DEBUG;
1298            break;
1299        }
1300
1301        Enhydra.getLogChannel().write(dbg, "StandardSessionManager("
1302                                      + Thread.currentThread().getName()
1303                                      + "): " + msg);
1304    }
1305
1306}