Indirected Session storage.
authorFredrik Tolf <fredrik@dolda2000.com>
Mon, 28 Mar 2011 23:10:14 +0000 (01:10 +0200)
committerFredrik Tolf <fredrik@dolda2000.com>
Mon, 28 Mar 2011 23:10:14 +0000 (01:10 +0200)
src/dolda/jsvc/util/Session.java

index eb17fcf..01c6d16 100644 (file)
@@ -4,130 +4,196 @@ import dolda.jsvc.*;
 import java.util.*;
 import java.security.SecureRandom;
 
-public class Session implements java.io.Serializable {
-    private static final Map<String, Session> sessions = new HashMap<String, Session>();
+public abstract class Session implements java.io.Serializable {
+    public static final ContextParam<Storage> store = new ContextParam<Storage>(new MemoryStorage());
     private static final Map<Request, Session> cache = new WeakHashMap<Request, Session>();
-    private static final SecureRandom prng;
-    private static long lastclean = 0;
-    private final String id;
     private final Map<Object, Object> props = new IdentityHashMap<Object, Object>();
-    private long ctime = System.currentTimeMillis(), atime = ctime, etime = 86400 * 1000;
+    public long ctime = System.currentTimeMillis(), atime = ctime, etime = 86400 * 1000;
     private Collection<Listener> ll = new HashSet<Listener>();
     
-    static {
-       try {
-           prng = SecureRandom.getInstance("SHA1PRNG");
-       } catch(java.security.NoSuchAlgorithmException e) {
-           throw(new Error(e));
-       }
-    }
-    
     public static interface Listener {
        public void destroy(Session sess);
     }
     
-    private Session(String id) {
-       this.id = id;
-    }
-    
-    public synchronized void listen(Listener l) {
-       ll.add(l);
+    public static interface Storage {
+       public Session get(Request req);
     }
     
-    public synchronized Object get(Object key, Object def) {
-       if(props.containsKey(key))
-           return(props.get(key));
-       else
-           return(def);
-    }
-    
-    public synchronized Object put(Object key, Object val) {
-       return(props.put(key, val));
-    }
-    
-    public void destroy() {
-       synchronized(Session.class) {
-           sessions.remove(id);
+    public static abstract class BaseStorage implements Storage {
+       private static final SecureRandom prng;
+       
+       static {
+           try {
+               prng = SecureRandom.getInstance("SHA1PRNG");
+           } catch(java.security.NoSuchAlgorithmException e) {
+               throw(new Error(e));
+           }
+       }
+       
+       public Session get(Request req) {
+           MultiMap<String, Cookie> cookies = Cookie.get(req);
+           Cookie sc = cookies.get("jsvc-session");
+
+           Session sess = null;
+           if(sc != null)
+               sess = get(sc.value);
+           if(sess == null) {
+               sess = create(req);
+               sc = new Cookie("jsvc-session", sess.id());
+               sc.expires = new Date(System.currentTimeMillis() + (86400L * 365L * 1000L));
+               sc.path = req.ctx().sysconfig("jsvc.session.path", req.rooturl().getPath());
+               String pd = req.ctx().sysconfig("jsvc.session.domain", null);
+               if(pd != null)
+                   sc.domain = pd;
+               sc.addto(req);
+           }
+           return(sess);
+       }
+       
+       protected abstract Session get(String id);
+       protected abstract Session create(Request req);
+
+       public static String newid() {
+           byte[] rawid = new byte[16];
+           prng.nextBytes(rawid);
+           StringBuilder buf = new StringBuilder();
+           for(byte b : rawid) {
+               buf.append(Misc.int2hex((b & 0xf0) >> 4, false));
+               buf.append(Misc.int2hex(b & 0x0f, false));
+           }
+           return(buf.toString());
        }
-       expire();
-    }
-    
-    private synchronized void expire() {
-       for(Listener l : ll)
-           l.destroy(this);
     }
     
-    public synchronized static int num() {
-       return(sessions.size());
-    }
+    public static class MemoryStorage extends BaseStorage {
+       private final Map<Long, MemorySession> sessions = new HashMap<Long, MemorySession>();
+       private static long lastclean = 0;
+       
+       private class MemorySession extends Session {
+           private final long id = BaseStorage.prng.nextLong();
+           
+           private MemorySession(Request req) {
+               super(req);
+           }
+           
+           public void destroy() {
+               synchronized(sessions) {
+                   sessions.remove(id);
+               }
+               super.destroy();
+           }
+           
+           private void expire() {
+               super.destroy();
+           }
+           
+           public String id() {
+               return(Long.toString(id));
+           }
+       }
+       
+       public int num() {
+           synchronized(sessions) {
+               return(sessions.size());
+           }
+       }
+       
+       public Session get(String id) {
+           long idl;
+           try {
+               idl = Long.parseLong(id);
+           } catch(NumberFormatException e) {
+               return(null);
+           }
+           synchronized(sessions) {
+               return(sessions.get(idl));
+           }
+       }
+       
+       public synchronized Session create(Request req) {
+           MemorySession sess = new MemorySession(req);
+           synchronized(sessions) {
+               sessions.put(sess.id, sess);
+           }
+           return(sess);
+       }
 
-    private static String newid() {
-       byte[] rawid = new byte[16];
-       prng.nextBytes(rawid);
-       StringBuilder buf = new StringBuilder();
-       for(byte b : rawid) {
-           buf.append(Misc.int2hex((b & 0xf0) >> 4, false));
-           buf.append(Misc.int2hex(b & 0x0f, false));
+       private void clean() {
+           long now = System.currentTimeMillis();
+           synchronized(sessions) {
+               for(Iterator<MemorySession> i = sessions.values().iterator(); i.hasNext();) {
+                   MemorySession sess = i.next();
+                   if(now > sess.atime + sess.etime) {
+                       i.remove();
+                       sess.expire();
+                   }
+               }
+           }
+       }
+       
+       public Session get(Request req) {
+           long now = System.currentTimeMillis();
+           if(now - lastclean > 3600 * 1000) {
+               clean();
+               lastclean = now;
+           }
+       
+           return(super.get(req));
        }
-       return(buf.toString());
     }
-
-    private static Session create(Request req, String id) {
-       Session sess = new Session(id);
-       long etime = 0;
+    
+    protected Session(Request req) {
        int ct;
        ct = Integer.parseInt(req.ctx().libconfig("jsvc.session.expire", "0"));
        if(ct > 0)
-           sess.etime = ct;
+           etime = ct;
        ct = Integer.parseInt(req.ctx().sysconfig("jsvc.session.expire", "0"));
        if(ct > 0)
-           sess.etime = ct;
-       return(sess);
+           etime = ct;
     }
     
-    private synchronized static void clean() {
-       long now = System.currentTimeMillis();
-       for(Iterator<Session> i = sessions.values().iterator(); i.hasNext();) {
-           Session sess = i.next();
-           if(now > sess.atime + sess.etime) {
-               i.remove();
-               sess.expire();
-           }
+    public abstract String id();
+    
+    public void listen(Listener l) {
+       synchronized(ll) {
+           ll.add(l);
        }
     }
-
-    public synchronized static Session get(Request req) {
-       long now = System.currentTimeMillis();
-       if(now - lastclean > 3600 * 1000) {
-           clean();
-           lastclean = now;
+    
+    public Object get(Object key, Object def) {
+       synchronized(props) {
+           if(props.containsKey(key))
+               return(props.get(key));
+           else
+               return(def);
        }
-       
-       Session sess = cache.get(req);
-       if(sess != null) {
-           sess.atime = System.currentTimeMillis();
-           return(sess);
+    }
+    
+    public synchronized Object put(Object key, Object val) {
+       return(props.put(key, val));
+    }
+    
+    public void destroy() {
+       synchronized(ll) {
+           for(Listener l : ll)
+               l.destroy(this);
        }
-       
-       MultiMap<String, Cookie> cookies = Cookie.get(req);
-       Cookie sc = cookies.get("jsvc-session");
-
-       if(sc != null)
-           sess = sessions.get(sc.value);
-       if(sess == null) {
-           sess = create(req, newid());
-           sessions.put(sess.id, sess);
-           sc = new Cookie("jsvc-session", sess.id);
-           sc.expires = new Date(System.currentTimeMillis() + (86400L * 365L * 1000L));
-           sc.path = req.ctx().sysconfig("jsvc.session.path", req.rooturl().getPath());
-           String pd = req.ctx().sysconfig("jsvc.session.domain", null);
-           if(pd != null)
-               sc.domain = pd;
-           sc.addto(req);
+    }
+    
+    public static Session get(Request req) {
+       Session sess;
+       synchronized(req) {
+           synchronized(cache) {
+               sess = cache.get(req);
+           }
+           if(sess == null) {
+               sess = store.get().get(req);
+               synchronized(cache) {
+                   cache.put(req, sess);
+               }
+           }
+           sess.atime = System.currentTimeMillis();
        }
-       
-       cache.put(req, sess);
-       sess.atime = System.currentTimeMillis();
        return(sess);
     }