--- /dev/null
+<?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>
--- /dev/null
+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);
+}
--- /dev/null
+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();
+}
--- /dev/null
+package dolda.jsvc;
+
+public interface Responder {
+ public void respond(Request request);
+}
--- /dev/null
+package dolda.jsvc;
+
+public class RootResponder implements Responder {
+ public void respond(Request req) {
+ }
+}
--- /dev/null
+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));
+ }
+ }
+}
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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));
+ }
+}
--- /dev/null
+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");
+ }
+}
--- /dev/null
+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();
+}
--- /dev/null
+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() {}
+}
--- /dev/null
+<?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>