From 78f5d1201f8d3aecb660b7877b08d9bfbe650911 Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Sun, 4 Oct 2009 07:14:17 +0200 Subject: [PATCH] Initial commit with hopefully working J2EE request handler. --- .gitignore | 1 + build.xml | 49 +++++++ src/dolda/jsvc/MultiMap.java | 9 ++ src/dolda/jsvc/Request.java | 24 ++++ src/dolda/jsvc/Responder.java | 5 + src/dolda/jsvc/RootResponder.java | 6 + src/dolda/jsvc/j2ee/J2eeRequest.java | 126 +++++++++++++++++ src/dolda/jsvc/j2ee/Servlet.java | 24 ++++ src/dolda/jsvc/util/HeaderTreeMap.java | 15 ++ src/dolda/jsvc/util/Misc.java | 18 +++ src/dolda/jsvc/util/ResponseBuffer.java | 86 ++++++++++++ src/dolda/jsvc/util/WrappedMultiMap.java | 233 +++++++++++++++++++++++++++++++ www/web.xml | 22 +++ 13 files changed, 618 insertions(+) create mode 100644 .gitignore create mode 100644 build.xml create mode 100644 src/dolda/jsvc/MultiMap.java create mode 100644 src/dolda/jsvc/Request.java create mode 100644 src/dolda/jsvc/Responder.java create mode 100644 src/dolda/jsvc/RootResponder.java create mode 100644 src/dolda/jsvc/j2ee/J2eeRequest.java create mode 100644 src/dolda/jsvc/j2ee/Servlet.java create mode 100644 src/dolda/jsvc/util/HeaderTreeMap.java create mode 100644 src/dolda/jsvc/util/Misc.java create mode 100644 src/dolda/jsvc/util/ResponseBuffer.java create mode 100644 src/dolda/jsvc/util/WrappedMultiMap.java create mode 100644 www/web.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..0e6d4e6 --- /dev/null +++ b/build.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dolda/jsvc/MultiMap.java b/src/dolda/jsvc/MultiMap.java new file mode 100644 index 0000000..6f89dc6 --- /dev/null +++ b/src/dolda/jsvc/MultiMap.java @@ -0,0 +1,9 @@ +package dolda.jsvc; + +import java.util.*; + +public interface MultiMap extends Map { + public Collection values(K key); + public void add(K key, V value); + public Collection putValues(K key, Collection values); +} diff --git a/src/dolda/jsvc/Request.java b/src/dolda/jsvc/Request.java new file mode 100644 index 0000000..fbadd23 --- /dev/null +++ b/src/dolda/jsvc/Request.java @@ -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 inheaders(); + public MultiMap params(); + + /* Output */ + public OutputStream output(); + public void status(int code); + public void status(int code, String message); + public MultiMap outheaders(); + + /* Misc. */ + public Map props(); +} diff --git a/src/dolda/jsvc/Responder.java b/src/dolda/jsvc/Responder.java new file mode 100644 index 0000000..d6abfb1 --- /dev/null +++ b/src/dolda/jsvc/Responder.java @@ -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 index 0000000..5b7623e --- /dev/null +++ b/src/dolda/jsvc/RootResponder.java @@ -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 index 0000000..a5e7518 --- /dev/null +++ b/src/dolda/jsvc/j2ee/J2eeRequest.java @@ -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 inheaders() { + MultiMap 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 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 index 0000000..d050c01 --- /dev/null +++ b/src/dolda/jsvc/j2ee/Servlet.java @@ -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 index 0000000..f0d9d76 --- /dev/null +++ b/src/dolda/jsvc/util/HeaderTreeMap.java @@ -0,0 +1,15 @@ +package dolda.jsvc.util; + +import java.util.*; + +public class HeaderTreeMap extends WrappedMultiMap { + private static final Comparator cicmp = new Comparator() { + public int compare(String a, String b){ + return(a.toLowerCase().compareTo(b.toLowerCase())); + } + }; + + public HeaderTreeMap() { + super(new TreeMap>(cicmp)); + } +} diff --git a/src/dolda/jsvc/util/Misc.java b/src/dolda/jsvc/util/Misc.java new file mode 100644 index 0000000..c313183 --- /dev/null +++ b/src/dolda/jsvc/util/Misc.java @@ -0,0 +1,18 @@ +package dolda.jsvc.util; + +import java.util.*; + +public class Misc { + private static Map stext = new HashMap(); + + 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 index 0000000..7c57608 --- /dev/null +++ b/src/dolda/jsvc/util/ResponseBuffer.java @@ -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 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 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 index 0000000..42f211c --- /dev/null +++ b/src/dolda/jsvc/util/WrappedMultiMap.java @@ -0,0 +1,233 @@ +package dolda.jsvc.util; + +import dolda.jsvc.MultiMap; +import java.util.*; + +public class WrappedMultiMap implements MultiMap { + private Map> bk; + private EntrySet entryset; + private Values values; + + public WrappedMultiMap(Map> bk) { + this.bk = bk; + } + + private V get1(Collection vs) { + if(vs == null) + return(null); + Iterator i = vs.iterator(); + if(!i.hasNext()) + return(null); + return(i.next()); + } + + public void clear() { + modified(); + bk.clear(); + } + + public boolean containsKey(Object key) { + Collection 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 values(K key) { + Collection vs = bk.get(key); + if(vs == null) { + vs = new LinkedList(); + bk.put(key, vs); + } + return(vs); + } + + public int hashCode() { + return(bk.hashCode()); + } + + public boolean isEmpty() { + return(values().isEmpty()); + } + + public Set keySet() { + return(bk.keySet()); + } + + public V put(K key, V value) { + Collection vs = new LinkedList(); + vs.add(value); + modified(); + return(get1(bk.put(key, vs))); + } + + public void add(K key, V value) { + modified(); + values(key).add(value); + } + + public Collection putValues(K key, Collection values) { + modified(); + return(bk.put(key, values)); + } + + private class DumbEntry implements Map.Entry { + 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> { + public Iterator> iterator() { + return(new Iterator>() { + private Iterator>> bki = bk.entrySet().iterator(); + private K curkey; + private Iterator vsi = null; + + public boolean hasNext() { + if((vsi != null) && vsi.hasNext()) + return(true); + return(bki.hasNext()); + } + + public Map.Entry next() { + if((vsi == null) || !vsi.hasNext()) { + Map.Entry> 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 { + public Iterator iterator() { + return(new Iterator() { + Iterator> 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> entrySet() { + if(entryset == null) + entryset = new EntrySet(); + return(entryset); + } + + public Collection values() { + if(values == null) + values = new Values(); + return(values); + } + + public void putAll(Map m) { + modified(); + for(Map.Entry 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 vs : bk.values()) { + if(vs.contains(value)) + return(true); + } + return(false); + } + + public int size() { + int i = 0; + for(Collection 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 index 0000000..e2e5908 --- /dev/null +++ b/www/web.xml @@ -0,0 +1,22 @@ + + + + + + + JSvc-wrapping + + + jsvc + dolda.jsvc.j2ee.Servlet + + + + + jsvc + /* + + + -- 2.11.0