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