Improved the store library massively and added a preliminary filesystem store.
authorFredrik Tolf <fredrik@dolda2000.com>
Fri, 16 Oct 2009 00:16:09 +0000 (02:16 +0200)
committerFredrik Tolf <fredrik@dolda2000.com>
Fri, 16 Oct 2009 00:31:20 +0000 (02:31 +0200)
src/dolda/jsvc/store/File.java [new file with mode: 0644]
src/dolda/jsvc/store/FileStore.java [new file with mode: 0644]
src/dolda/jsvc/store/Store.java

diff --git a/src/dolda/jsvc/store/File.java b/src/dolda/jsvc/store/File.java
new file mode 100644 (file)
index 0000000..56b80b9
--- /dev/null
@@ -0,0 +1,12 @@
+package dolda.jsvc.store;
+
+import dolda.jsvc.*;
+import java.io.*;
+
+public interface File {
+    public InputStream read();
+    public OutputStream store();
+    public long mtime();
+    public void remove();
+    public String name();
+}
diff --git a/src/dolda/jsvc/store/FileStore.java b/src/dolda/jsvc/store/FileStore.java
new file mode 100644 (file)
index 0000000..17a8f88
--- /dev/null
@@ -0,0 +1,249 @@
+package dolda.jsvc.store;
+
+import dolda.jsvc.*;
+import dolda.jsvc.util.*;
+import java.io.*;
+import java.util.*;
+import java.security.*;
+
+class FileStore extends Store {
+    private final java.io.File base;
+    private static final int smbuflimit;
+    private static int txserial = 0;
+    
+    static {
+       int res; /* Java is stupid, as usual... */
+       try {
+           String p = System.getProperty("dolda.jsvc.store.smallbuf");
+           if(p != null)
+               res = Integer.parseInt(p);
+           else
+               res = 65536;
+       } catch(SecurityException e) {
+           res = 65536;
+       }
+       smbuflimit = res;
+    }
+
+    private FileStore(Package pkg, java.io.File root) {
+       super(pkg);
+       String nm = pkg.getName();
+       java.io.File base = root;
+       int p = 0;
+       int p2;
+       while((p2 = nm.indexOf('.', p)) >= 0) {
+           base = new java.io.File(base, nm.substring(p, p2));
+           p = p2 + 1;
+       }
+       this.base = new java.io.File(base, nm.substring(p));
+       AccessController.doPrivileged(new PrivilegedAction<Object>() {
+               public Object run() {
+                   if(!FileStore.this.base.mkdirs())
+                       throw(new RuntimeException("Could not create store directory (Java won't tell me why)"));
+                   return(null);
+               }
+           });
+    }
+
+    private static String mangle(String in) {
+       byte[] bytes = in.getBytes(Misc.utf8);
+       StringBuilder buf = new StringBuilder();
+       for(int i = 0; i < bytes.length; i++) {
+           byte b = bytes[i];
+           if(((b >= '0') && (b <= '9')) || ((b >= 'A') && (b <= 'Z')) || ((b >= 'a') && (b <= 'z'))) {
+               buf.append((char)b);
+           } else {
+               buf.append('_');
+               buf.append(Misc.int2hex((b & 0xf0) >> 4, true));
+               buf.append(Misc.int2hex(b & 0x0f, true));
+           }
+       }
+       return(buf.toString());
+    }
+    
+    private static String demangle(String in) {
+       ByteArrayOutputStream buf = new ByteArrayOutputStream();
+       for(int i = 0; i < in.length(); i++) {
+           char c = in.charAt(i);
+           if(c == '_') {
+               char d1 = in.charAt(i + 1);
+               char d2 = in.charAt(i + 2);
+               i += 2;
+               buf.write((byte)((Misc.hex2int(d1) << 4) | Misc.hex2int(d2)));
+           } else {
+               if(c >= 256)
+                   throw(new RuntimeException("Invalid filename in store"));
+               buf.write(c);
+           }
+       }
+       byte[] bytes = buf.toByteArray();
+       return(new String(bytes, Misc.utf8));
+    }
+    
+    private class RFile implements File {
+       private final java.io.File fs;
+       private final String name;
+       
+       private class TXStream extends OutputStream {
+           private FileOutputStream buf = null;
+           private java.io.File tmpfile;
+           private boolean closed = false;
+           
+           private void init() throws IOException {
+               try {
+                   buf = AccessController.doPrivileged(new PrivilegedExceptionAction<FileOutputStream>() {
+                           public FileOutputStream run() throws IOException {
+                               synchronized(RFile.class) {
+                                   int serial = txserial++;
+                                   tmpfile = new java.io.File(fs.getPath() + ".new." + txserial);
+                                   if(tmpfile.exists()) {
+                                       if(!tmpfile.delete())
+                                           throw(new IOException("Could not delete previous temporary file (Java won't tell my why)"));
+                                   }
+                                   return(new FileOutputStream(tmpfile));
+                               }
+                           }
+                       });
+               } catch(PrivilegedActionException e) {
+                   throw((IOException)e.getCause());
+               }
+           }
+
+           public void write(byte[] b, int off, int len) throws IOException {
+               if(closed)
+                   throw(new IOException("This file has already been committed"));
+               if(buf == null)
+                   init();
+               buf.write(b, off, len);
+           }
+
+           public void write(byte[] b) throws IOException {
+               if(closed)
+                   throw(new IOException("This file has already been committed"));
+               if(buf == null)
+                   init();
+               buf.write(b);
+           }
+
+           public void write(int b) throws IOException {
+               if(closed)
+                   throw(new IOException("This file has already been committed"));
+               if(buf == null)
+                   init();
+               buf.write(b);
+           }
+           
+           public void flush() throws IOException {
+               if(buf == null)
+                   init();
+               buf.flush();
+           }
+           
+           public void close() throws IOException {
+               flush();
+               closed = true;
+               buf.close();
+               try {
+                   AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
+                           public Object run() throws IOException {
+                               if(!tmpfile.renameTo(fs)) {
+                                   fs.delete();
+                                   if(!tmpfile.renameTo(fs))
+                                       throw(new IOException("Could not replace previous file contents with new (Java won't tell me why)"));
+                               }
+                               return(null);
+                           }
+                       });
+               } catch(PrivilegedActionException e) {
+                   throw((IOException)e.getCause());
+               }
+           }
+       }
+
+       private RFile(java.io.File fs, String name) {
+           this.fs = fs;
+           this.name = name;
+       }
+       
+       public String name() {
+           return(name);
+       }
+       
+       public InputStream read() {
+           return(AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
+                       public InputStream run() {
+                           try {
+                               return(new FileInputStream(fs));
+                           } catch(FileNotFoundException e) {
+                               return(null);
+                           }
+                       }
+                   }));
+       }
+       
+       public OutputStream store() {
+           return(new TXStream());
+       }
+       
+       public long mtime() {
+           return(AccessController.doPrivileged(new PrivilegedAction<Long>() {
+                       public Long run() {
+                           return(fs.lastModified());
+                       }
+                   }));
+       }
+       
+       public void remove() {
+           AccessController.doPrivileged(new PrivilegedAction<Object>() {
+                   public InputStream run() {
+                       if(!fs.delete())
+                           throw(new RuntimeException("Could not delete the file " + fs.getPath() + " (Java won't tell me why)"));
+                       return(null);
+                   }
+               });
+       }
+    }
+
+    public File get(String name) {
+       return(new RFile(new java.io.File(base, mangle(name)), name));
+    }
+    
+    public Iterator<File> iterator() {
+       final java.io.File[] ls = base.listFiles();
+       return(new Iterator<File>() {
+               private int i = 0;
+               private File cur = null;
+               
+               public boolean hasNext() {
+                   return(i < ls.length);
+               }
+               
+               public File next() {
+                   java.io.File f = ls[i++];
+                   cur = new RFile(f, demangle(f.getName()));
+                   return(cur);
+               }
+               
+               public void remove() {
+                   if(cur == null)
+                       throw(new IllegalStateException());
+                   cur.remove();
+                   cur = null;
+               }
+           });
+    }
+    
+    public static void register() {
+       Store.register("file", new Factory() {
+               public Store create(String rootname, Package pkg) {
+                   java.io.File root = new java.io.File(rootname);
+                   ThreadContext ctx = ThreadContext.current();
+                   if(ctx != null) {
+                       if(ctx.server().name() != null)
+                           root = new java.io.File(root, ctx.server().name());
+                   }
+                   return(new FileStore(pkg, root));
+               }
+           });
+    }
+}
index 833c1da..0664156 100644 (file)
@@ -3,49 +3,68 @@ package dolda.jsvc.store;
 import dolda.jsvc.*;
 import dolda.jsvc.util.Misc;
 import java.io.*;
