| SessionManager.java |
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 ©
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| SessionManager.java |