Indirected Session storage.
[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 abstract class Session implements java.io.Serializable {
8     public static final ContextParam<Storage> store = new ContextParam<Storage>(new MemoryStorage());
9     private static final Map<Request, Session> cache = new WeakHashMap<Request, Session>();
10     private final Map<Object, Object> props = new IdentityHashMap<Object, Object>();
11     public long ctime = System.currentTimeMillis(), atime = ctime, etime = 86400 * 1000;
12     private Collection<Listener> ll = new HashSet<Listener>();
13     
14     public static interface Listener {
15         public void destroy(Session sess);
16     }
17     
18     public static interface Storage {
19         public Session get(Request req);
20     }
21     
22     public static abstract class BaseStorage implements Storage {
23         private static final SecureRandom prng;
24         
25         static {
26             try {
27                 prng = SecureRandom.getInstance("SHA1PRNG");
28             } catch(java.security.NoSuchAlgorithmException e) {
29                 throw(new Error(e));
30             }
31         }
32         
33         public Session get(Request req) {
34             MultiMap<String, Cookie> cookies = Cookie.get(req);
35             Cookie sc = cookies.get("jsvc-session");
36
37             Session sess = null;
38             if(sc != null)
39                 sess = get(sc.value);
40             if(sess == null) {
41                 sess = create(req);
42                 sc = new Cookie("jsvc-session", sess.id());
43                 sc.expires = new Date(System.currentTimeMillis() + (86400L * 365L * 1000L));
44                 sc.path = req.ctx().sysconfig("jsvc.session.path", req.rooturl().getPath());
45                 String pd = req.ctx().sysconfig("jsvc.session.domain", null);
46                 if(pd != null)
47                     sc.domain = pd;
48                 sc.addto(req);
49             }
50             return(sess);
51         }
52         
53         protected abstract Session get(String id);
54         protected abstract Session create(Request req);
55
56         public static String newid() {
57             byte[] rawid = new byte[16];
58             prng.nextBytes(rawid);
59             StringBuilder buf = new StringBuilder();
60             for(byte b : rawid) {
61                 buf.append(Misc.int2hex((b & 0xf0) >> 4, false));
62                 buf.append(Misc.int2hex(b & 0x0f, false));
63             }
64             return(buf.toString());
65         }
66     }
67     
68     public static class MemoryStorage extends BaseStorage {
69         private final Map<Long, MemorySession> sessions = new HashMap<Long, MemorySession>();
70         private static long lastclean = 0;
71         
72         private class MemorySession extends Session {
73             private final long id = BaseStorage.prng.nextLong();
74             
75             private MemorySession(Request req) {
76                 super(req);
77             }
78             
79             public void destroy() {
80                 synchronized(sessions) {
81                     sessions.remove(id);
82                 }
83                 super.destroy();
84             }
85             
86             private void expire() {
87                 super.destroy();
88             }
89             
90             public String id() {
91                 return(Long.toString(id));
92             }
93         }
94         
95         public int num() {
96             synchronized(sessions) {
97                 return(sessions.size());
98             }
99         }
100         
101         public Session get(String id) {
102             long idl;
103             try {
104                 idl = Long.parseLong(id);
105             } catch(NumberFormatException e) {
106                 return(null);
107             }
108             synchronized(sessions) {
109                 return(sessions.get(idl));
110             }
111         }
112         
113         public synchronized Session create(Request req) {
114             MemorySession sess = new MemorySession(req);
115             synchronized(sessions) {
116                 sessions.put(sess.id, sess);
117             }
118             return(sess);
119         }
120
121         private void clean() {
122             long now = System.currentTimeMillis();
123             synchronized(sessions) {
124                 for(Iterator<MemorySession> i = sessions.values().iterator(); i.hasNext();) {
125                     MemorySession sess = i.next();
126                     if(now > sess.atime + sess.etime) {
127                         i.remove();
128                         sess.expire();
129                     }
130                 }
131             }
132         }
133         
134         public Session get(Request req) {
135             long now = System.currentTimeMillis();
136             if(now - lastclean > 3600 * 1000) {
137                 clean();
138                 lastclean = now;
139             }
140         
141             return(super.get(req));
142         }
143     }
144     
145     protected Session(Request req) {
146         int ct;
147         ct = Integer.parseInt(req.ctx().libconfig("jsvc.session.expire", "0"));
148         if(ct > 0)
149             etime = ct;
150         ct = Integer.parseInt(req.ctx().sysconfig("jsvc.session.expire", "0"));
151         if(ct > 0)
152             etime = ct;
153     }
154     
155     public abstract String id();
156     
157     public void listen(Listener l) {
158         synchronized(ll) {
159             ll.add(l);
160         }
161     }
162     
163     public Object get(Object key, Object def) {
164         synchronized(props) {
165             if(props.containsKey(key))
166                 return(props.get(key));
167             else
168                 return(def);
169         }
170     }
171     
172     public synchronized Object put(Object key, Object val) {
173         return(props.put(key, val));
174     }
175     
176     public void destroy() {
177         synchronized(ll) {
178             for(Listener l : ll)
179                 l.destroy(this);
180         }
181     }
182     
183     public static Session get(Request req) {
184         Session sess;
185         synchronized(req) {
186             synchronized(cache) {
187                 sess = cache.get(req);
188             }
189             if(sess == null) {
190                 sess = store.get().get(req);
191                 synchronized(cache) {
192                     cache.put(req, sess);
193                 }
194             }
195             sess.atime = System.currentTimeMillis();
196         }
197         return(sess);
198     }
199     
200     public static Session get() {
201         return(get(RequestThread.request()));
202     }
203 }