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.*;
13import java.lang.reflect.Method;
14import java.lang.reflect.InvocationTargetException;
15
16/**
17 * Manages lifecycle of Sessions.
18 *
19 * @author Just van den Broecke - Just Objects &copy;
20 * @version $Id: SessionManager.java,v 1.12 2007/12/04 13:55:53 justb Exp $
21 */
22public class SessionManager implements ConfigDefs {
23
24    /**
25     * Singleton pattern:  single instance.
26     */
27    private static SessionManager instance;
28
29    static {
30        // Singleton + factory pattern:  create single instance
31        // from configured class name
32        try {
33            instance = (SessionManager) Config.getClass(SESSION_MANAGER_CLASS, "nl.justobjects.pushlet.core.SessionManager").newInstance();
34            Log.info("SessionManager created className=" + instance.getClass());
35        } catch (Throwable t) {
36            Log.fatal("Cannot instantiate SessionManager from config", t);
37        }
38    }
39
40    /**
41     * Timer to schedule session leasing TimerTasks.
42     */
43    private Timer timer;
44    private final long TIMER_INTERVAL_MILLIS = 60000;
45
46    /**
47     * Map of active sessions, keyed by their id, all access is through mutex.
48     */
49    private Map sessions = new HashMap(13);
50
51    /**
52     * Cache of Sessions for iteration and to allow concurrent modification.
53     */
54    private Session[] sessionCache = new Session[0];
55
56    /**
57     * State of SessionCache, becomes true whenever sessionCache out of sync with sessions Map.
58     */
59    private boolean sessionCacheDirty = false;
60
61    /**
62     * Lock for any operation on Sessions (Session Map and/or -cache).
63     */
64    private final Object mutex = new Object();
65
66    /**
67     * Singleton pattern: protected constructor needed for derived classes.
68     */
69    protected SessionManager() {
70    }
71
72    /**
73     * Visitor pattern implementation for Session iteration.
74     * <p/>
75     * This method can be used to iterate over all Sessions in a threadsafe way.
76     * See Dispatcher.multicast and broadcast methods for examples.
77     *
78     * @param visitor the object that should implement method parm
79     * @param method  the method to be called from visitor
80     * @param args  arguments to be passed in visit method, args[0] will always be Session object
81     */
82    public void apply(Object visitor, Method method, Object[] args) {
83
84        synchronized (mutex) {
85
86            // Refresh Session cache if required
87            // We use a cache for two reasons:
88            // 1. to prevent concurrent modification from within visitor method
89            // 2. some optimization (vs setting up Iterator for each apply()
90            if (sessionCacheDirty) {
91                // Clear out existing cache
92                for (int i = 0; i < sessionCache.length; i++) {
93                    sessionCache[i] = null;
94                }
95
96                // Refill cache and update state
97                sessionCache = (Session[]) sessions.values().toArray(sessionCache);
98                sessionCacheDirty = false;
99            }
00
01            // Valid session cache: loop and call supplied Visitor method
02            Session nextSession;
03            for (int i = 0; i < sessionCache.length; i++) {
04                nextSession = sessionCache[i];
05
06                // Session cache may not be entirely filled
07                if (nextSession == null) {
08                    break;
09                }
10
11                try {
12                    // First argument is always a Session object
13                    args[0] = nextSession;
14
15                    // Use Java reflection to call the method passed by the Visitor
16                    method.invoke(visitor, args);
17                } catch (IllegalAccessException e) {
18                    Log.warn("apply: illegal method access: ", e);
19                } catch (InvocationTargetException e) {
20                    Log.warn("apply: method invoke: ", e);
21                }
22            }
23        }
24    }
25
26    /**
27     * Create new Session (but add later).
28     */
29    public Session createSession(Event anEvent) throws PushletException {
30        // Trivial
31        return Session.create(createSessionId());
32    }
33
34
35    /**
36     * Singleton pattern: get single instance.
37     */
38    public static SessionManager getInstance() {
39        return instance;
40    }
41
42    /**
43     * Get Session by session id.
44     */
45    public Session getSession(String anId) {
46        synchronized (mutex) {
47            return (Session) sessions.get(anId);
48        }
49    }
50
51    /**
52     * Get copy of listening Sessions.
53     */
54    public Session[] getSessions() {
55        synchronized (mutex) {
56            return (Session[]) sessions.values().toArray(new Session[0]);
57        }
58    }
59
60    /**
61     * Get number of listening Sessions.
62     */
63    public int getSessionCount() {
64        synchronized (mutex) {
65            return sessions.size();
66        }
67    }
68
69    /**
70     * Get status info.
71     */
72    public String getStatus() {
73        Session[] sessions = getSessions();
74        StringBuffer statusBuffer = new StringBuffer();
75        statusBuffer.append("SessionMgr: " + sessions.length + " sessions \\n");
76        for (int i = 0; i < sessions.length; i++) {
77            statusBuffer.append(sessions[i] + "\\n");
78        }
79        return statusBuffer.toString();
80    }
81
82    /**
83     * Is Session present?.
84     */
85    public boolean hasSession(String anId) {
86        synchronized (mutex) {
87            return sessions.containsKey(anId);
88        }
89    }
90
91    /**
92     * Add session.
93     */
94    public void addSession(Session session) {
95        synchronized (mutex) {
96            sessions.put(session.getId(), session);
97            sessionCacheDirty = true;
98        }
99        // log(session.getId() + " at " + session.getAddress() + " adding ");
00        info(session.getId() + " at " + session.getAddress() + " added ");
01    }
02
03    /**
04     * Register session for removal.
05     */
06    public Session removeSession(Session aSession) {
07        synchronized (mutex) {
08            Session session = (Session) sessions.remove(aSession.getId());
09            if (session != null) {
10                info(session.getId() + " at " + session.getAddress() + " removed ");
11            }
12            sessionCacheDirty = true;
13            return session;
14        }
15    }
16
17
18    /**
19     * Starts us.
20     */
21    public void start() throws PushletException {
22        if (timer != null) {
23            stop();
24        }
25        timer = new Timer(false);
26        timer.schedule(new AgingTimerTask(), TIMER_INTERVAL_MILLIS, TIMER_INTERVAL_MILLIS);
27        info("started; interval=" + TIMER_INTERVAL_MILLIS + "ms");
28    }
29
30    /**
31     * Stopis us.
32     */
33    public void stop() {
34        if (timer != null) {
35            timer.cancel();
36            timer = null;
37        }
38        synchronized (mutex) {
39            sessions.clear();
40        }
41        info("stopped");
42    }
43
44    /**
45     * Create unique Session id.
46     */
47    protected String createSessionId() {
48        // Use UUID if specified in config (thanks Uli Romahn)
49        if (Config.hasProperty(SESSION_ID_GENERATION) && Config.getProperty(SESSION_ID_GENERATION).equals(SESSION_ID_GENERATION_UUID)) {
50            // We want to be Java 1.4 compatible so use UID class (1.5+ we may use java.util.UUID). 
51            return new UID().toString();
52        }
53
54        // Other cases use random name
55
56        // Create a unique session id
57        // In 99.9999 % of the cases this should be generated at once
58        // We need the mutext to prevent the chance of creating
59        // same-valued ids (thanks Uli Romahn)
60        synchronized (mutex) {
61            String id;
62            while (true) {
63                id = Rand.randomName(Config.getIntProperty(SESSION_ID_SIZE));
64                if (!hasSession(id)) {
65                    // Created unique session id
66                    break;
67                }
68            }
69            return id;
70        }
71    }
72
73    /**
74     * Util: stdout printing.
75     */
76    protected void info(String s) {
77        Log.info("SessionManager: " + new Date() + " " + s);
78    }
79
80    /**
81     * Util: stdout printing.
82     */
83    protected void warn(String s) {
84        Log.warn("SessionManager: " + s);
85    }
86
87    /**
88     * Util: stdout printing.
89     */
90    protected void debug(String s) {
91        Log.debug("SessionManager: " + s);
92    }
93
94    /**
95     * Manages Session timeouts.
96     */
97    private class AgingTimerTask extends TimerTask {
98        private long lastRun = Sys.now();
99        private long delta;
00        private Method visitMethod;
01
02        public AgingTimerTask() throws PushletException {
03            try {
04                // Setup Visitor Methods for callback from SessionManager
05                Class[] argsClasses = {Session.class};
06                visitMethod = this.getClass().getMethod("visit", argsClasses);
07            } catch (NoSuchMethodException e) {
08                throw new PushletException("Failed to setup AgingTimerTask", e);
09            }
10        }
11
12        /**
13         * Clock tick callback from Timer.
14         */
15        public void run() {
16            long now = Sys.now();
17            delta = now - lastRun;
18            lastRun = now;
19            debug("AgingTimerTask: tick");
20
21            // Use Visitor pattern to loop through Session objects (see visit() below)
22            getInstance().apply(this, visitMethod, new Object[1]);
23        }
24
25        /**
26         * Callback from SessionManager during apply()
27         */
28        public void visit(Session aSession) {
29            try {
30                // Age the lease
31                aSession.age(delta);
32                debug("AgingTimerTask: visit: " + aSession);
33
34                // Stop session if lease expired
35                if (aSession.isExpired()) {
36                    info("AgingTimerTask: Session expired: " + aSession);
37                    aSession.stop();
38                }
39            } catch (Throwable t) {
40                warn("AgingTimerTask: Error in timer task : " + t);
41            }
42        }
43    }
44}
45
46/*
47 * $Log: SessionManager.java,v $
48 * Revision 1.12  2007/12/04 13:55:53  justb
49 * reimplement SessionManager concurrency (prev version was not thread-safe!)
50 *
51 * Revision 1.11  2007/11/23 14:33:07  justb
52 * core classes now configurable through factory
53 *
54 * Revision 1.10  2007/11/10 14:47:45  justb
55 * make session key generation configurable (can use uuid)
56 *
57 * Revision 1.9  2007/11/10 14:17:18  justb
58 * minor cosmetic changes just commit now
59 *
60 * Revision 1.8  2007/07/02 08:12:16  justb
61 * redo to original version of session cache (with break, but nullify array first)
62 *
63 * Revision 1.7  2007/07/02 07:33:02  justb
64 * small fix in sessionmgr for holes in sessioncache array (continue i.s.o. break)
65 *
66 * Revision 1.6  2006/11/18 12:13:47  justb
67 * made SessionManager constructor protected to allow constructing derived classes
68 *
69 * Revision 1.5  2005/02/28 15:58:05  justb
70 * added SimpleListener example
71 *
72 * Revision 1.4  2005/02/28 12:45:59  justb
73 * introduced Command class
74 *
75 * Revision 1.3  2005/02/28 09:14:55  justb
76 * sessmgr/dispatcher factory/singleton support
77 *
78 * Revision 1.2  2005/02/25 15:13:01  justb
79 * session id generation more robust
80 *
81 * Revision 1.1  2005/02/21 16:59:09  justb
82 * SessionManager and session lease introduced
83 *
84
85 *
86 */
87