Indirected Session storage.
[jsvc.git] / src / dolda / jsvc / util / Session.java
CommitLineData
d5dd6a2d
FT
1package dolda.jsvc.util;
2
3import dolda.jsvc.*;
4import java.util.*;
5import java.security.SecureRandom;
6
55a299a0
FT
7public abstract class Session implements java.io.Serializable {
8 public static final ContextParam<Storage> store = new ContextParam<Storage>(new MemoryStorage());
48ea770f 9 private static final Map<Request, Session> cache = new WeakHashMap<Request, Session>();
3d90f73a 10 private final Map<Object, Object> props = new IdentityHashMap<Object, Object>();
55a299a0 11 public long ctime = System.currentTimeMillis(), atime = ctime, etime = 86400 * 1000;
d5dd6a2d
FT
12 private Collection<Listener> ll = new HashSet<Listener>();
13
d5dd6a2d 14 public static interface Listener {
f0f0cfd1
FT
15 public void destroy(Session sess);
16 }
17
55a299a0
FT
18 public static interface Storage {
19 public Session get(Request req);
d5dd6a2d
FT
20 }
21
55a299a0
FT
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());
f0f0cfd1 65 }
d5dd6a2d
FT
66 }
67
55a299a0
FT
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 }
d5dd6a2d 120
55a299a0
FT
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));
d5dd6a2d 142 }
d5dd6a2d 143 }
55a299a0
FT
144
145 protected Session(Request req) {
d5dd6a2d
FT
146 int ct;
147 ct = Integer.parseInt(req.ctx().libconfig("jsvc.session.expire", "0"));
148 if(ct > 0)
55a299a0 149 etime = ct;
d5dd6a2d
FT
150 ct = Integer.parseInt(req.ctx().sysconfig("jsvc.session.expire", "0"));
151 if(ct > 0)
55a299a0 152 etime = ct;
d5dd6a2d
FT
153 }
154
55a299a0
FT
155 public abstract String id();
156
157 public void listen(Listener l) {
158 synchronized(ll) {
159 ll.add(l);
d5dd6a2d
FT
160 }
161 }
55a299a0
FT
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);
d5dd6a2d 169 }
55a299a0
FT
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);
70c1fc5a 180 }
55a299a0
FT
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();
d5dd6a2d
FT
196 }
197 return(sess);
198 }
199
200 public static Session get() {
201 return(get(RequestThread.request()));
202 }
203}