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}