Initial commit with hopefully working J2EE request handler.
authorFredrik Tolf <fredrik@dolda2000.com>
Sun, 4 Oct 2009 05:14:17 +0000 (07:14 +0200)
committerFredrik Tolf <fredrik@dolda2000.com>
Sun, 4 Oct 2009 05:14:17 +0000 (07:14 +0200)
13 files changed:
.gitignore [new file with mode: 0644]
build.xml [new file with mode: 0644]
src/dolda/jsvc/MultiMap.java [new file with mode: 0644]
src/dolda/jsvc/Request.java [new file with mode: 0644]
src/dolda/jsvc/Responder.java [new file with mode: 0644]
src/dolda/jsvc/RootResponder.java [new file with mode: 0644]
src/dolda/jsvc/j2ee/J2eeRequest.java [new file with mode: 0644]
src/dolda/jsvc/j2ee/Servlet.java [new file with mode: 0644]
src/dolda/jsvc/util/HeaderTreeMap.java [new file with mode: 0644]
src/dolda/jsvc/util/Misc.java [new file with mode: 0644]
src/dolda/jsvc/util/ResponseBuffer.java [new file with mode: 0644]
src/dolda/jsvc/util/WrappedMultiMap.java [new file with mode: 0644]
www/web.xml [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..796b96d
--- /dev/null
@@ -0,0 +1 @@
+/build
diff --git a/build.xml b/build.xml
new file mode 100644 (file)
index 0000000..0e6d4e6
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+
+<project name="haven-jsvc" default="jsvc-jar">
+    
+   <property environment="env" />
+
+   <!--
+   <path id="classpath">
+      <pathelement path="${env.CATALINA_HOME}/common/lib/servlet-api.jar" />
+   </path>
+   -->
+    
+   <target name="build-env">
+      <mkdir dir="build" />
+      <mkdir dir="build/classes" />
+      <mkdir dir="build/webapp" />
+      <mkdir dir="build/webapp/WEB-INF/classes" />
+    </target>
+
+    <target name="jsvc" depends="build-env">
+      <javac srcdir="src" destdir="build/classes" debug="on">
+       <!-- <classpath refid="classpath" /> -->
+        <compilerarg value="-Xlint:unchecked" />
+      </javac>
+    </target>
+    
+    <target name="jsvc-jar" depends="build-env, jsvc">
+        <jar destfile="build/jsvc.jar" basedir="build/classes" />
+    </target>
+    
+    <target name="webapp" depends="build-env">
+       <mkdir dir="build/webapp/WEB-INF/lib" />
+       <copy file="build/jsvc.jar" tofile="build/webapp/WEB-INF/lib/jsvc.jar" />
+       <copy todir="build/webapp/WEB-INF/lib">
+         <fileset dir="lib" />
+       </copy>
+    </target>
+
+    <target name="war" depends="build-env, jsvc-jar, webapp">
+       <war destfile="build/jsvc.war"
+           basedir="build/webapp"
+           webxml="www/web.xml"
+           />
+    </target>
+
+    <target name="clean">
+       <delete dir="build" />
+    </target>
+</project>
diff --git a/src/dolda/jsvc/MultiMap.java b/src/dolda/jsvc/MultiMap.java
new file mode 100644 (file)
index 0000000..6f89dc6
--- /dev/null
@@ -0,0 +1,9 @@
+package dolda.jsvc;
+
+import java.util.*;
+
+public interface MultiMap<K, V> extends Map<K, V> {
+    public Collection<V> values(K key);
+    public void add(K key, V value);
+    public Collection<V> putValues(K key, Collection<V> values);
+}
diff --git a/src/dolda/jsvc/Request.java b/src/dolda/jsvc/Request.java
new file mode 100644 (file)
index 0000000..fbadd23
--- /dev/null
@@ -0,0 +1,24 @@
+package dolda.jsvc;
+
+import java.io.*;
+import java.net.URL;
+import java.util.Map;
+
+public interface Request {
+    /* Input */
+    public URL url();
+    public String method();
+    public String path();
+    public InputStream input();
+    public MultiMap<String, String> inheaders();
+    public MultiMap<String, String> params();
+    
+    /* Output */
+    public OutputStream output();
+    public void status(int code);
+    public void status(int code, String message);
+    public MultiMap<String, String> outheaders();
+    
+    /* Misc. */
+    public Map<?, ?> props();
+}
diff --git a/src/dolda/jsvc/Responder.java b/src/dolda/jsvc/Responder.java
new file mode 100644 (file)
index 0000000..d6abfb1
--- /dev/null
@@ -0,0 +1,5 @@
+package dolda.jsvc;
+
+public interface Responder {
+    public void respond(Request request);
+}
diff --git a/src/dolda/jsvc/RootResponder.java b/src/dolda/jsvc/RootResponder.java
new file mode 100644 (file)
index 0000000..5b7623e
--- /dev/null
@@ -0,0 +1,6 @@
+package dolda.jsvc;
+
+public class RootResponder implements Responder {
+    public void respond(Request req) {
+    }
+}
diff --git a/src/dolda/jsvc/j2ee/J2eeRequest.java b/src/dolda/jsvc/j2ee/J2eeRequest.java
new file mode 100644 (file)
index 0000000..a5e7518
--- /dev/null
@@ -0,0 +1,126 @@
+package dolda.jsvc.j2ee;
+
+import dolda.jsvc.*;
+import dolda.jsvc.util.*;
+import java.io.*;
+import java.util.*;
+import java.net.*;
+import javax.servlet.*;
+import javax.servlet.http.*;
+
+public class J2eeRequest extends ResponseBuffer {
+    private ServletConfig cfg;
+    private HttpServletRequest req;
+    private HttpServletResponse resp;
+    private String method, path;
+    private URL url;
+    private Map<?, ?> props = new HashMap();
+    
+    public J2eeRequest(ServletConfig cfg, HttpServletRequest req, HttpServletResponse resp) {
+       this.cfg = cfg;
+       this.req = req;
+       this.resp = resp;
+       try {
+           req.setCharacterEncoding("UTF-8");
+           resp.setCharacterEncoding("UTF-8");
+       } catch(UnsupportedEncodingException e) {
+           throw(new AssertionError(e));
+       }
+       {
+           String host = req.getHeader("Host");
+           if((host == null) || (host.length() < 1))
+               host = req.getLocalAddr();
+           String pi = req.getPathInfo();
+           if(pi == null)
+               pi = "";
+           String q = req.getQueryString();
+           if(q != null)
+               q = "?" + q;
+           else
+               q = "";
+           try {
+               url = new URL(req.getScheme(), host, req.getServerPort(), req.getContextPath() + req.getServletPath() + pi + q);
+           } catch(MalformedURLException e) {
+               throw(new Error(e));
+           }
+       }
+       method = req.getMethod().toUpperCase().intern();
+       path = req.getPathInfo();
+       while((path.length() > 0) && (path.charAt(0) == '/'))
+           path = path.substring(1);
+    }
+    
+    public Map<?, ?> props() {
+       return(props);
+    }
+    
+    public URL url() {
+       return(url);
+    }
+    
+    public String method() {
+       return(method);
+    }
+    
+    public String path() {
+       return(path);
+    }
+
+    public InputStream input() {
+       try {
+           return(req.getInputStream());
+       } catch(IOException e) {
+           /* It is not obvious why this would happen, so I'll wait
+            * until I know whatever might happen to try and implement
+            * meaningful behavior. */
+           throw(new RuntimeException(e));
+       }
+    }
+
+    public MultiMap<String, String> inheaders() {
+       MultiMap<String, String> h = new HeaderTreeMap();
+       Enumeration ki = req.getHeaderNames();
+       if(ki != null) {
+           while(ki.hasMoreElements()) {
+               String k = (String)ki.nextElement();
+               Enumeration vi = req.getHeaders(k);
+               if(vi != null) {
+                   while(vi.hasMoreElements()) {
+                       String v = (String)vi.nextElement();
+                       h.add(k, v);
+                   }
+               }
+           }
+       }
+       return(h);
+    }
+    
+    public MultiMap<String, String> params() {
+       return(null);
+    }
+    
+    protected void backflush() {
+       for(String key : outheaders().keySet()) {
+           boolean first = true;
+           for(String val : outheaders().values(key)) {
+               if(first) {
+                   resp.setHeader(key, val);
+                   first = false;
+               } else {
+                   resp.addHeader(key, val);
+               }
+           }
+       }
+    }
+    
+    protected OutputStream realoutput() {
+       try {
+           return(resp.getOutputStream());
+       } catch(IOException e) {
+           /* It is not obvious why this would happen, so I'll wait
+            * until I know whatever might happen to try and implement
+            * meaningful behavior. */
+           throw(new RuntimeException(e));
+       }
+    }
+}
diff --git a/src/dolda/jsvc/j2ee/Servlet.java b/src/dolda/jsvc/j2ee/Servlet.java
new file mode 100644 (file)
index 0000000..d050c01
--- /dev/null
@@ -0,0 +1,24 @@
+package dolda.jsvc.j2ee;
+
+import dolda.jsvc.*;
+import java.io.*;
+import javax.servlet.http.*;
+
+public class Servlet extends HttpServlet {
+    private Responder root;
+    
+    public void init() {
+       
+    }
+    
+    public void service(HttpServletRequest req, HttpServletResponse resp) {
+       try {
+           req.setCharacterEncoding("UTF-8");
+           resp.setCharacterEncoding("UTF-8");
+       } catch(UnsupportedEncodingException e) {
+           throw(new Error(e));
+       }
+       Request rr = new J2eeRequest(getServletConfig(), req, resp);
+       root.respond(rr);
+    }
+}
diff --git a/src/dolda/jsvc/util/HeaderTreeMap.java b/src/dolda/jsvc/util/HeaderTreeMap.java
new file mode 100644 (file)
index 0000000..f0d9d76
--- /dev/null
@@ -0,0 +1,15 @@
+package dolda.jsvc.util;
+
+import java.util.*;
+
+public class HeaderTreeMap extends WrappedMultiMap<String, String> {
+    private static final Comparator<String> cicmp = new Comparator<String>() {
+       public int compare(String a, String b){
+           return(a.toLowerCase().compareTo(b.toLowerCase()));
+       }
+    };
+    
+    public HeaderTreeMap() {
+       super(new TreeMap<String, Collection<String>>(cicmp));
+    }
+}
diff --git a/src/dolda/jsvc/util/Misc.java b/src/dolda/jsvc/util/Misc.java
new file mode 100644 (file)
index 0000000..c313183
--- /dev/null
@@ -0,0 +1,18 @@
+package dolda.jsvc.util;
+
+import java.util.*;
+
+public class Misc {
+    private static Map<Integer, String> stext = new HashMap<Integer, String>();
+    
+    static {
+       stext.put(200, "OK");
+    }
+    
+    public static String statustext(int status) {
+       String text;
+       if((text = stext.get(status)) != null)
+           return(text);
+       return("Unknown Response");
+    }
+}
diff --git a/src/dolda/jsvc/util/ResponseBuffer.java b/src/dolda/jsvc/util/ResponseBuffer.java
new file mode 100644 (file)
index 0000000..7c57608
--- /dev/null
@@ -0,0 +1,86 @@
+package dolda.jsvc.util;
+
+import dolda.jsvc.*;
+import java.io.*;
+import java.util.*;
+
+public abstract class ResponseBuffer implements Request {
+    private boolean flushed = false;
+    private int respcode = -1;
+    private String resptext = null;
+    private OutputStream out = null, wrapout = null;
+    private MultiMap<String, String> headers = new HeaderTreeMap() {
+           protected void modified() {
+               ckflush();
+           }
+    };
+    
+    private void ckflush() {
+       if(flushed)
+           throw(new IllegalStateException("Response has been flushed; header information cannot be modified"));
+    }
+    
+    private void flush() {
+       if(flushed)
+           return;
+       if(respcode < 0) {
+           respcode = 200;
+           resptext = "OK";
+       }
+       backflush();
+       out = realoutput();
+       flushed = true;
+    }
+
+    private class FlushStream extends OutputStream {
+       private FlushStream() {
+       }
+       
+       public void flush() throws IOException {
+           ResponseBuffer.this.flush();
+           out.flush();
+       }
+       
+       public void close() throws IOException {
+           flush();
+           out.close();
+       }
+       
+       public void write(int b) throws IOException {
+           ResponseBuffer.this.flush();
+           out.write(b);
+       }
+       
+       public void write(byte[] b) throws IOException {
+           write(b, 0, b.length);
+       }
+       
+       public void write(byte[] b, int off, int len) throws IOException {
+           ResponseBuffer.this.flush();
+           out.write(b, off, len);
+       }
+    }
+
+    public OutputStream output() {
+       if(wrapout == null)
+           wrapout = new BufferedOutputStream(new FlushStream(), 16384);
+       return(wrapout);
+    }
+
+    public void status(int code) {
+       status(code, Misc.statustext(code));
+    }
+    
+    public void status(int code, String text) {
+       ckflush();
+       respcode = code;
+       resptext = text;
+    }
+    
+    public MultiMap<String, String> outheaders() {
+       return(headers);
+    }
+    
+    protected abstract void backflush();
+    protected abstract OutputStream realoutput();
+}
diff --git a/src/dolda/jsvc/util/WrappedMultiMap.java b/src/dolda/jsvc/util/WrappedMultiMap.java
new file mode 100644 (file)
index 0000000..42f211c
--- /dev/null
@@ -0,0 +1,233 @@
+package dolda.jsvc.util;
+
+import dolda.jsvc.MultiMap;
+import java.util.*;
+
+public class WrappedMultiMap<K, V> implements MultiMap<K, V> {
+    private Map<K, Collection<V>> bk;
+    private EntrySet entryset;
+    private Values values;
+    
+    public WrappedMultiMap(Map<K, Collection<V>> bk) {
+       this.bk = bk;
+    }
+    
+    private V get1(Collection<V> vs) {
+       if(vs == null)
+           return(null);
+       Iterator<V> i = vs.iterator();
+       if(!i.hasNext())
+           return(null);
+       return(i.next());
+    }
+
+    public void clear() {
+       modified();
+       bk.clear();
+    }
+    
+    public boolean containsKey(Object key) {
+       Collection<V> vs = bk.get(key);
+       if(vs == null)
+           return(false);
+       return(!vs.isEmpty());
+    }
+    
+    public boolean equals(Object o) {
+       return(bk.equals(o));
+    }
+    
+    public V get(Object key) {
+       return(get1(bk.get(key)));
+    }
+    
+    public Collection<V> values(K key) {
+       Collection<V> vs = bk.get(key);
+       if(vs == null) {
+           vs = new LinkedList<V>();
+           bk.put(key, vs);
+       }
+       return(vs);
+    }
+    
+    public int hashCode() {
+       return(bk.hashCode());
+    }
+    
+    public boolean isEmpty() {
+       return(values().isEmpty());
+    }
+    
+    public Set<K> keySet() {
+       return(bk.keySet());
+    }
+    
+    public V put(K key, V value) {
+       Collection<V> vs = new LinkedList<V>();
+       vs.add(value);
+       modified();
+       return(get1(bk.put(key, vs)));
+    }
+    
+    public void add(K key, V value) {
+       modified();
+       values(key).add(value);
+    }
+    
+    public Collection<V> putValues(K key, Collection<V> values) {
+       modified();
+       return(bk.put(key, values));
+    }
+    
+    private class DumbEntry implements Map.Entry<K, V> {
+       private K key;
+       private V value;
+       
+       public DumbEntry(K key, V value) {
+           this.key = key;
+           this.value = value;
+       }
+       
+       public boolean equals(Object o) {
+           if(!(o instanceof Map.Entry))
+               return(false);
+           Map.Entry oe = (Map.Entry)o;
+           return(((key == null)?(oe.getKey() == null):key.equals(oe.getKey())) &&
+                  ((value == null)?(oe.getValue() == null):value.equals(oe.getValue())));
+       }
+       
+       public K getKey() {
+           return(key);
+       }
+       
+       public V getValue() {
+           return(value);
+       }
+       
+       public int hashCode() {
+           return(key.hashCode() + value.hashCode());
+       }
+       
+       public V setValue(V value) {
+           throw(new UnsupportedOperationException());
+       }
+    }
+
+    private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
+       public Iterator<Map.Entry<K, V>> iterator() {
+           return(new Iterator<Map.Entry<K, V>>() {
+                   private Iterator<Map.Entry<K, Collection<V>>> bki = bk.entrySet().iterator();
+                   private K curkey;
+                   private Iterator<V> vsi = null;
+                   
+                   public boolean hasNext() {
+                       if((vsi != null) && vsi.hasNext())
+                           return(true);
+                       return(bki.hasNext());
+                   }
+                   
+                   public Map.Entry<K, V> next() {
+                       if((vsi == null) || !vsi.hasNext()) {
+                           Map.Entry<K, Collection<V>> ne = bki.next();
+                           curkey = ne.getKey();
+                           vsi = ne.getValue().iterator();
+                       }
+                       return(new DumbEntry(curkey, vsi.next()));
+                   }
+                   
+                   public void remove() {
+                       modified();
+                       vsi.remove();
+                   }
+               });
+       }
+       
+       public int size() {
+           return(WrappedMultiMap.this.size());
+       }
+       
+       public boolean remove(Object o) {
+           modified();
+           return(WrappedMultiMap.this.remove(o) != null);
+       }
+       
+       public void clear() {
+           modified();
+           bk.clear();
+       }
+    }
+
+    private class Values extends AbstractCollection<V> {
+       public Iterator<V> iterator() {
+           return(new Iterator<V>() {
+                   Iterator<Map.Entry<K, V>> bki = WrappedMultiMap.this.entrySet().iterator();
+                   
+                   public boolean hasNext() {
+                       return(bki.hasNext());
+                   }
+
+                   public V next() {
+                       return(bki.next().getValue());
+                   }
+                   
+                   public void remove() {
+                       modified();
+                       bki.remove();
+                   }
+               });
+       }
+       
+       public int size() {
+           return(WrappedMultiMap.this.size());
+       }
+       
+       public boolean contains(Object o) {
+           return(containsValue(o));
+       }
+       
+       public void clear() {
+           modified();
+           bk.clear();
+       }
+    }
+
+    public Set<Map.Entry<K, V>> entrySet() {
+       if(entryset == null)
+           entryset = new EntrySet();
+       return(entryset);
+    }
+    
+    public Collection<V> values() {
+       if(values == null)
+           values = new Values();
+       return(values);
+    }
+    
+    public void putAll(Map<? extends K, ? extends V> m) {
+       modified();
+       for(Map.Entry<? extends K, ? extends V> e : m.entrySet())
+           add(e.getKey(), e.getValue());
+    }
+    
+    public V remove(Object key) {
+       modified();
+       return(get1(bk.remove(key)));
+    }
+    
+    public boolean containsValue(Object value) {
+       for(Collection<V> vs : bk.values()) {
+           if(vs.contains(value))
+               return(true);
+       }
+       return(false);
+    }
+    
+    public int size() {
+       int i = 0;
+       for(Collection<V> vs : bk.values())
+           i += vs.size();
+       return(i);
+    }
+    
+    protected void modified() {}
+}
diff --git a/www/web.xml b/www/web.xml
new file mode 100644 (file)
index 0000000..e2e5908
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE web-app
+          PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+          "http://java.sun.com/dtd/web-app_2_3.dtd">
+
+<web-app>
+
+  <display-name>JSvc-wrapping</display-name>
+
+  <servlet>
+    <servlet-name>jsvc</servlet-name>
+    <servlet-class>dolda.jsvc.j2ee.Servlet</servlet-class>
+    <load-on-startup />
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>jsvc</servlet-name>
+    <url-pattern>/*</url-pattern>
+  </servlet-mapping>
+
+</web-app>