1 // Copyright (c) 2000 Just Objects B.V. <just@justobjects.nl>
2 // Distributable under LGPL license. See terms of license at gnu.org.
3 
4 package nl.justobjects.pushlet.core;
5 
6 import nl.justobjects.pushlet.util.Log;
7 import nl.justobjects.pushlet.util.PushletException;
8 import nl.justobjects.pushlet.util.Rand;
9 import nl.justobjects.pushlet.util.Sys;
10
11import java.rmi.server.UID;
12import java.util.*;
13
14/**
15 * Manages lifecycle of Sessions.
16 *
17 * @author Just van den Broecke - Just Objects &copy;
18 * @version $Id: SessionManager.java,v 1.11 2007/11/23 14:33:07 justb Exp $
19 */
20public class SessionManager implements ConfigDefs {
21
22    /**
23     * Singleton pattern:  single instance.
24     */
25    private static SessionManager instance;
26
27    static {
28        // Singleton + factory pattern:  create single instance
29        // from configured class name
30        try {
31            instance = (SessionManager) Config.getClass(SESSION_MANAGER_CLASS, "nl.justobjects.pushlet.core.SessionManager").newInstance();
32            Log.info("SessionManager created className=" + instance.getClass());
33        } catch (Throwable t) {
34            Log.fatal("Cannot instantiate SessionManager from config", t);
35        }
36    }
37
38    /**
39     * Timer to schedule session leasing TimerTasks.
40     */
41    private Timer timer;
42    private final long TIMER_INTERVAL_MILLIS = 60000;
43
44    /**
45     * Map of active sessions, keyed by their id.
46     */
47    private Map sessions = Collections.synchronizedMap(new HashMap(13));
48
49    /**
50     * Shadow cache of active Sessions.
51     */
52    private Session[] sessionCache = new Session[0];
53
54    /**
55     * Flag indicating subscriptions have changed.
56     */
57    private volatile boolean sessionCacheDirty = false;
58
59    /**
60     * Singleton pattern: protected constructor needed for derived classes.
61     */
62    protected SessionManager() {
63    }
64
65    /**
66     * Create new Session (but add later).
67     */
68    public Session createSession(Event anEvent) throws PushletException {
69        // Trivial
70        return Session.create(createSessionId());
71    }
72
73    /**
74     * Create unique Session id.
75     */
76    public String createSessionId() {
77        // Use UUID if specified in config
78        if (Config.hasProperty(SESSION_ID_GENERATION) && Config.getProperty(SESSION_ID_GENERATION).equals(SESSION_ID_GENERATION_UUID)) {
79            return new UID().toString();
80        }
81
82        // Other cases use random name
83
84        // Create a unique session id
85        // In 99.9999 % of the cases this should be generated at once
86        String id = null;
87        while (true) {
88            id = Rand.randomName(Config.getIntProperty(SESSION_ID_SIZE));
89            if (!hasSession(id)) {
90                // Created unique session id
91                break;
92            }
93        }
94        return id;
95    }
96
97    /**
98     * Singleton pattern: get single instance.
99     */
00    public static SessionManager getInstance() {
01        return instance;
02    }
03
04    /**
05     * Get number of listening Sessions.
06     */
07    public Session getSession(String anId) {
08        return (Session) sessions.get(anId);
09    }
10
11    /**
12     * Get copy of listening Sessions.
13     */
14    public Session[] getSessions() {
15        return (Session[]) sessions.values().toArray(new Session[0]);
16    }
17
18    /**
19     * Get number of listening Sessions.
20     */
21    public int getSessionCount() {
22        return sessions.size();
23    }
24
25    /**
26     * Get status info.
27     */
28    public String getStatus() {
29        Session[] sessions = getSessions();
30        String statusInfo = "SessionMgr: " + sessions.length + " sessions \\n";
31        for (int i = 0; i < sessions.length; i++) {
32            statusInfo = statusInfo + sessions[i] + "\\n";
33        }
34        return statusInfo;
35    }
36
37    /**
38     * Is Session present?.
39     */
40    public boolean hasSession(String anId) {
41        return sessions.containsKey(anId);
42    }
43
44    /**
45     * Add session.
46     */
47    public void addSession(Session session) {
48        // log(session.getId() + " at " + session.getAddress() + " adding ");
49        sessions.put(session.getId(), session);
50        sessionCacheDirty = true;
51        info(session.getId() + " at " + session.getAddress() + " added ");
52    }
53
54    /**
55     * Register session for removal.
56     */
57    public Session removeSession(Session aSession) {
58        Session session = (Session) sessions.remove(aSession.getId());
59        if (session != null) {
60            sessionCacheDirty = true;
61            info(session.getId() + " at " + session.getAddress() + " removed ");
62        }
63        return session;
64    }
65
66    public Session[] getSnapshot() {
67        // If no session change return immediately.
68        if (!sessionCacheDirty) {
69            return sessionCache;
70        }
71
72        // Session cache is dirty: recreate
73        synchronized (sessionCache) {
74            // ASSERT: cache is dirty, need to update cache
75
76            // Session cache array may be reused: make all entries null.
77            for (int i = 0; i < sessionCache.length; i++) {
78                sessionCache[i] = null;
79            }
80
81            // Copy all sessions into cache
82            // toArray() expands cache size if required
83            sessionCache = (Session[]) sessions.values().toArray(sessionCache);
84
85            // Mark session cache actualized
86            sessionCacheDirty = false;
87
88            return sessionCache;
89        }
90    }
91
92
93    /**
94     * Util: stdout printing.
95     */
96    public void start() {
97        if (timer != null) {
98            stop();
99        }
00        timer = new Timer(false);
01        timer.schedule(new AgingTimerTask(), TIMER_INTERVAL_MILLIS, TIMER_INTERVAL_MILLIS);
02        info("started; interval=" + TIMER_INTERVAL_MILLIS + "ms");
03    }
04
05    /**
06     * Util: stdout printing.
07     */
08    public void stop() {
09        if (timer != null) {
10            timer.cancel();
11            timer = null;
12        }
13        sessions.clear();
14        sessionCache = new Session[0];
15        info("stopped");
16    }
17
18    /**
19     * Util: stdout printing.
20     */
21    private void info(String s) {
22        Log.info("SessionManager: " + new Date() + " " + s);
23    }
24
25    /**
26     * Util: stdout printing.
27     */
28    private void warn(String s) {
29        Log.warn("SessionManager: " + s);
30    }
31
32    /**
33     * Util: stdout printing.
34     */
35    private void debug(String s) {
36        Log.debug("SessionManager: " + s);
37    }
38
39    /**
40     * Manages session timeouts.
41     */
42    private class AgingTimerTask extends TimerTask {
43        private long lastRun = Sys.now();
44
45        public void run() {
46            long now = Sys.now();
47            long delta = now - lastRun;
48            lastRun = now;
49            // info("tick " + delta);
50
51            Session[] sessions = getSnapshot();
52            Session nextSession = null;
53            for (int i = 0; i < sessions.length; i++) {
54                nextSession = sessions[i];
55
56                // Null denotes end of cache array
57                if (nextSession == null) {
58                    break;
59                }
60
61                try {
62                    // Age the lease
63                    nextSession.age(delta);
64
65                    // Stop session if lease expired
66                    if (nextSession.isExpired()) {
67                        info("Session expired: " + nextSession);
68                        nextSession.stop();
69                    }
70                } catch (Throwable t) {
71                    warn("Error in timer task : " + t);
72                }
73            }
74        }
75    }
76}
77
78/*
79 * $Log: SessionManager.java,v $
80 * Revision 1.11  2007/11/23 14:33:07  justb
81 * core classes now configurable through factory
82 *
83 * Revision 1.10  2007/11/10 14:47:45  justb
84 * make session key generation configurable (can use uuid)
85 *
86 * Revision 1.9  2007/11/10 14:17:18  justb
87 * minor cosmetic changes just commit now
88 *
89 * Revision 1.8  2007/07/02 08:12:16  justb
90 * redo to original version of session cache (with break, but nullify array first)
91 *
92 * Revision 1.7  2007/07/02 07:33:02  justb
93 * small fix in sessionmgr for holes in sessioncache array (continue i.s.o. break)
94 *
95 * Revision 1.6  2006/11/18 12:13:47  justb
96 * made SessionManager constructor protected to allow constructing derived classes
97 *
98 * Revision 1.5  2005/02/28 15:58:05  justb
99 * added SimpleListener example
00 *
01 * Revision 1.4  2005/02/28 12:45:59  justb
02 * introduced Command class
03 *
04 * Revision 1.3  2005/02/28 09:14:55  justb
05 * sessmgr/dispatcher factory/singleton support
06 *
07 * Revision 1.2  2005/02/25 15:13:01  justb
08 * session id generation more robust
09 *
10 * Revision 1.1  2005/02/21 16:59:09  justb
11 * SessionManager and session lease introduced
12 *
13
14 *
15 */
16