Added an explicit destruction action for sessions.
[jsvc.git] / src / dolda / jsvc / util / Session.java
1 package dolda.jsvc.util;
2
3 import dolda.jsvc.*;
4 import java.util.*;
5 import java.security.SecureRandom;
6
7 public class Session implements java.io.Serializable {
8     private static final Map<String, Session> sessions = new HashMap<String, Session>();
9     private static final Map<Request, Session> cache = new WeakHashMap<Request, Session>();
10     private static final SecureRandom prng;
11     private static long lastclean = 0;
12     private final String id;
13     private final Map<Object, Object> props = new HashMap<Object, Object>();
14     private long ctime = System.currentTimeMillis(), atime = ctime, etime = 86400 * 1000;
15     private Collection<Listener> ll = new HashSet<Listener>();
16     
17     static {
18         try {
19             prng = SecureRandom.getInstance("SHA1PRNG");
20         } catch(java.security.NoSuchAlgorithmException e) {
21             throw(new Error(e));
22         }
23     }
24     
25     public static interface Listener {
26         public void destroy(Session sess);
27     }
28     
29     private Session(String id) {
30         this.id = id;
31     }
32     
33     public synchronized void listen(Listener l) {
34         ll.add(l);
35     }
36     
37     public synchronized Object get(Object key, Object def) {
38         if(props.containsKey(key))
39             return(props.get(key));
40         else
41             return(def);
42     }
43     
44     public synchronized Object put(Object key, Object val) {
45         return(props.put(key, val));
46     }
47     
48     public void destroy() {
49         synchronized(Session.class) {
50             sessions.remove(id);
51         }
52         expire();
53     }
54     
55     private synchronized void expire() {
56         for(Listener l : ll)
57             l.destroy(this);
58     }
59     
60     public synchronized static int num() {
61         return(sessions.size());
62     }
63
64     private static String newid() {
65         byte[] rawid = new byte[16];
66         prng.nextBytes(rawid);
67         StringBuilder buf = new StringBuilder();
68         for(byte b : rawid) {
69             buf.append(Misc.int2hex((b & 0xf0) >> 4, false));
70             buf.append(Misc.int2hex(b & 0x0f, false));
71         }
72         return(buf.toString());
73     }
74
75     private static Session create(Request req, String id) {
76         Session sess = new Session(id);
77         long etime = 0;
78         int ct;
79         ct = Integer.parseInt(req.ctx().libconfig("jsvc.session.expire", "0"));
80         if(ct > 0)
81             sess.etime = ct;
82         ct = Integer.parseInt(req.ctx().sysconfig("jsvc.session.expire", "0"));
83         if(ct > 0)
84             sess.etime = ct;
85         return(sess);
86     }
87     
88     private synchronized static void clean() {
89         long now = System.currentTimeMillis();
90         for(Iterator<Session> i = sessions.values().iterator(); i.hasNext();) {
91             Session sess = i.next();
92             if(now > sess.atime + sess.etime) {
93                 i.remove();
94                 sess.expire();
95             }
96         }
97     }
98
99     public synchronized static Session get(Request req) {
100         long now = System.currentTimeMillis();
101         if(now - lastclean > 3600 * 1000) {
102             clean();
103             lastclean = now;
104         }
105         
106         Session sess = cache.get(req);
107         if(sess != null) {
108             sess.atime = System.currentTimeMillis();
109             return(sess);
110         }
111         
112         MultiMap<String, Cookie> cookies = Cookie.get(req);
113         Cookie sc = cookies.get("jsvc-session");
114
115         if(sc != null)
116             sess = sessions.get(sc.value);
117         if(sess == null) {
118             sess = create(req, newid());
119             sessions.put(sess.id, sess);
120             sc = new Cookie("jsvc-session", sess.id);
121             sc.expires = new Date(System.currentTimeMillis() + (86400L * 365L * 1000L));
122             sc.path = req.ctx().sysconfig("jsvc.session.path", req.rooturl().getPath());
123             String pd = req.ctx().sysconfig("jsvc.session.domain", null);
124             if(pd != null)
125                 sc.domain = pd;
126             sc.addto(req);
127         }
128         
129         cache.put(req, sess);
130         sess.atime = System.currentTimeMillis();
131         return(sess);
132     }
133     
134     public static Session get() {
135         return(get(RequestThread.request()));
136     }
137 }