Use Destroyable values instead of session listeners.
[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     
13     public static interface Storage {
14         public Session get(Request req);
15     }
16     
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());
60         }
61     }
62     
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         }
115
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));
137         }
138     }
139     
140     protected Session(Request req) {
141         int ct;
142         ct = Integer.parseInt(req.ctx().libconfig("jsvc.session.expire", "0"));
143         if(ct > 0)
144             etime = ct;
145         ct = Integer.parseInt(req.ctx().sysconfig("jsvc.session.expire", "0"));
146         if(ct > 0)
147             etime = ct;
148     }
149     
150     public abstract String id();
151     
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);
158         }
159     }
160     
161     public synchronized Object put(Object key, Object val) {
162         return(props.put(key, val));
163     }
164     
165     public void destroy() {
166         synchronized(props) {
167             for(Object val : props.values()) {
168                 if(val instanceof Destroyable)
169                     ((Destroyable)val).destroy();
170             }
171         }
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();
187         }
188         return(sess);
189     }
190     
191     public static Session get() {
192         return(get(RequestThread.request()));
193     }
194 }