-import java.security.*;
-import java.security.cert.Certificate;
 import java.util.*;
 
-public class Store {
+public abstract class Store implements Iterable<File> {
+    private static Map<String, Factory> kinds = new TreeMap<String, Factory>();
     private static Map<Package, Store> interned = new WeakHashMap<Package, Store>();
-    private final Package pkg;
-    private final File base;
+    protected final Package pkg;
     
-    private Store(Package pkg, File root) {
+    protected Store(Package pkg) {
        this.pkg = pkg;
-       String nm = pkg.getName();
-       File base = root;
-       int p = 0;
-       int p2;
-       while((p2 = nm.indexOf('.', p)) >= 0) {
-           base = new File(base, nm.substring(p, p2));
-           p = p2 + 1;
-       }
-       this.base = new File(base, nm.substring(p));
     }
     
-    private static File getstoreroot() {
+    public abstract File get(String name);
+    
+    public static interface Factory {
+       public Store create(String root, Package pkg);
+    }
+
+    private static String getstoreroot() {
        ThreadContext ctx = ThreadContext.current();
        if(ctx == null)
            throw(new RuntimeException("Not running in jsvc context"));
        String bn = ctx.server().config("jsvc.storage");
        if(bn == null)
            throw(new RuntimeException("No storage root has been configured"));
-       return(new File(bn));
+       return(bn);
     }
-
+    
     public static Store forclass(final Class<?> cl) {
        Package pkg = cl.getPackage();
-       File root = getstoreroot();
        Store s;
        synchronized(interned) {
            s = interned.get(pkg);
            if(s == null) {
-               s = new Store(pkg, root);
+               String root = getstoreroot();
+               int p = root.indexOf(':');
+               if(p < 0)
+                   throw(new RuntimeException("Invalid store specification: " + root));
+               String kind = root.substring(0, p);
+               root = root.substring(p + 1);
+               Factory fac;
+               synchronized(kinds) {
+                   fac = kinds.get(kind);
+                   if(fac == null)
+                       throw(new RuntimeException("No such store kind: " + kind));
+               }
+               s = fac.create(root, pkg);
                interned.put(pkg, s);
            }
        }
        return(s);
     }
+    
+    public static void register(String kind, Factory fac) {
+       synchronized(kinds) {
+           if(!kinds.containsKey(kind))
+               kinds.put(kind, fac);
+           else
+               throw(new RuntimeException("Store of type " + kind + " already exists"));
+       }
+    }
+    
+    static {
+       FileStore.register();
+    }
 }