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