1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19 package org.apache.catalina.session;
20
21 import java.beans.PropertyChangeEvent;
22 import java.beans.PropertyChangeListener;
23 import java.io.IOException;
24 import java.security.AccessController;
25 import java.security.PrivilegedActionException;
26 import java.security.PrivilegedExceptionAction;
27 import java.util.HashMap;
28 import java.util.Map;
29
30 import org.apache.catalina.Container;
31 import org.apache.catalina.Context;
32 import org.apache.catalina.Lifecycle;
33 import org.apache.catalina.LifecycleException;
34 import org.apache.catalina.LifecycleListener;
35 import org.apache.catalina.Session;
36 import org.apache.catalina.Store;
37 import org.apache.catalina.util.LifecycleSupport;
38
39 import org.apache.catalina.security.SecurityUtil;
40 import org.apache.juli.logging.Log;
41 import org.apache.juli.logging.LogFactory;
42 /**
43 * Extends the <b>ManagerBase</b> class to implement most of the
44 * functionality required by a Manager which supports any kind of
45 * persistence, even if onlyfor restarts.
46 * <p>
47 * <b>IMPLEMENTATION NOTE</b>: Correct behavior of session storing and
48 * reloading depends upon external calls to the <code>start()</code> and
49 * <code>stop()</code> methods of this class at the correct times.
50 *
51 * @author Craig R. McClanahan
52 * @author Jean-Francois Arcand
53 * @version $Revision: 892872 $ $Date: 2009-12-21 17:38:29 +0100 (Mon, 21 Dec 2009) $
54 */
55
56 public abstract class PersistentManagerBase
57 extends ManagerBase
58 implements Lifecycle, PropertyChangeListener {
59
60 private static Log log = LogFactory.getLog(PersistentManagerBase.class);
61
62 // ---------------------------------------------------- Security Classes
63
64 private class PrivilegedStoreClear
65 implements PrivilegedExceptionAction {
66
67 PrivilegedStoreClear() {
68 }
69
70 public Object run() throws Exception{
71 store.clear();
72 return null;
73 }
74 }
75
76 private class PrivilegedStoreRemove
77 implements PrivilegedExceptionAction {
78
79 private String id;
80
81 PrivilegedStoreRemove(String id) {
82 this.id = id;
83 }
84
85 public Object run() throws Exception{
86 store.remove(id);
87 return null;
88 }
89 }
90
91 private class PrivilegedStoreLoad
92 implements PrivilegedExceptionAction {
93
94 private String id;
95
96 PrivilegedStoreLoad(String id) {
97 this.id = id;
98 }
99
100 public Object run() throws Exception{
101 return store.load(id);
102 }
103 }
104
105 private class PrivilegedStoreSave
106 implements PrivilegedExceptionAction {
107
108 private Session session;
109
110 PrivilegedStoreSave(Session session) {
111 this.session = session;
112 }
113
114 public Object run() throws Exception{
115 store.save(session);
116 return null;
117 }
118 }
119
120 private class PrivilegedStoreKeys
121 implements PrivilegedExceptionAction {
122
123 PrivilegedStoreKeys() {
124 }
125
126 public Object run() throws Exception{
127 return store.keys();
128 }
129 }
130
131 // ----------------------------------------------------- Instance Variables
132
133
134 /**
135 * The descriptive information about this implementation.
136 */
137 private static final String info = "PersistentManagerBase/1.1";
138
139
140 /**
141 * The lifecycle event support for this component.
142 */
143 protected LifecycleSupport lifecycle = new LifecycleSupport(this);
144
145
146 /**
147 * The maximum number of active Sessions allowed, or -1 for no limit.
148 */
149 protected int maxActiveSessions = -1;
150
151
152 /**
153 * The descriptive name of this Manager implementation (for logging).
154 */
155 private static String name = "PersistentManagerBase";
156
157
158 /**
159 * Has this component been started yet?
160 */
161 protected boolean started = false;
162
163
164 /**
165 * Store object which will manage the Session store.
166 */
167 protected Store store = null;
168
169
170 /**
171 * Whether to save and reload sessions when the Manager <code>unload</code>
172 * and <code>load</code> methods are called.
173 */
174 protected boolean saveOnRestart = true;
175
176
177 /**
178 * How long a session must be idle before it should be backed up.
179 * -1 means sessions won't be backed up.
180 */
181 protected int maxIdleBackup = -1;
182
183
184 /**
185 * Minimum time a session must be idle before it is swapped to disk.
186 * This overrides maxActiveSessions, to prevent thrashing if there are lots
187 * of active sessions. Setting to -1 means it's ignored.
188 */
189 protected int minIdleSwap = -1;
190
191 /**
192 * The maximum time a session may be idle before it should be swapped
193 * to file just on general principle. Setting this to -1 means sessions
194 * should not be forced out.
195 */
196 protected int maxIdleSwap = -1;
197
198
199 /**
200 * Number of session creations that failed due to maxActiveSessions.
201 */
202 protected int rejectedSessions = 0;
203
204
205 /**
206 * Processing time during session expiration and passivation.
207 */
208 protected long processingTime = 0;
209
210
211 /**
212 * Sessions currently being swapped in and the associated locks
213 */
214 private final Map<String,Object> sessionSwapInLocks =
215 new HashMap<String,Object>();
216
217
218 // ------------------------------------------------------------- Properties
219
220
221
222
223
224 /**
225 * Indicates how many seconds old a session can get, after its last use in a
226 * request, before it should be backed up to the store. -1 means sessions
227 * are not backed up.
228 */
229 public int getMaxIdleBackup() {
230
231 return maxIdleBackup;
232
233 }
234
235
236 /**
237 * Sets the option to back sessions up to the Store after they
238 * are used in a request. Sessions remain available in memory
239 * after being backed up, so they are not passivated as they are
240 * when swapped out. The value set indicates how old a session
241 * may get (since its last use) before it must be backed up: -1
242 * means sessions are not backed up.
243 * <p>
244 * Note that this is not a hard limit: sessions are checked
245 * against this age limit periodically according to <b>processExpiresFrequency</b>.
246 * This value should be considered to indicate when a session is
247 * ripe for backing up.
248 * <p>
249 * So it is possible that a session may be idle for maxIdleBackup +
250 * processExpiresFrequency * engine.backgroundProcessorDelay seconds, plus the time it takes to handle other
251 * session expiration, swapping, etc. tasks.
252 *
253 * @param backup The number of seconds after their last accessed
254 * time when they should be written to the Store.
255 */
256 public void setMaxIdleBackup (int backup) {
257
258 if (backup == this.maxIdleBackup)
259 return;
260 int oldBackup = this.maxIdleBackup;
261 this.maxIdleBackup = backup;
262 support.firePropertyChange("maxIdleBackup",
263 new Integer(oldBackup),
264 new Integer(this.maxIdleBackup));
265
266 }
267
268
269 /**
270 * The time in seconds after which a session should be swapped out of
271 * memory to disk.
272 */
273 public int getMaxIdleSwap() {
274
275 return maxIdleSwap;
276
277 }
278
279
280 /**
281 * Sets the time in seconds after which a session should be swapped out of
282 * memory to disk.
283 */
284 public void setMaxIdleSwap(int max) {
285
286 if (max == this.maxIdleSwap)
287 return;
288 int oldMaxIdleSwap = this.maxIdleSwap;
289 this.maxIdleSwap = max;
290 support.firePropertyChange("maxIdleSwap",
291 new Integer(oldMaxIdleSwap),
292 new Integer(this.maxIdleSwap));
293
294 }
295
296
297 /**
298 * The minimum time in seconds that a session must be idle before
299 * it can be swapped out of memory, or -1 if it can be swapped out
300 * at any time.
301 */
302 public int getMinIdleSwap() {
303
304 return minIdleSwap;
305
306 }
307
308
309 /**
310 * Sets the minimum time in seconds that a session must be idle before
311 * it can be swapped out of memory due to maxActiveSession. Set it to -1
312 * if it can be swapped out at any time.
313 */
314 public void setMinIdleSwap(int min) {
315
316 if (this.minIdleSwap == min)
317 return;
318 int oldMinIdleSwap = this.minIdleSwap;
319 this.minIdleSwap = min;
320 support.firePropertyChange("minIdleSwap",
321 new Integer(oldMinIdleSwap),
322 new Integer(this.minIdleSwap));
323
324 }
325
326
327 /**
328 * Set the Container with which this Manager has been associated. If it is a
329 * Context (the usual case), listen for changes to the session timeout
330 * property.
331 *
332 * @param container
333 * The associated Container
334 */
335 public void setContainer(Container container) {
336
337 // De-register from the old Container (if any)
338 if ((this.container != null) && (this.container instanceof Context))
339 ((Context) this.container).removePropertyChangeListener(this);
340
341 // Default processing provided by our superclass
342 super.setContainer(container);
343
344 // Register with the new Container (if any)
345 if ((this.container != null) && (this.container instanceof Context)) {
346 setMaxInactiveInterval
347 ( ((Context) this.container).getSessionTimeout()*60 );
348 ((Context) this.container).addPropertyChangeListener(this);
349 }
350
351 }
352
353
354 /**
355 * Return descriptive information about this Manager implementation and
356 * the corresponding version number, in the format
357 * <code><description>/<version></code>.
358 */
359 public String getInfo() {
360
361 return (info);
362
363 }
364
365
366 /**
367 * Return true, if the session id is loaded in memory
368 * otherwise false is returned
369 *
370 * @param id The session id for the session to be searched for
371 */
372 public boolean isLoaded( String id ){
373 try {
374 if ( super.findSession(id) != null )
375 return true;
376 } catch (IOException e) {
377 log.error("checking isLoaded for id, " + id + ", "+e.getMessage(), e);
378 }
379 return false;
380 }
381
382
383 /**
384 * Return the maximum number of active Sessions allowed, or -1 for
385 * no limit.
386 */
387 public int getMaxActiveSessions() {
388
389 return (this.maxActiveSessions);
390
391 }
392
393
394 /**
395 * Set the maximum number of actives Sessions allowed, or -1 for
396 * no limit.
397 *
398 * @param max The new maximum number of sessions
399 */
400 public void setMaxActiveSessions(int max) {
401
402 int oldMaxActiveSessions = this.maxActiveSessions;
403 this.maxActiveSessions = max;
404 support.firePropertyChange("maxActiveSessions",
405 new Integer(oldMaxActiveSessions),
406 new Integer(this.maxActiveSessions));
407
408 }
409
410
411 /**
412 * Number of session creations that failed due to maxActiveSessions.
413 *
414 * @return The count
415 */
416 public int getRejectedSessions() {
417 return rejectedSessions;
418 }
419
420
421 public void setRejectedSessions(int rejectedSessions) {
422 this.rejectedSessions = rejectedSessions;
423 }
424
425 /**
426 * Return the descriptive short name of this Manager implementation.
427 */
428 public String getName() {
429
430 return (name);
431
432 }
433
434
435 /**
436 * Get the started status.
437 */
438 protected boolean isStarted() {
439
440 return started;
441
442 }
443
444
445 /**
446 * Set the started flag
447 */
448 protected void setStarted(boolean started) {
449
450 this.started = started;
451
452 }
453
454
455 /**
456 * Set the Store object which will manage persistent Session
457 * storage for this Manager.
458 *
459 * @param store the associated Store
460 */
461 public void setStore(Store store) {
462 this.store = store;
463 store.setManager(this);
464
465 }
466
467
468 /**
469 * Return the Store object which manages persistent Session
470 * storage for this Manager.
471 */
472 public Store getStore() {
473
474 return (this.store);
475
476 }
477
478
479
480 /**
481 * Indicates whether sessions are saved when the Manager is shut down
482 * properly. This requires the unload() method to be called.
483 */
484 public boolean getSaveOnRestart() {
485
486 return saveOnRestart;
487
488 }
489
490
491 /**
492 * Set the option to save sessions to the Store when the Manager is
493 * shut down, then loaded when the Manager starts again. If set to
494 * false, any sessions found in the Store may still be picked up when
495 * the Manager is started again.
496 *
497 * @param saveOnRestart true if sessions should be saved on restart, false if
498 * they should be ignored.
499 */
500 public void setSaveOnRestart(boolean saveOnRestart) {
501
502 if (saveOnRestart == this.saveOnRestart)
503 return;
504
505 boolean oldSaveOnRestart = this.saveOnRestart;
506 this.saveOnRestart = saveOnRestart;
507 support.firePropertyChange("saveOnRestart",
508 new Boolean(oldSaveOnRestart),
509 new Boolean(this.saveOnRestart));
510
511 }
512
513
514 // --------------------------------------------------------- Public Methods
515
516
517 /**
518 * Clear all sessions from the Store.
519 */
520 public void clearStore() {
521
522 if (store == null)
523 return;
524
525 try {
526 if (SecurityUtil.isPackageProtectionEnabled()){
527 try{
528 AccessController.doPrivileged(new PrivilegedStoreClear());
529 }catch(PrivilegedActionException ex){
530 Exception exception = ex.getException();
531 log.error("Exception clearing the Store: " + exception);
532 exception.printStackTrace();
533 }
534 } else {
535 store.clear();
536 }
537 } catch (IOException e) {
538 log.error("Exception clearing the Store: " + e);
539 e.printStackTrace();
540 }
541
542 }
543
544
545 /**
546 * Implements the Manager interface, direct call to processExpires and processPersistenceChecks
547 */
548 public void processExpires() {
549
550 long timeNow = System.currentTimeMillis();
551 Session sessions[] = findSessions();
552 int expireHere = 0 ;
553 if(log.isDebugEnabled())
554 log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
555 for (int i = 0; i < sessions.length; i++) {
556 if (!sessions[i].isValid()) {
557 expiredSessions++;
558 expireHere++;
559 }
560 }
561 processPersistenceChecks();
562 if ((getStore() != null) && (getStore() instanceof StoreBase)) {
563 ((StoreBase) getStore()).processExpires();
564 }
565
566 long timeEnd = System.currentTimeMillis();
567 if(log.isDebugEnabled())
568 log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
569 processingTime += (timeEnd - timeNow);
570
571 }
572
573
574 /**
575 * Called by the background thread after active sessions have been checked
576 * for expiration, to allow sessions to be swapped out, backed up, etc.
577 */
578 public void processPersistenceChecks() {
579
580 processMaxIdleSwaps();
581 processMaxActiveSwaps();
582 processMaxIdleBackups();
583
584 }
585
586
587 /**
588 * Return the active Session, associated with this Manager, with the
589 * specified session id (if any); otherwise return <code>null</code>.
590 * This method checks the persistence store if persistence is enabled,
591 * otherwise just uses the functionality from ManagerBase.
592 *
593 * @param id The session id for the session to be returned
594 *
595 * @exception IllegalStateException if a new session cannot be
596 * instantiated for any reason
597 * @exception IOException if an input/output error occurs while
598 * processing this request
599 */
600 public Session findSession(String id) throws IOException {
601
602 Session session = super.findSession(id);
603 // OK, at this point, we're not sure if another thread is trying to
604 // remove the session or not so the only way around this is to lock it
605 // (or attempt to) and then try to get it by this session id again. If
606 // the other code ran swapOut, then we should get a null back during
607 // this run, and if not, we lock it out so we can access the session
608 // safely.
609 if(session != null) {
610 synchronized(session){
611 session = super.findSession(session.getIdInternal());
612 if(session != null){
613 // To keep any external calling code from messing up the
614 // concurrency.
615 session.access();
616 session.endAccess();
617 }
618 }
619 }
620 if (session != null)
621 return (session);
622
623 // See if the Session is in the Store
624 session = swapIn(id);
625 return (session);
626
627 }
628
629 /**
630 * Remove this Session from the active Sessions for this Manager,
631 * but not from the Store. (Used by the PersistentValve)
632 *
633 * @param session Session to be removed
634 */
635 public void removeSuper(Session session) {
636 super.remove (session);
637 }
638
639 /**
640 * Load all sessions found in the persistence mechanism, assuming
641 * they are marked as valid and have not passed their expiration
642 * limit. If persistence is not supported, this method returns
643 * without doing anything.
644 * <p>
645 * Note that by default, this method is not called by the MiddleManager
646 * class. In order to use it, a subclass must specifically call it,
647 * for example in the start() and/or processPersistenceChecks() methods.
648 */
649 public void load() {
650
651 // Initialize our internal data structures
652 sessions.clear();
653
654 if (store == null)
655 return;
656
657 String[] ids = null;
658 try {
659 if (SecurityUtil.isPackageProtectionEnabled()){
660 try{
661 ids = (String[])
662 AccessController.doPrivileged(new PrivilegedStoreKeys());
663 }catch(PrivilegedActionException ex){
664 Exception exception = ex.getException();
665 log.error("Exception in the Store during load: "
666 + exception);
667 exception.printStackTrace();
668 }
669 } else {
670 ids = store.keys();
671 }
672 } catch (IOException e) {
673 log.error("Can't load sessions from store, " + e.getMessage(), e);
674 return;
675 }
676
677 int n = ids.length;
678 if (n == 0)
679 return;
680
681 if (log.isDebugEnabled())
682 log.debug(sm.getString("persistentManager.loading", String.valueOf(n)));
683
684 for (int i = 0; i < n; i++)
685 try {
686 swapIn(ids[i]);
687 } catch (IOException e) {
688 log.error("Failed load session from store, " + e.getMessage(), e);
689 }
690
691 }
692
693
694 /**
695 * Remove this Session from the active Sessions for this Manager,
696 * and from the Store.
697 *
698 * @param session Session to be removed
699 */
700 public void remove(Session session) {
701
702 super.remove (session);
703
704 if (store != null){
705 removeSession(session.getIdInternal());
706 }
707 }
708
709
710 /**
711 * Remove this Session from the active Sessions for this Manager,
712 * and from the Store.
713 *
714 * @param id Session's id to be removed
715 */
716 protected void removeSession(String id){
717 try {
718 if (SecurityUtil.isPackageProtectionEnabled()){
719 try{
720 AccessController.doPrivileged(new PrivilegedStoreRemove(id));
721 }catch(PrivilegedActionException ex){
722 Exception exception = ex.getException();
723 log.error("Exception in the Store during removeSession: "
724 + exception);
725 exception.printStackTrace();
726 }
727 } else {
728 store.remove(id);
729 }
730 } catch (IOException e) {
731 log.error("Exception removing session " + e.getMessage());
732 e.printStackTrace();
733 }
734 }
735
736 /**
737 * Save all currently active sessions in the appropriate persistence
738 * mechanism, if any. If persistence is not supported, this method
739 * returns without doing anything.
740 * <p>
741 * Note that by default, this method is not called by the MiddleManager
742 * class. In order to use it, a subclass must specifically call it,
743 * for example in the stop() and/or processPersistenceChecks() methods.
744 */
745 public void unload() {
746
747 if (store == null)
748 return;
749
750 Session sessions[] = findSessions();
751 int n = sessions.length;
752 if (n == 0)
753 return;
754
755 if (log.isDebugEnabled())
756 log.debug(sm.getString("persistentManager.unloading",
757 String.valueOf(n)));
758
759 for (int i = 0; i < n; i++)
760 try {
761 swapOut(sessions[i]);
762 } catch (IOException e) {
763 ; // This is logged in writeSession()
764 }
765
766 }
767
768
769 // ------------------------------------------------------ Protected Methods
770
771
772 /**
773 * Look for a session in the Store and, if found, restore
774 * it in the Manager's list of active sessions if appropriate.
775 * The session will be removed from the Store after swapping
776 * in, but will not be added to the active session list if it
777 * is invalid or past its expiration.
778 */
779 protected Session swapIn(String id) throws IOException {
780
781 if (store == null)
782 return null;
783
784 Object swapInLock = null;
785
786 /*
787 * The purpose of this sync and these locks is to make sure that a
788 * session is only loaded once. It doesn't matter if the lock is removed
789 * and then another thread enters this method and tries to load the same
790 * session. That thread will re-create a swapIn lock for that session,
791 * quickly find that the session is already in sessions, use it and
792 * carry on.
793 */
794 synchronized (this) {
795 swapInLock = sessionSwapInLocks.get(id);
796 if (swapInLock == null) {
797 swapInLock = new Object();
798 sessionSwapInLocks.put(id, swapInLock);
799 }
800 }
801
802 Session session = null;
803
804 synchronized (swapInLock) {
805 // First check to see if another thread has loaded the session into
806 // the manager
807 session = sessions.get(id);
808
809 if (session == null) {
810 try {
811 if (SecurityUtil.isPackageProtectionEnabled()){
812 try {
813 session = (Session) AccessController.doPrivileged(
814 new PrivilegedStoreLoad(id));
815 } catch (PrivilegedActionException ex) {
816 Exception e = ex.getException();
817 log.error(sm.getString(
818 "persistentManager.swapInException", id),
819 e);
820 if (e instanceof IOException){
821 throw (IOException)e;
822 } else if (e instanceof ClassNotFoundException) {
823 throw (ClassNotFoundException)e;
824 }
825 }
826 } else {
827 session = store.load(id);
828 }
829 } catch (ClassNotFoundException e) {
830 String msg = sm.getString(
831 "persistentManager.deserializeError", id);
832 log.error(msg, e);
833 throw new IllegalStateException(msg, e);
834 }
835
836 if (session != null && !session.isValid()) {
837 log.error(sm.getString(
838 "persistentManager.swapInInvalid", id));
839 session.expire();
840 removeSession(id);
841 session = null;
842 }
843
844 if (session != null) {
845 if(log.isDebugEnabled())
846 log.debug(sm.getString("persistentManager.swapIn", id));
847
848 session.setManager(this);
849 // make sure the listeners know about it.
850 ((StandardSession)session).tellNew();
851 add(session);
852 ((StandardSession)session).activate();
853 // endAccess() to ensure timeouts happen correctly.
854 // access() to keep access count correct or it will end up
855 // negative
856 session.access();
857 session.endAccess();
858 }
859 }
860 }
861
862 // Make sure the lock is removed
863 synchronized (this) {
864 sessionSwapInLocks.remove(id);
865 }
866
867 return (session);
868
869 }
870
871
872 /**
873 * Remove the session from the Manager's list of active
874 * sessions and write it out to the Store. If the session
875 * is past its expiration or invalid, this method does
876 * nothing.
877 *
878 * @param session The Session to write out.
879 */
880 protected void swapOut(Session session) throws IOException {
881
882 if (store == null || !session.isValid()) {
883 return;
884 }
885
886 ((StandardSession)session).passivate();
887 writeSession(session);
888 super.remove(session);
889 session.recycle();
890
891 }
892
893
894 /**
895 * Write the provided session to the Store without modifying
896 * the copy in memory or triggering passivation events. Does
897 * nothing if the session is invalid or past its expiration.
898 */
899 protected void writeSession(Session session) throws IOException {
900
901 if (store == null || !session.isValid()) {
902 return;
903 }
904
905 try {
906 if (SecurityUtil.isPackageProtectionEnabled()){
907 try{
908 AccessController.doPrivileged(new PrivilegedStoreSave(session));
909 }catch(PrivilegedActionException ex){
910 Exception exception = ex.getException();
911 log.error("Exception in the Store during writeSession: "
912 + exception);
913 exception.printStackTrace();
914 }
915 } else {
916 store.save(session);
917 }
918 } catch (IOException e) {
919 log.error(sm.getString
920 ("persistentManager.serializeError", session.getIdInternal(), e));
921 throw e;
922 }
923
924 }
925
926
927 // ------------------------------------------------------ Lifecycle Methods
928
929
930 /**
931 * Add a lifecycle event listener to this component.
932 *
933 * @param listener The listener to add
934 */
935 public void addLifecycleListener(LifecycleListener listener) {
936
937 lifecycle.addLifecycleListener(listener);
938
939 }
940
941
942 /**
943 * Get the lifecycle listeners associated with this lifecycle. If this
944 * Lifecycle has no listeners registered, a zero-length array is returned.
945 */
946 public LifecycleListener[] findLifecycleListeners() {
947
948 return lifecycle.findLifecycleListeners();
949
950 }
951
952
953 /**
954 * Remove a lifecycle event listener from this component.
955 *
956 * @param listener The listener to remove
957 */
958 public void removeLifecycleListener(LifecycleListener listener) {
959
960 lifecycle.removeLifecycleListener(listener);
961
962 }
963
964
965 /**
966 * Prepare for the beginning of active use of the public methods of this
967 * component. This method should be called after <code>configure()</code>,
968 * and before any of the public methods of the component are utilized.
969 *
970 * @exception LifecycleException if this component detects a fatal error
971 * that prevents this component from being used
972 */
973 public void start() throws LifecycleException {
974
975 // Validate and update our current component state
976 if (started) {
977 log.info(sm.getString("standardManager.alreadyStarted"));
978 return;
979 }
980 if( ! initialized )
981 init();
982
983 lifecycle.fireLifecycleEvent(START_EVENT, null);
984 started = true;
985
986 // Force initialization of the random number generator
987 if (log.isDebugEnabled())
988 log.debug("Force random number initialization starting");
989 String dummy = generateSessionId();
990 if (log.isDebugEnabled())
991 log.debug("Force random number initialization completed");
992
993 if (store == null)
994 log.error("No Store configured, persistence disabled");
995 else if (store instanceof Lifecycle)
996 ((Lifecycle)store).start();
997
998 }
999
1000
1001 /**
1002 * Gracefully terminate the active use of the public methods of this
1003 * component. This method should be the last one called on a given
1004 * instance of this component.
1005 *
1006 * @exception LifecycleException if this component detects a fatal error
1007 * that needs to be reported
1008 */
1009 public void stop() throws LifecycleException {
1010
1011 if (log.isDebugEnabled())
1012 log.debug("Stopping");
1013
1014 // Validate and update our current component state
1015 if (!isStarted()) {
1016 log.info(sm.getString("standardManager.notStarted"));
1017 return;
1018 }
1019
1020 lifecycle.fireLifecycleEvent(STOP_EVENT, null);
1021 setStarted(false);
1022
1023 if (getStore() != null && saveOnRestart) {
1024 unload();
1025 } else {
1026 // Expire all active sessions
1027 Session sessions[] = findSessions();
1028 for (int i = 0; i < sessions.length; i++) {
1029 StandardSession session = (StandardSession) sessions[i];
1030 if (!session.isValid())
1031 continue;
1032 session.expire();
1033 }
1034 }
1035
1036 if (getStore() != null && getStore() instanceof Lifecycle)
1037 ((Lifecycle)getStore()).stop();
1038
1039 // Require a new random number generator if we are restarted
1040 this.random = null;
1041
1042 if( initialized )
1043 destroy();
1044
1045 }
1046
1047
1048 // ----------------------------------------- PropertyChangeListener Methods
1049
1050
1051 /**
1052 * Process property change events from our associated Context.
1053 *
1054 * @param event The property change event that has occurred
1055 */
1056 public void propertyChange(PropertyChangeEvent event) {
1057
1058 // Validate the source of this event
1059 if (!(event.getSource() instanceof Context))
1060 return;
1061 Context context = (Context) event.getSource();
1062
1063 // Process a relevant property change
1064 if (event.getPropertyName().equals("sessionTimeout")) {
1065 try {
1066 setMaxInactiveInterval
1067 ( ((Integer) event.getNewValue()).intValue()*60 );
1068 } catch (NumberFormatException e) {
1069 log.error(sm.getString("standardManager.sessionTimeout",
1070 event.getNewValue().toString()));
1071 }
1072 }
1073
1074 }
1075
1076
1077 // ------------------------------------------------------ Protected Methods
1078
1079
1080 /**
1081 * Swap idle sessions out to Store if they are idle too long.
1082 */
1083 protected void processMaxIdleSwaps() {
1084
1085 if (!isStarted() || maxIdleSwap < 0)
1086 return;
1087
1088 Session sessions[] = findSessions();
1089 long timeNow = System.currentTimeMillis();
1090
1091 // Swap out all sessions idle longer than maxIdleSwap
1092 if (maxIdleSwap >= 0) {
1093 for (int i = 0; i < sessions.length; i++) {
1094 StandardSession session = (StandardSession) sessions[i];
1095 synchronized (session) {
1096 if (!session.isValid())
1097 continue;
1098 int timeIdle = // Truncate, do not round up
1099 (int) ((timeNow - session.getLastAccessedTime()) / 1000L);
1100 if (timeIdle > maxIdleSwap && timeIdle > minIdleSwap) {
1101 if (session.accessCount != null &&
1102 session.accessCount.get() > 0) {
1103 // Session is currently being accessed - skip it
1104 continue;
1105 }
1106 if (log.isDebugEnabled())
1107 log.debug(sm.getString
1108 ("persistentManager.swapMaxIdle",
1109 session.getIdInternal(), new Integer(timeIdle)));
1110 try {
1111 swapOut(session);
1112 } catch (IOException e) {
1113 ; // This is logged in writeSession()
1114 }
1115 }
1116 }
1117 }
1118 }
1119
1120 }
1121
1122
1123 /**
1124 * Swap idle sessions out to Store if too many are active
1125 */
1126 protected void processMaxActiveSwaps() {
1127
1128 if (!isStarted() || getMaxActiveSessions() < 0)
1129 return;
1130
1131 Session sessions[] = findSessions();
1132
1133 // FIXME: Smarter algorithm (LRU)
1134 if (getMaxActiveSessions() >= sessions.length)
1135 return;
1136
1137 if(log.isDebugEnabled())
1138 log.debug(sm.getString
1139 ("persistentManager.tooManyActive",
1140 new Integer(sessions.length)));
1141
1142 int toswap = sessions.length - getMaxActiveSessions();
1143 long timeNow = System.currentTimeMillis();
1144
1145 for (int i = 0; i < sessions.length && toswap > 0; i++) {
1146 StandardSession session = (StandardSession) sessions[i];
1147 synchronized (session) {
1148 int timeIdle = // Truncate, do not round up
1149 (int) ((timeNow - session.getLastAccessedTime()) / 1000L);
1150 if (timeIdle > minIdleSwap) {
1151 if (session.accessCount != null &&
1152 session.accessCount.get() > 0) {
1153 // Session is currently being accessed - skip it
1154 continue;
1155 }
1156 if(log.isDebugEnabled())
1157 log.debug(sm.getString
1158 ("persistentManager.swapTooManyActive",
1159 session.getIdInternal(), new Integer(timeIdle)));
1160 try {
1161 swapOut(session);
1162 } catch (IOException e) {
1163 ; // This is logged in writeSession()
1164 }
1165 toswap--;
1166 }
1167 }
1168 }
1169
1170 }
1171
1172
1173 /**
1174 * Back up idle sessions.
1175 */
1176 protected void processMaxIdleBackups() {
1177
1178 if (!isStarted() || maxIdleBackup < 0)
1179 return;
1180
1181 Session sessions[] = findSessions();
1182 long timeNow = System.currentTimeMillis();
1183
1184 // Back up all sessions idle longer than maxIdleBackup
1185 if (maxIdleBackup >= 0) {
1186 for (int i = 0; i < sessions.length; i++) {
1187 StandardSession session = (StandardSession) sessions[i];
1188 synchronized (session) {
1189 if (!session.isValid())
1190 continue;
1191 int timeIdle = // Truncate, do not round up
1192 (int) ((timeNow - session.getLastAccessedTime()) / 1000L);
1193 if (timeIdle > maxIdleBackup) {
1194 if (log.isDebugEnabled())
1195 log.debug(sm.getString
1196 ("persistentManager.backupMaxIdle",
1197 session.getIdInternal(), new Integer(timeIdle)));
1198
1199 try {
1200 writeSession(session);
1201 } catch (IOException e) {
1202 ; // This is logged in writeSession()
1203 }
1204 }
1205 }
1206 }
1207 }
1208
1209 }
1210
1211 }